1 栈和队列的基本操 作是线性表操作的子集, 是限定性(操作受限制) 的数据结构。 第三章栈和队列 数 据 结 构 之 栈 和 队 列 2 3. 1 栈 ? 定义:是限定仅在表尾进行插入或删除操作 的线性表。 (后进先出线性表 LIFO) ? 栈底指针 (base) :是线性表的基址; ? 栈顶指针 (top):指向线性表最后一个元素的后面。 ? 当 top=base 时,为空栈。 ? 基本操作: InitStack(&S), DestroyStack(&S), StackEmpty(S) , ClearStack(&S), GetTop(S ,&e), StackLength(S) , Push(&S, e): 完成在表尾插入一个元素 e. Pop(&S,&e): 完成在表尾删除一个元素。 2 数 据 结 构 之 栈 和 队 列 3 ?栈的表示和实现 ? 顺序栈:是利用一组地址连续的存储单元 依次存放自栈底到栈顶的数据元素;栈满 之后,可再追加栈空间即为动态栈。 ? 顺序栈的结构类型定义: typedef int SElemType; typedef struct{ SElemType *base; /* 栈底指针 */ SElemType *top; /* 栈顶指针 */ int stacksize; /* 栈空间大小 */ }SqStack ; 数 据 结 构 之 栈 和 队 列 4 ? 基本算法描述 ?建立能存放 50个栈元素的空栈 #define STACK_INIT_SIZE 50 #define STACKINCREMENT 10 Status InitStack_Sq(Stack &S){ S.base=(SET*)malloc(STACK_INIT_SIZE *sizeof(SET)); /*为栈分配空间 */ if(S.base==NULL) exit(OVERFLOW); /*存储分配失败 */ S.top=S.base; S.stacksize = STACK_INIT_SIZE; return OK; } 3 数 据 结 构 之 栈 和 队 列 5 ?出栈操作算法 void pop(Sqstack s, SElemType e) { if(s.top= = s.base) return ERROR; else{ s.top--; e= *s.top; } return OK; } 出栈操作 top A B Y top A B Ybase base 数 据 结 构 之 栈 和 队 列 6 ?压栈操作算法 void Push(SqStack s, SElemType e) if(s.top-s.base>= S.stacksize;) { S.base=(SET*)realloc(S,base,(S.stacksize+STACKINCREMEN T) *sizeof(SET)); /*为栈重新分配空间 */ if(! S.base) exit(OVERFLOW); S.top=S.base+S.stacksize; S.stacksize+=STACKINCREMENT; } *S.top=e; S.top++;} return OK; } top A B 压栈操作 top A B e base base 4 数 据 结 构 之 栈 和 队 列 7 ?栈的销毁 void DestroyStack_Sq(Stack &S){ if (S.base) free(S.base); S.base=NULL;S.top=NULL; S.stacksize=0; } ?栈的清除 void ClearStack_Sq(Stack &S){ S.top = S.base ; } 数 据 结 构 之 栈 和 队 列 8 ?判断栈是否为空栈 Status StackEmpty_Sq(Stack S){ if(S.top==S.base) return TRUE; else return FALSE; } ?获得栈的实际长度 int StackLength_Sq(Stack S){ return(abs(S.top-S.base)); } 5 数 据 结 构 之 栈 和 队 列 9 ? 多个栈共享邻接空间 两个栈共享一空间 : : : : : : top1 top2 1 m 中间可用空间 栈 1 栈 2 地址 Base1 Base 2 …… 数 据 结 构 之 栈 和 队 列 10 3. 3 栈与递归 ? 递归函数:一个直接调用自己或通 过一系列的调用语句间接地调用自 己的函数。 ? 调用函数前,系统需先完成三件事: ?将所有的实在参数、返回地址等信息传 递给被调用函数保存; ?为被调用函数的局部变量分配存储区; ?将控制转移到被调用函数的入口。 6 数 据 结 构 之 栈 和 队 列 11 ? 从被调用函数返回调用函数之前,系 统需先完成三件事: ?保存被调函数的计算结果; ?释放被调函数的数据区; ?依照被调函数保存的返回地址将控制转 移到调用函数。 ? 嵌套调用时,按照“后调用先返回” 的原则 数 据 结 构 之 栈 和 队 列 12 ? 栈的应用 ——递归和非递归 ? 函数之间的信息传递和控制转移必须通过 “栈”来实现:系统将整个程序运行时所 需的数据空间安排在一个栈中,每当调用 一个函数时,就为它在栈顶分配一个存储 区;每当从一个函数退出时,就释放它的 存储区,则当前正运行的函数的数据区必 在栈顶。 ( Page 56 ) 7 数 据 结 构 之 栈 和 队 列 13 ? 递归函数的运行过程类似于多个函数 的嵌套调用 ? 递归函数的运行的“层次” 数 据 结 构 之 栈 和 队 列 14 例 1.已知 f(m,n)函数 (m,n均为自然数 )的定义 如下: m+n+1 (m*n=0) f(m,n)= f(m-1,f(m,n-1)) (m*n≠0) ( 1)写出计算f(m,n)的递归算法。 ( 2)利用堆栈改写出非递归算法。 ( 3)根据非递归算法,画出计算f(2,1)时 值参、局部变量以及堆栈的变化情况。 8 数 据 结 构 之 栈 和 队 列 15 F函数的递归算法 int f(int m,int n){ if(m*n==0) return(m+n+1); else return( f (m-1, f(m,n-1) ) ); } 数 据 结 构 之 栈 和 队 列 16 F函数递归示意图 5 3 5 3 4 2 , 0 1,0 0,2 1 , 2 0 , 4 1, 1 0, 3 1 , 3 2, 1 2 3 4 5 int f(int m,int n){ if(m*n==0) return(m+n+1); else {x=f(m,n-1) ; y=f (m-1, x) ; return(y);} } 9 数 据 结 构 之 栈 和 队 列 17 F函数的非递归算法 int F(int m,int n){ if(m>=0 && n>=0){ InitStack(S); push(S, m , n); while(!StackEmpty(S) ){ pop(S, m , n); if (n==-1) n=result; if (m*n= =0) {result = m+n+1;} else {push(S,m-1,-1);push(S,m,n-1);} } return result; } } //typedef struct{int m,n;}SElemType; 数 据 结 构 之 栈 和 队 列 18 while(!StackEmpty(S) ) { pop(S, m , n); if(n==-1) n=result; if(m*n= =0) result = m+n+1; else { push(S,m-1,-1); push(S,m,n-1); } } 0, -1320第七次循环 0, -1 0, -1 0, -1 201第六次循环 1, 0 0, -1 0, -1 0, -1 311第五次循环 1, 1 0, -1 0, -1 321第四次循环 1, 2 0, -1 331第三次循环 1, -1302第二次循环 2, 0 1, -1 12第一次循环 2,1121,2条语句 SResultNM标号 10 数 据 结 构 之 栈 和 队 列 19 ? 例 2: Hanoi问题:设有三座塔,依次命名为 X、 Y、 Z,有 n个直径不相同的圆盘,由小到大依 次编号为 1, 2, 3, ….n,开始时,它们前部按 大小递减的顺序插在塔座 X上,现在要求把 n个 圆盘按大小递减的顺序放在塔座 Z上。其移动规 则如下: 1、每次只能移动一个圆盘; 2、圆盘可以放在任一个塔座上; 3、任何时刻不能将大圆盘压在小圆盘上; 1 2 3 1 2 3 XY Z X Y Z 数 据 结 构 之 栈 和 队 列 20 int count=0; void hanoi(int n,char x,char y, char z) { if( n == 1) move(x,1,z); else{ hanoi(n-1,x,z,y); /*递归调用 */ move(x,n,z); /*将 x上编号为 n的盘放到 z上 */ hanoi(n-1,y,x,z); } } void move(char x, int n, char z) { printf(“%d: move disk %d from %c to %c\n”,++count, n,x,z); return; } 11 数 据 结 构 之 栈 和 队 列 21 汉诺塔问题的非递归算法。 void Hanoi(int n,int x,int y,int z){ InitStack(S);push(S , n , x , y , z); while(!StackEmpty(S)){ pop(S,n,x,y,z); if ((y== -1)||(n ==1)) move(n,x,z); else{ push(S,n-1,y,x,z); push(S,n,x,-1,z); push(S,n-1,x,z,y);} } } void move(int n,int a,int b){ printf(“第 %d号盘子从 %d柱移到 %d柱 ”,n,a,b);} 数 据 结 构 之 栈 和 队 列 22 1. 4 队列 ? 定义:是一种先进先出的线性表,它只允 许在表的一端进行插入,而在另一端删除 元素。 ? 队头 (front)——是删除元素(出队操作)端; ? 队尾 (rear)——是插入元素(入队操作)端。 12 数 据 结 构 之 栈 和 队 列 23 ? 基本操作: ? InitQueue(&Q) ? DestroyQueue(&Q) ? ClearQueue(&Q) ? QueueEmpty(Q) ? QueueLength(Q) ? GetHead(Q,&e) ? EnQueue(&Q, e) ? DeQueue(&Q,&e) 数 据 结 构 之 栈 和 队 列 24 ? 链式队列 typedef int QElemType; /* 队列元素类型 */ typedef struct QNode{ QElemType data; struct QNode *next; } Qnode; /* 队列结点类型 */ typedef struct Queue{ Qnode *rear ; Qnode *front ; } LinkQueue; /* 链式队列类型 */ 13 数 据 结 构 之 栈 和 队 列 25 LinkQueue Q ; 队列: 出队端 入队端 空队列: 头 5 8 3 6 ^front rear Q. 头 ^front rear Q. 数 据 结 构 之 栈 和 队 列 26 ? 链式队列的基本操作 Status InitQueue(LinkQueue &Q){ p=(Qnode *)malloc(sizeof(Qnode)); if(!p) exit(OVERFLOW); p->next = NULL; Q.front =p; Q.rear =p; return OK; } 14 数 据 结 构 之 栈 和 队 列 27 void addq(LinkQueue &Q,QElemType e) { p=(Qnode *)malloc(sizeof(Qnode)); if(!p) return (OVERFLOW); p->data=e; p->next=NULL; Q.rear->next=p; Q.rear=p; return OK; } 队首 队尾 front rear …… ^ 队首 队尾 front rear …… ^ 数 据 结 构 之 栈 和 队 列 28 void deleteq(LinkQueue Q,QElemType &e) { /* 在 front 处删除一个元素,把值送入 e中 */ if(Q.front==Q.rear) return ERROR; p = Q.front->next; e=p->data; Q.front->next=p->next; if(Q.rear= =p) Q.rear=Q.front; free(p); return OK; } 队首 队尾 front rear …… ^ 队首 front …… 队尾 ^ rear 15 数 据 结 构 之 栈 和 队 列 29 ? 队列的顺序表示和实现 typedef Struct { QElemType *base ; int front ; int rear ; int qsize ; } SqQueue ; SqQueue Q; 空队状态: Q.front == Q.rear 满队状态: Q.rear == Q.qsize 数 据 结 构 之 栈 和 队 列 30 ? 循环队列 ? 定义 :设向量 Q(0:n-1)表示队列, Q(0)表示队 首, Q(n-1)表示队尾,而 Q(0)接在 Q(n-1)后面 形成一个环,顺时针方向旋转。 ? 为了测定队列是否满,将采用队列少用一个 元素空间,作为队列呈“满”状态的标志。 空队: Q.front = = Q.rear 满队: (Q.rear+1) % Q.qsize = = Q.front 16 数 据 结 构 之 栈 和 队 列 31 #define MAXQSIZE 100 typedef struct{ QElemType *base; /*初始化的动态分配 存储空间 */ int front; /*头指针,若队列不空, 指向队列头元素 */ int rear; /*尾指针,队列不空时指 向队列的下一个元素 */ } SqQueue; /*队列名称 */ 数 据 结 构 之 栈 和 队 列 32 入队列的程序段如下: int en_cycque(SqQueue &Q, QElemType e) { if (Q.rear+1)%MAXQSIZE=Q.front) return ERROR Q.base[Q.rear]=e; Q.rear=(Q.rear+1)%maxsize; return OK; } Q.front=3 Q.rear=2 J1 J2 J4 J3 01 2 3 队满,不能插入,此方法 少用一个元素空间。 17 数 据 结 构 之 栈 和 队 列 33 出队列的程序段如下: void DeQueue(SqQueue &Q, QElemType &e) { if(Q.front==Q.rear) return ERROR; e=Q.base[Q.front]; Q.front=(Q.front+1)%MAXSIZE; return OK; } Q.front=0 Q.rear=2 J1 J2 0 1 2 3 J4 J3 数 据 结 构 之 栈 和 队 列 34 ? 循环队列的基本操作: ……必须熟练掌握这些算法 …... ?InitQueue(&Q) ?DestroyQueue(&Q) ?ClearQueue(&Q) ?QueueEmpty(Q) ?QueueLength(Q) ?GetHead(Q,&e) ?EnQueue(&Q, e) ?DeQueue(&Q,&e) 18 数 据 结 构 之 栈 和 队 列 35 问题: 确定一个链式队列需要头、尾 两个指针,而确定链式循环队列只 需一个指针,问该指针应该指向循 环链队的何处? ? 算法练习:算法练习: 请编写循环链队的出、入队算法。请编写循环链队的出、入队算法。 数 据 结 构 之 栈 和 队 列 36 ?本章重点 ? 堆栈和队列的基本概念和基本操作 ? 分别用顺序结构和链式结构实现堆栈 及队列的基本操作 ? 堆栈及队列的应用及程序设计。 19 数 据 结 构 之 栈 和 队 列 37 单元测验 ? 线性表、栈、队列的基本概念、 基于两种存储表示的各类型定义; ? 各基本操作的算法描述; ? 线性表、栈、队列的应用。