950 likes | 1.04k Views
第 3 ç« æ ˆã€é˜Ÿåˆ—和数组. 3.1 æ ˆ 3.2 队列 3.3 数组和广义表. 3.1.1 æ ˆçš„å®šä¹‰å’Œè¿ç®—. Return. å‡ºæ ˆ. å…¥æ ˆ. æ ˆé¡¶ top. an. a2. æ ˆåº• bottom. a1. 图 3-1 æ ˆçš„ç¤ºæ„图. 1 ï¼Žæ ˆçš„å®šä¹‰
E N D
第 3 章 栈、队列和数组 3.1 栈 3.2 队列 3.3 数组和广义表
3.1.1 栈的定义和运算 Return 出栈 入栈 栈顶 top an a2 栈底 bottom a1 图3-1栈的示意图 1.栈的定义 栈(stack)是一种只允许在一端进行插入和删除的线性表,它是一种操作受限的线性表。在表中只允许进行插入和删除的一端称为栈顶(top),另一端称为栈底(bottom)。栈的插入操作通常称为入栈或进栈(push),而栈的删除操作则称为出栈或退栈(pop)。当栈中无数据元素时,称为空栈。 栈是按照后进先出(LIFO)的原则组织数据的,因此,栈也被称为“后进先出”的线性表。 ...
2.栈的运算 1.初始化栈init_stack(S) 操作结果:构造一个空栈S。 2.判断栈是否为空stack_empty(S) 初始条件:栈S已存在 操作结果:若栈S为空栈,则返回TRUE;否则,返回FALSE。 3.取栈顶元素值stack_top(S,x) 初始条件:栈S已存在且非空 操作结果: 用x返回S的栈顶元素
4.入栈 push_stack(S,x) 初始条件:栈S已存在 操作结果:插入元素x为新的栈顶元素 5.出栈 pop_stack(S) 初始条件:栈S已存在且非空 操作结果: 返回S的栈顶元素 6.判断栈是否已满 stack_full(S) 初始条件:栈S已存在 操作结果:栈S已满返回TRUE;否则返回FALSE。
3.1.2 顺序栈 顺序栈是用顺序存储结构实现的栈,即利用一组地址连续的存储单元依次存放自栈底到栈顶的数据元素,同时由于栈的操作的特殊性,还必须附设一个位置指针top(栈顶指针)来动态地指示栈顶元素在顺序栈中的位置。 通常以top=-1表示空栈。
data a1 a2 a3 a4 … an seqstack top 顺序栈的存储结构定义 #define maxsize 50 typedef struct stack { elementtype data[maxsize];/*用来存放栈中元素的一维数组*/ int top;/*用来存放栈顶元素的下标*/ }seqstack;
S->top=-1 S->top=0 S->top=4 S->top=2 下图3-2展示了顺序栈入栈、出栈中数据元素与栈顶指针的变化。 E D C C B B A A A (a) (b) (c) (d) (a)空栈;(b)插入元素A后;(c)插入元素B、C、D、E后;(d)删除元素E、D后 seqstack *S;
顺序栈运算实现 1.初始化: void init_stack(seqstack * S){S->top = -1;} 2. 判栈空: BOOL stack_empty(seqstack S){return (S.top == -1);} 3. 判栈满: BOOL stack_full(seqstack S){ if ( S.top == maxsize-1 ) return(TRUE); else return FALSE; } 4. 读栈顶元素:void stack_top(seqstack * S,elementtype &x){ if ( S -> top == -1 ) error(“栈空”); else x=S->data[s->top]; }
5.入栈操作 void push_stack(seqstack *S, elementtype x) //将元素x插入到栈S中,作为S的新栈顶 { if (S->top== maxsize -1) error("栈满"); else { S->top++; S->data[S->top]=x; } data top you 2 min 1 push_stack(S,’you’) xx 0
6.出栈操作 void pop_stack(seqstack *S,elementtype &x) {/*若栈s不为空,则删除栈顶元素*/ { if(s->top<0) error("栈空,不能删除"); else { x=S->data[S->top]; S->top- -; } data top min 2 you 1 xx 0 x=‘min’
双向栈在一维数组中的实现 自由区 0 Stack_Size -1 lefttop rightto 图3-3 两个栈共享邻接空间 栈的共享中最常见的是两栈的共享。假设两个栈共享一维数组S->elem[Stack_Size],则可以利用栈的“栈底位置不变,栈顶位置动态变化”的特性,两个栈底分别为0和Stack_Size-1,而它们的栈顶都往中间方向延伸。因此,只要整个数组S->elem[Stack_Size]未被占满,无论哪个栈的入栈都不会发生上溢。
3.1.3 链栈 栈也可以采用链式存储结构表示,这种结构的栈简称为链栈。 一个链栈(不带头结点)可由栈顶指针top唯一确定,当top为NULL时,是一个空栈。 链栈的C语言定义为: typedef struct StackNode { elementtype data; Struct StackNode *next; }StackNode,*LinkStack;
链栈示意图 p top C top B B top A ^ A A ^ ^ (a) (b) (c) 图3-6 链栈的存储结构图 (a)含有两个元素A、B的栈;(b)插入元素C后的栈;(c)删除元素C、B后的栈
栈底 …... top ^ data next top top 栈底 p x …... ^ q top 栈底 top …... ^ • 入栈过程 • 出栈过程
链栈入栈、出栈的算法实现 top p 1)入栈操作void Push (LinkStack &top, elementtype e){ /*将元素e压入链栈top中*/p=(LinkStack)malloc(sizeof(StackNode)); if(!p) error("OVERFLOW"); p->data =e; p->next=top; top=p; } e
p top b c d ^ 2)出栈操作void Pop(LinkStack &top,elementtype &e){ /*从链栈top中删除栈顶元素*/ if (top= =NULL) error(“空栈”); else{ p=top; top=top->next; e=p->data; free(p); } e=‘a’ a
练习 四个元素入栈的顺序是A,B,C,D.不可能的出栈顺序是: (a)ABCD (b)DCBA (c)BADC (d)ADCB (e)DACB
3.1.4 栈的应用举例 • 数制转换 • 2. 递归函数的栈的使用
1. 数制转换 将一个非负的十进制整数N转换为另一个等价的基为B的B进制数的问题,很容易通过"除B取余法"来解决。【例】将十进制数13转化为二进制数。 2 13 1 6 2 3 2 0 1 2 1 0 1
S->top 算法实现 2 13 1 6 2 void conversion(int N,int B){//假设N是非负的十进制整数,输出等值的B进制数init_stack(S); while(N){ //从右向左产生B进制的各位数字,并将其进栈push_stack( S,N%B); N=N/B; } while(!stack_empty( S)){ //栈非空时退栈输出pop_stack( S,e); cout<<e; } } 3 0 2 1 1 2 e= 1 0 1 1 0 1
2、递归函数的栈的使用 递归过程及其实现 递归:函数直接或间接的调用自身叫递归。 实现:建立递归工作栈 例题 void print(int w) { int i; if ( w!=0) { print(w-1); for(i=1;i<=w;++i) printf(“%3d,”,w); printf(“\n”); } } 调用print(3) 运行结果: 1, 2,2, 3,3,3,
w w 1 0 w print(0); 2 返回 w (4)输出:1 (4)输出:1 主程序 print(1); 3 (3) 输出:2, 2 (3) 输出:2, 2 print(2); w=3; (2) 输出:3, 3, 3 (2) 输出:3, 3, 3 print(w) (1) (1) S->top (3)w=1 (1 ) 3 (4)w=0 (2)w=2 (3)w=1 (2) 2 (1)w=3 (2)w=2 (1) 3 top (1)w=3 top (4) 0 top (3) 1 (3) 1 top (2)w=2 (2) 2 (2) 2 (1)w=3 (1) 3 (1) 3 (1)w=3 S->top S->top S->top 结束 递归调用执行情况如下: if ( w!=0) { print(w-1); for(i=1;i<=w;++i) printf(“%3d,”,w); printf(“\n”); }
Return 3.2 队 列 3.2.1 队列的定义和运算 3.2.2 链队列 3.2.3 顺序队列(循环队列)
3.2.1 队列的抽象数据类型定义 在日常生活中队列很常见,如,我们经常排队购物或购票. 队列在计算机系统中的应用也非常广泛。例如:操作系统中的作业排队。
1、队列的定义及特点 出队 a1 a2 a3…………………….an 入队 队列Q=(a1,a2,……,an) front rear • 定义:允许在线性表的一端插入,另一端进行删除操作的线性表称为队列.插入的一端为队尾,删除的一端为队头。 • ※队尾(rear)——允许插入的一端 • ※队头(front)——允许删除的一端 • 队列特点:先进先出(FIFO)
2 队列运算 (1)初始化:init_queue(Q); (2)判队空:queue_empty(Q); (3)取队头:queue_front(Q,x); (4)入队:enqueue(Q,x); (5)出队:outqueue(Q); (6)判队满:queue_full(Q);
3.2.2 链队列 1、存储结构定义: typedef struct { elementtype data; struct node * next; } node; typedef struct { node *front,*rear; } linkqueue ; Q->front x y z Q->rear linkqueue Q;
front ^ Q rear 2 .链队列运算实现(初始化) (1)初始化队列: void init_queue(linkqueue &Q) { Q.front = (node *)malloc(sizeof(node)); Q.front -> next = NULL; Q.rear = Q.front; }
2.链队列运算实现 (2)判队空: BOOL queue_empty( linkqueue Q) {return ( Q.front == Q.rear );} (3)取队头: void queue_front( linkqueue Q, elementtype &x) { if ( empty(Q) ) error(“队列空”); else x = Q.front -> next -> data; }
p (4)入队列: void En_queue(linkqueue *Q, elementtype e) {p= (node *)malloc(sizeof(node)); if(!p) exit(OVERFLOW); p->data = e; p->next=null; Q->rear->next =p; Q->rear =p;} e Q->front m n Q->rear
p x (5)出队列 void Out_queue(linkqueue *Q,elementtype &e) { if Q->front = = Q->rear error("队列为空"); //队列Q为空队列 p=Q->front->next; e=p->data; Q->front->next = p->next if(Q.rear ==p) Q.rear=Q.front; //若Q只有一个结点 free(p);} e=‘x’ Q.front z y Q.rear
3.2.3 顺序队列 Q.rear Q.front 1.顺序队列的存储结构定义: #define maxsize 100//最大队列长度 typedef struct{ elementtype data[maxsize]; int front,rear ; }seqqueue; Seqqueue Q; 5 4 3 2 1 0 Q.rear 5 4 3 2 1 0 k i g s Q.front Q.rear = 4; Q.front = 0 Q.rear= =Q.front= = 0 初始化队列为空
队列的顺序存储结构 5 5 5 5 Q.rear 4 4 4 4 Q.rear Q.rear Q.rear Q.rear Q.rear 3 3 3 3 Q.front Q.front Q.front 2 2 2 2 1 1 1 1 Q.front 0 0 0 0 教材上约定: rear指示队尾元素; front指示队头元素上一位置。 道理相同! Q.rear J6 J5 Q.front J4 J3 J3 J2 J2 Q.front=0 Q.rear=0 J1 J1 Q.front J4,J5,J6入队 J1,J2,J3出队 队空 J1,J1,J3入队 设两个指针Q.front,Q.rear,约定: rear指示队尾元素下一位置; front指示队头元素 初值Q.front=Q.rear=0 空队列条件:Q.front==Q.rear 入队列:Q.data[Q.rear++]=x; 出队列:x=Q.data[Q.front++];
2.顺序队列存储中的缺点: Q.rear Q.rear Q.front n 5 4 3 2 1 0 w 5 4 3 2 1 0 l j Q.front Q.rear = MAXSIZE 队列满 Q.rear = = Q.front 为空,但不能使用
解决缺点的方法: 1、采用平移元素的方法: Q.rear z y z x rear y Q.front 2、将整个队列作为循环队列处理: x • 入队列: front z Q.rear=(Q.rear +1)%MAXSIZE 4 3 2 1 0 z y y x x Q.front Q.front Q.rear m Q.rear
(2) 出队列: 4 3 2 1 0 Q.rear m Q.front Q.front=(Q.front + 1)% MAXSIZE
(3) 在Q.rear和Q.front可以循环指示时,如何判断队列的空和满: 令:Q.rear = = Q.front 为空 令:(Q.rear + 1)%MAXSIZE = = Q.front为满 c Q.rear b d Q.rear w v Q.front v Q.front q c Q.rear Q.front 队列为满 队列为空
3.循环队列的重要算法: (1)构造一个空队列Q void init_queue (seqqueue Q) { Q.front = Q.rear=0 }
(2)求队列Q的长度 int QueueLength (SqQueue Q) {return ((Q.rear-Q.front+MAXSIZE)%MAXSIZE); }
(3) 循环队列的入队列: void En_queque(seqqueque *Q, elementtype e) { if ((Q.rear +1)%MAXSIZE = = Q.front) error("队列满"); Q.rear= (Q.rear +1)% MAXSIZE; Q->data[Q.rear]=e; }
(4)循环队列出队列: void Out_queue (seqqueque *Q, elementtype &e) { if (Q->front = = Q->rear) error(“队空”); else {Q->front= (Q->front + 1 ) % MAXSIZE; e=Q->data[Q->front]; } }
0 3.2.4 队列的应用 例:打印杨辉三角 • 1 • 1 • 1 2 1 • 3 3 1 • 1 4 6 4 1 • 5 10 10 5 1 • 1 6 15 20 15 6 1 • 1 7 21 35 35 21 7 1
3. 队列的应用 • 银行业务模拟程序 • 舞伴问题—假设在周末舞会上,男士们和女士们进入舞厅时,各自排成一队。跳舞开始时,依次从男队和女队的队头上各出一人配成舞伴。若两队初始人数不相同,则较长的那一队中未配对者等待下一轮舞曲。现要求写一算法模拟上述舞伴配对问题。
4、图的深度优先遍历—栈的应用(见第7章图)4、图的深度优先遍历—栈的应用(见第7章图) 5、图的广度优先遍历—队列的应用(见第7章图)
Return 3.3 数组和广义表 3.3.1 数组的定义和运算 3.3.2 数组的顺序表示和实现 3.3.3 矩阵的压缩存储 3.3.4 广义表的定义 3.3.5 广义表的存储结构
3.3.1 数组的定义和运算 数组的定义 数组是大家都已经很熟悉的一种数据类型,几乎所有高级语言程序设计中都设定了数组类型。 1.一维数组 一维数组可以看成是一个线性表或一个向量(第2章已经介绍),它在计算机内是存放在一块连续的存储单元中,适合于随机查找。。 (a0,a1,a3,……,an-1)
2.二维数组 二维数组中的每一个元素最多可有两个直接前驱和两个直接后继(边界除外),故是一种典型的非线性结构。例如,设A是一个有m行n列的二维数组,则A可以表示为: 二维数组可以看成是这样一个定长的线性表, 它的每个数据元素也是一个定长的线性表。
二维数组可以看成是一个线性表 A=(a0,a1,……,an-1) 其中每个数据元素aj是一个列向量形式的线性表 A=(a0 , a1 , …… ,an-1)
或者,可以看成是一个线性表 A=(a0,a1,……,am-1) 其中每个数据元素aj是一个行向量形式的线性表 A =( a0 , a1 , . . . am-1 ai0 aij-1 aij )
3.n维数组 同理,n维数组可以看成是这样一个定长的线性表, 它的每个数据元素也是n-1维的数组。 n维数组最多可有n个直接前驱和n个直接后继,故n维数组是一种非线性结构。