1 / 57

第四章 栈和队列

第四章 栈和队列. 出栈. 进栈. a n. 栈顶. a 2. 栈底. a 1. 栈和队列也是线性表,只不过是 操作受限 的线性表。 但从数据类型角度看,他们是和线性表大不相同的两类 重要的抽象数据类型。广泛应用于各种软件系统中。. 4.1 栈及其基本运算. 4.1.1. 定义 栈 是限定在表尾进行插入或删除操作的线性表。 表尾端称 栈顶 ,表头端称 栈底 。如图。. 栈的修改是按后进 先出的原则进行的。. 4.1.2 栈的基本运算. 1. 创建一个空栈; 2. 判断栈是否为空栈; 3. 往栈中插入(或称推入)一个元素;

vevay
Download Presentation

第四章 栈和队列

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. 第四章 栈和队列 出栈 进栈 an 栈顶 ... a2 栈底 a1 栈和队列也是线性表,只不过是操作受限的线性表。 但从数据类型角度看,他们是和线性表大不相同的两类 重要的抽象数据类型。广泛应用于各种软件系统中。 4.1 栈及其基本运算 4.1.1. 定义 栈是限定在表尾进行插入或删除操作的线性表。表尾端称 栈顶,表头端称栈底。如图。 栈的修改是按后进 先出的原则进行的。

  2. 4.1.2栈的基本运算 • 1. 创建一个空栈; • 2. 判断栈是否为空栈; • 3. 往栈中插入(或称推入)一个元素; • 4. 从栈中删除(或称弹出)一个元素; • 5. 求栈顶元素的值。

  3. 4.2 栈的表示和实现 4.2.1顺序表示 typedef int ElemType; /* 定义栈元素的数据类型, 这里定义为整型 */ #define MAXNUM 100 /* 栈中能达到的最大容量, 这里设为100 */ struct SeqStack /* 顺序栈类型定义 */ { ElemType s[MAXNUM]; int t; }SeqStack, *PSeqStack;

  4. 算法4.1 创建空栈 PSeqStack createEmptyStack_seq( void ) { PSeqStack pastack; pastack = (PSeqStack)malloc(sizeof(SeqStack)); if (pastack==NULL) printf("Out space!! \n"); else pastack->t=-1; return pastack; }

  5. 算法4.2 判断栈是否为空栈 int isEmptyStack_seq( PSeqStack pastack ) { return ( pastack->t == -1 ); } 算法4.3 栈的推入 void push_seq( PSeqStack pastack, DataType x ) /* 在栈中压入一元素x */ { if( pastack->t >= MAXNUM - 1 ) printf( "overflow! \n" ); else { pastack->t = pastack->t + 1; pastack->s[pastack->t] = x; } }

  6. 算法4.4 栈的弹出 ElemType pop_seq( PSeqStack pastack ) /* 删除栈顶元素 */ { ElemType temp; if( isEmptyStack_seq( pastack ) ) printf( "Underflow!\n" ); else { temp = pastack.s[pastack->t]; pastack->t = pastack->t - 1; } return temp; } 算法4.5 读栈顶元素 DataType top_seq( PSeqStack pastack ) /* 当pastack所指的栈不为空栈时,求栈顶元素的值 */ { return pastack->s[pastack->t]; }

  7. 4.2.2链接表示 typedef int ElemType; /* 定义栈中元素类型为整型, 也可定义为其他类型 */ struct Node /* 单链表结点结构 */ { ElemType info; struct Node *link; }; struct LinkStack /* 链接栈类型定义 */ { struct Node *top; /* 指向栈顶结点 */ }LinkStack, *PLinkStack;

  8. 算法4.6 创建一个空栈 PLinkStack createEmptyStack_link( ) { PLinkStack plstack; plstack = (struct LinkStack *)malloc( sizeof(struct LinkStack)); if (plstack != NULL) plstack->top = NULL; else printf("Out of space! \n"); return plstack; }

  9. 算法4.7 判单链形式栈是否为空栈 int isEmptyStack_link( PLinkStack plstack ) { return (plstack->top == NULL); } 算法4.8 单链形式栈的推入 void push_link( PLinkStack plstack, DataType x ) /* 在栈中压入一元素x */ { struct Node * p; p = (struct Node *)malloc( sizeof( struct Node ) ); if ( p == NULL ) printf("Out of space!\n"); else { p->info = x; p->link = plstack->top; plstack->top = p; } }

  10. 算法4.9 单链形式栈的弹出 ElemType pop_link( PLinkStack plstack ) /* 在栈中删除栈顶元素 */ { struct Node * p; ElemType elem; if( isEmptyStack_link( plstack ) ) printf( "Empty stack pop.\n" ); else{ p = plstack->top; elem = p->info; plstack->top = plstack->top->link; free(p); } return elem; } 算法4.10 读单链形式栈的栈顶元素 DataType top_link( PLinkStack plstack ) /* 对非空栈求栈顶元素 */ { return plstack->top->info; }

  11. 4.3 栈的应用举例 4.3.1数制转换 void Conversion() { PSeqStack ps; int n; ps = createEmptyStack_seq(); /*InitStack(&s); */ scanf(“%d”, &n); while(n) { push_seq(ps, n%8); n /= 8; } while(!isEmptyStack_seq(ps)) { n= pop_seq(ps); printf(“%d”, n); } free(ps); } /* End of Conversion() */

  12. 4.3.2栈与递归 1。递归的概念及递归调用过程 用自身的简单情况来定义自己的方式,称为“递归定义”。 int factorial(int n) { if(n == 1) return 1; else return(n*factorial(n-1)); }; 2。简化的背包问题 一个背包可以放入的物品重量为s,现有n件物品,重量分别为w1,w2,…,wn,问能否从这些物品中选若干件放入背包中,使得放入的重量之和正好是s.

  13. 1 s = 0 0 s < 0 Knap(s,n)= 0 s > 0 , n < 1 knap(s,n-1) 或 knap(s-wn, n-1); 当s>0,n>=1

  14. int knap(int s, int n) { if( s==0) return 1; else if(s<0 || s>0 && n<1) return 0; else if(knap(s-w[n-1], n-1) == 1) { printf(“result:n=%d,w[%d]=%d\n”, n, n-1, w[n-1]); return 1; } else return (knap(s, n-1)); }

  15. 汉诺塔问题的递归算法 行号 void Hanoi(int n, char x, char y, char z) 1 { 2 if(n = = 1) 3 move(x, 1, z); 4 else{ 5 Hanoi(n-1, x, z, y); 6 move(x, n, z); 7 Hanoi(n-1, y, x, z); 8 } 9 } void move (char x, int n, char y) { printf(“Move disk %d from %c to %c”, n, x, y); }

  16. 函数调用的过程 调用前: (1)将所有的实参、返回地址传递给被调用函数保存 (2)为被调用函数的局部变量分配存储区 (3)将控制转移到被调用函数入口。 调用后: (1)保存被调用函数的计算结果。 (2)释放被调用函数的数据区 (3)依照被调用函数保存的返回地址将控制转移到调用函数

  17. 多个函数嵌套调用时,按照“后调用先返回”的原则进行。多个函数嵌套调用时,按照“后调用先返回”的原则进行。 int main() { int m,n; ... first(m,n); 1: … } int first(int s, int t) { int i; … second(i); 2: … } int second(int d) { int x,y; 3: … }

  18. 函数嵌套调用过程示例 second first main int main() { int m,n; ... { int i; … { int x,y; 3: … } 2: … } 1: … }

  19. 递归 层次 汉诺塔问题的递归调用过程 递归工作栈状态 (返址,盘号,x,y,z) 6,1,a,b,c 6,2,a,c,b 6,2,a,c,b 6,2,a,c,b 0,3,a,b,c 0,3,a,b,c 0,3,a,b,c 0,3,a,b,c a a a b b b c c c 塔与圆盘的状态 运行语句序号 1,2,4,5 1 2 1,2,4,5 3 1,2,3,9 2 6,7

  20. 递归 层次 递归工作栈状态 (返址,盘号,x,y,z) 8,1,c,a,b 6,2,a,c,b 6,2,a,c,b 8,2,b,a,c 0,3,a,b,c 0,3,a,b,c 0,3,a,b,c 0,3,a,b,c a a b b c c 塔与圆盘的状态 运行语句序号 1,2,3,9 3 2 8,9 1 6,7 2 1,2,4,5

  21. 递归 层次 递归工作栈状态 (返址,盘号,x,y,z) 6,1,b,c,a 8,1,a,b,c 8,2,b,a,c 6,2,b,a,c 8,2,b,a,c 8,2,b,a,c 0,3,a,b,c 0,3,a,b,c 0,3,a,b,c 0,3,a,b,c a a a a b b b b c c c c 塔与圆盘的状态 运行语句序号 1,2,3,9 3 2 6,7 3 1,2,3,9 2 8,9

  22. 递归 层次 递归工作栈状态 (返址,盘号,x,y,z) 0,3,a,b,c a b c 塔与圆盘的状态 运行语句序号 8,9 1 0 栈空

  23. 出队列 入队列 a1 a2 a3 … an 队尾 队头 3.4 队列 1。定义 是一种先进先出的线性表(FIFO),只允许在表的一端 进行插入,另一端进行删除。 队头 队尾

  24. 2。递归函数到非递归函数的转换 算法4.11 阶乘的递归计算 int fact( int n ) { if ( n == 0 ) return 1; else return ( n * fact( n – 1 ) ); }

  25. 算法4.12 阶乘的非递归计算 int nfact( int n ) { int res; PSeqStack st; /* 使用顺序存储结构实现的栈 */ st = createEmptyStack_seq( ); while (n>0) { push_seq(st,n); n = n – 1; } res = 1; while (! isEmptyStack_seq(st)) { res = res * top_seq(st); pop_seq(st); } return ( res ); }

  26. 3。简化的背包问题* 设有一个背包可以放入的物品重量为s,现有n件物品,重量分别为w1, w2, …,wn。问能否从这n件物品中选择若干件放入此背包,使得放入的重量之和正好为s。 (1)分析问题,得到数学模型 1 当 s = 0 0 当 s < 0 knap( s , n ) = 0 当 s > 0 且 n < 1 knap( s , n - 1 ) 或 knap( s - wn , n - 1 ) 当 s > 0 且 n ≥ 1

  27. (2)设计算法:递归算法 (3)程序设计: int knap(int s,int n) { if ( s == 0 ) return 1; else if ((s<0)||((s>0)&&(n<1))) return 0; else if ( knap(s - w[n-1],n - 1)==1 ) { printf("result: n=%d ,w[%d]=%d \n",n,n-1,w[n-1]); return 1; } else return ( knap(s,n - 1) ); }

  28. 递归函数到非递归函数的转换 首先我们设计一个栈st,栈中的每个结点包含以下四个字段:参数s, n,返回地址r和结果单元k。由于knap算法中有两处要递归调用knap算法,所以返回地址一共有三种情况: 第一,计算knap( s0 , n0 )完毕,返回到调用本函数的其它函数; 第二,计算knap( s – w[n-1] , n - 1 )完毕,返回到本调用函数中继续计算; 第三,计算knap( s , n - 1 )完毕,返回到本调用函数继续计算。 为区分三种返回,r分别用1,2,3表示,另外引入一个变量x作为进出栈的缓冲。

  29. 栈用顺序存储结构实现,栈中元素和变量的说明如下:栈用顺序存储结构实现,栈中元素和变量的说明如下: struct NodeBag /* 栈中元素的定义 */ { int s , n ; int r ; /* r的值为1,2,3 */ int k; }; typedef struct NodeBag DataType; PSeqStack st; struct NodeBag x;

  30. 转换的做法按以下规律进行,凡调用语句knap( s1 , n1 )均代换成: (1) x.s = s1 ; x.n = n1 ; x.r = 返回地址编号; (2) push_seq( st, x ); (3) goto 递归入口。 将调用返回统一处理成: (1) x = top_seq( st ); (2) pop_seq( st ); (3) 根据x.r的值,进行相应处理 x.r = 1 , 返回; x.r = 2 , 继续处理1; x.r = 3 , 继续处理2;

  31. 并将算法中的所有局部量都改用栈st中栈顶结点的相应字段,为了在算法中直接引入栈,并在书写上一致,算法开头增加将结点( s , n , 1 )推入栈st中的动作 算法4.14 背包问题的非递归算法 int nknap(int s,int n) { struct NodeBag x; PSeqStack st; st = createEmptyStack_seq( ); /* entry0: 初始调用入口 */ x.s = s; x.n = n; x.r = 1; push_seq(st,x);

  32. entry1: /* 递归调用入口 */ if (top_seq(st).s == 0) { st->s[st->t].k = TRUE; goto exit2; } else if (top_seq(st).s<0 || (top_seq(st).s>0 && top_seq(st).n<1)) { st->s[st->t].k = FALSE; goto exit2; } else { x.s = top_seq(st).s - w[top_seq(st).n-1]; x.n = top_seq(st).n - 1; x.r = 2; push_seq(st,x); goto entry1; }

  33. exit2: /* 返回处理 */ x = top_seq(st); pop_seq(st); switch (x.r) {case 1: return(x.k); case 2: goto L3; case 3: goto L4; } L3: /* 继续处理1 */ if (x.k == TRUE) { st->s[st->t].k = TRUE; printf("result n=%d , w=%d \n", top_seq(st).n,w[top_seq(st).n-1]); goto exit2; }

  34. else { x.s = top_seq(st).s; x.n = top_seq(st).n - 1; x.r = 3; push_seq(st,x); goto entry1; } L4: /* 继续处理2 */ st->s[st->t].k = x.k; goto exit2; }

  35. 4。迷宫问题 (a) 迷宫的图形表示 (b) 迷宫的二维数组表示

  36. 求解迷宫问题的简单方法是:从入口出发,沿某一方向进行探索,若能走通,则继续向前走;否则沿原路返回,换一方向再进行探索,直到所有可能的通路都探索到为止。求解迷宫问题的简单方法是:从入口出发,沿某一方向进行探索,若能走通,则继续向前走;否则沿原路返回,换一方向再进行探索,直到所有可能的通路都探索到为止。 为避免走回到已经进入的点(包括已在当前路径上的点和曾经在当前路径上的点),凡是进入过的点都应做上记号。 为了记录当前位置以及在该位置上所选的方向,算法中设置了一个栈,栈中每个元素包括三项,分别记录当前位置的行坐标、列坐标以及在该位置上所选的方向(即directon数组的下标值)

  37. 栈用顺序存储结构实现,栈中元素的说明如下: struct NodeMaze { int x,y,d; }; typedef struct NodeMaze DataType; 算法4.15 求迷宫中一条路径的算法 void mazePath(int *maze[],int *direction[],int x1,int y1,int x2,int y2) /* 迷宫maze[M][N]中求从入口maze[x1][y1]到出口maze[x2][y2]的一条路径 */ /* 其中 1<=x1,x2<=M-2 , 1<=y1,y2<=N-2 */

  38. { int i,j,k,kk; int g,h; PSeqStack st; DataType element; st = createEmptyStack_seq( ); maze[x1][y1] = 2; /* 从入口开始进入,作标记 */ element.x = x1; element.y = y1; element.d = -1; push_seq(st,element); /* 入口点进栈 */ while (not isEmptyStack_seq(st)) /* 走不通时,一步步回退 */ { element = top_seq(st); i = element.x; j = element.y; k = element.d + 1;

  39. pop_seq(st); while (k<=3) /* 依次试探每个方向 */ { g = i + direction[k][0]; h = j + direction[k][1]; if (g==x2 && h==y2 && maze[g][h]==0) /* 走到出口点 */ { printf("The path is:\n"); /* 打印路径上的每一点 */ for (kk=1;kk<=st->t;kk++) printf("the %d node is: %d %d \n",kk, st->s[kk].x,st->s[kk].y); printf("the %d node is: %d %d \n",kk,i,j); printf("the %d node is: %d %d \n",kk+1,g,h); return; }

  40. if (maze[g][h]==0) /* 走到没走过的点 */ { maze[g][h] = 2; /* 作标记 */ element.x = i; element.y = j; element.d = k; push_seq(st,element); /* 进栈 */ i = g; /* 下一点转换成当前点 */ j = h; k = -1; } k = k + 1; } } printf("The path has not been found.\n"); /* 栈退完,未找到路径 */ }

  41. 4.4队列的定义及基本运算 “队列”也是一种特殊的线性表,对于它所有的插入都在表的一端进行,所有的删除都在表的另一端进行。进行删除的这一端叫队列的“头”,进行插入的这一 端叫队列的“尾”。当队列中没有任何元素时,称为“空队列”。 队列的基本运算有以下五种: 1. 创建一个空队列或将队列置成空队列; 2. 判队列是否为空队列; 3. 往队列中插入一个元素; 4. 从队列中删除一个元素; 5. 求队列头部元素的值。

  42. 4.5队列的实现

  43. 1。队列的链式表示和实现 typedef struct _QNode { ElemType info; struct _QNode *link; }Node, *PNode; typedef struct { PNode f; PNode r; }LinkQueue, *PLinkQueue; 算法4.21 创建一个空队列 PLinkQueue createEmptyQueue_link( ) { PLinkQueue plqu; plqu = (struct LinkQueue *)malloc(sizeof(struct LinkQueue)); if (plqu!=NULL) { plqu->f = NULL; plqu->r = NULL; } else printf("Out space!! \n"); return plqu; }

  44. 算法4.23 链接表示队列的插入 void enQueue_link( PLinkQueue plqu, Datatype x ) { struct Node *p; p = (struct Node *)malloc( sizeof( struct Node ) ); if ( p == NULL ) printf("out of space!"); else{ p->info = x; p->link = NULL; if (plqu->f == NULL) { plqu->f = p; plqu->r = p; } else { plqu->r->link = p; plqu->r = p; } } }

  45. 算法4.22 判断链接表示队列是否为空队列 int isEmptyQueue_link( PLinkQueue plqu ) { return (plqu->f == NULL); } 算法4.24 链接表示队列的删除 void deQueue_link( PLinkQueue plqu ) { struct Node * p; if( plqu->f == NULL ) printf( "Empty queue.\n " ); else{ p = plqu->f; plqu->f = plqu->f->link; free(p); } } 算法4.25 读链接表示队列的头部结点值 Datatype frontQueue_link( PLinkQueue plqu ) /* 在非空队列中求队头元素 */ { return plqu->f->info; }

  46. 2。循环队列——队列的顺序表示和实现 Q.rear Q.rear Q.front Q.front (1)普通情况 7 J6 6 J5 5 4 3 J3 2 1 J2 Q.rear Q.front 0 J1 队列数组越界 队列空

  47. Q.rear Q.rear Q.rear Q.front Q.front Q.front J1 J2 J8 J3 J4 J7 J5 J6 J1 J2 J3 J4 J7 J5 J6 (2)循环队列 图1 队列满 图2 队列空 图3 队列满,判断Q.rear->next == Q.front

  48. struct SeqQueue /* 顺序队列类型定义 */ { ElemType q[MAXNUM]; int f, r; }; typedef struct SeqQueue *PSeqQueue; /* 顺序队列类型 的指针类型 */ 算法4.17 判队列是否为空队列 int isEmptyQueue_seq( PSeqQueue paqu ) { return (paqu->f == paqu->r); }

  49. 算法4.16 创建一个空队列 PSeqQueue createEmptyQueue_seq( void ) { PSeqQueue paqu; paqu = (struct SeqQueue *)malloc(sizeof(struct SeqQueue)); if (paqu==NULL) printf("Out space!! \n"); else { paqu->f = 0; paqu->r = 0; } return paqu; }

  50. 算法4.18 队列的插入 void enQueue_seq( PSeqQueue paqu, DataType x ) /* 在队列中插入一元素x */ { if( (paqu->r + 1) % MAXNUM == paqu->f ) printf( "Full queue.\n" ); else { paqu->q[paqu->r] = x; paqu->r = (paqu->r + 1) % MAXNUM; } }

More Related