470 likes | 737 Views
第三章 栈与队列. 栈 ( Stack ) 队列 ( Queue ). 3.1 栈 ( Stack ). 只允许在一端插入和删除的顺序表 允许插入和删除 的一端称为 栈顶 ( top ) ,另一端称 为 栈底 ( bottom ) 特点 后进先出 ( LIFO ). 退栈. 进栈. 基本操作. Push Q Push A Pop Pop. Push R Push D Push M Pop Push Q. 栈的抽象数据类型 p45. ADT Stack{
E N D
第三章 栈与队列 • 栈 ( Stack ) • 队列 ( Queue )
3.1栈 ( Stack ) • 只允许在一端插入和删除的顺序表 • 允许插入和删除 的一端称为栈顶 (top),另一端称 为栈底(bottom) • 特点 后进先出 (LIFO) 退栈 进栈
基本操作 Push Q Push A Pop Pop
Push R Push D Push M Pop Push Q
栈的抽象数据类型 p45 ADT Stack{ D = {ai | ai ElemSet, i = 1,2,…n } R = {<ai-1, ai> | ai-1, ai D, i = 2,…n} 约定an端为栈顶, a1端为栈底 P: InitStack(&S) ClearStack(&S) StackEmpty(S) StackLength(S) GetTop(S, &e) Push(&S, e) Pop(&S, &e) StackTraverse(S, visit()); }ADT Stack
栈的表示和实现 栈是一种线性结构,有 顺序栈和链栈两种存储结构, 这两种存储结构的不同,则使得实现栈的基本运算的算法也有所不同。
如何存储栈? 进栈、出栈等操作如何实现? ? 3.1 栈 栈的顺序存储和实现 一、栈的顺序存储结构 1 栈的顺序存储结构 #define MAX 50 int stack[MAX]; int top=0;
an an-1 a2 a1 MAX-1 n n-1 n-2 1 0 top 当栈用顺序结构存储时, 栈的基本操作如建空栈、 进栈、出栈等操作如何实现? 顺序栈的图示 ? 约定栈顶指针指向 栈顶元素的下一个位置
x an an-1 a2 a1 MAX-1 n n-1 n-2 1 0 an an-1 a2 a1 MAX-1 n n-1 n-2 1 0 top top x 进栈后 x 进栈前 1 ) 进栈操作 viod Push( int x ) {if (top>=MAX) printf(“ overflow” ); else {stack[top]=x; top++; } }
an an-1 a2 a1 an an-1 a2 a1 MAX-1 n n-1 n-2 1 0 MAX-1 n n-1 n-2 1 0 top top 出栈操作前 出栈操作后 2)出栈操作 int pop( ){if (top==0) {printf(“underflow”); return(-1);} top--; return(0); }
出栈 进栈 an 栈顶 a3 a2 栈底 a1 栈的表示和实现 顺序栈 P46-47 • 在连续的存储单元内依次存放栈中的数据元素,并设指针top来指示栈顶元素的位置 • top为0时表示空栈 #define STACK_INIT_SIZE 100; #define STACKINCREMENT 10; typedef struct{ SElemType *base; //栈底指针 SElemType *top; //栈顶指针 int stacksize; //栈的当前最大可用容量 }SqStack; SqStack S;
顺序栈的实现 Status InitStack(SqStack &S){ S.base = (SElemType *) malloc(STACK_INIT_SIZE * sizeof(ElemType)); if (!S.base) exit(OVERFLOW); S.top = S.base; S.stacksize = STACK_INIT_SIZE; return OK; } Status GetTop(SqStack S, SElemType &e){ if (S.top == S.base) return ERROR; e = *(S.top –1); return OK; }
顺序栈的实现 Status Push(SqStack &S, SElemType e){ if (S.top – S.base >= S.stacksize){ //栈满,追加空间 S.base = (ElemType *)realloc(S.base, (S.stacksize + STACKINCREMENT) * sizeof(ElemType)); if (!S.base) exit(OVERFLOW); S.top = S.base + S.stacksize; S.stacksize += STACKINCREMENT; } *S.top ++ = e; //进栈 return OK; }
Status Pop(SqStack &S, SElemType &e){ if (S.top == S.base) return ERROR; e = * -- S.top; //出栈 return OK; }
顺序栈的实现 void ClearStack(&S) { S.top = S.base; } int StackLength(S) { return (S.top –S.base); } int StackEmpty(S) { if (S.top == S.base) return TRUE else return FALSE; }
栈的链接表示 — 链式栈 • 链式栈无栈满问题,空间可扩充 • 插入与删除仅在栈顶处执行 • 链式栈的栈顶在链头 • 适合于多栈操作
data next S 栈顶 栈底 栈的表示和实现 typedef struct{ SElemType data; struct StackNode *next; }StackNode, *StackPtr; StackPtr S;
链栈的实现 Status InitStack(StackPtr &S){ S = (StackPtr)malloc(sizeof(StackNode)); if (!S) exit(OVERFLOW); S.next = NULL; return OK; } Status GetTop(StackPtr S, SElemType &e){ if (!S) return ERROR; e = S->data; return OK; }
p e S 链栈的实现 Status Push(StackPtr &S, SElemType e){ p = (StackPtr)malloc(sizeof(StackNode)); p->data = e; p->next = S; S = p; return OK; } Status Pop(StackPtr &S, SElemType &e){ if (!S) return ERROR; //空栈 p = S; e = S->data; S = S->next; free(p); return OK; } S
在顺序栈中有“上溢”和“下溢”的概念。顺序栈好比一个盒子,我们在里头放了一叠书,当我们要用书的话只能从第一本开始拿,那么当我们把书本放到这个栈中超过盒子的顶部时就放不下了,这时就是"上溢","上溢"也就是栈顶指针指出栈的外面,显然是出错了。反之,当栈中已没有书时,我们再去拿,看看没书,把盒子拎起来看看盒底,还是没有,这就是"下溢"。"下溢"本身可以表示栈为空栈,因此可以用它来作为控制转移的条件。在顺序栈中有“上溢”和“下溢”的概念。顺序栈好比一个盒子,我们在里头放了一叠书,当我们要用书的话只能从第一本开始拿,那么当我们把书本放到这个栈中超过盒子的顶部时就放不下了,这时就是"上溢","上溢"也就是栈顶指针指出栈的外面,显然是出错了。反之,当栈中已没有书时,我们再去拿,看看没书,把盒子拎起来看看盒底,还是没有,这就是"下溢"。"下溢"本身可以表示栈为空栈,因此可以用它来作为控制转移的条件。 • 链栈则没有上溢的限制,它就象是一条一头固定的链子,可以在活动的一头自由地增加链环(结点)而不会溢出,
3.2栈的应用举例 • 数制转换 • 括号匹配的检验[( [ ] [ ] )] • 行编辑程序p49
3.2.1 数制转换 十进制N和其它进制数的转换是计算机实现计算的基本问题,其解决方法很多,其中一个简单算法基于下列原理: N=(n div d)*d+n mod d ( 其中:div为整除运算,mod为求余运算) 例如 (1348)10=(2504)8,其运算过程如下:
n n div 8 n mod 8 1348 168 4 168 21 0 21 2 5 2 0 2
void conversion( ) { initstack(s); scanf (“%”,n); while(n){ push(s,n%8); n=n/8; } while(! Stackempty(s)){ pop(s,e); printf(“%d”,e); } }
3.2.4 迷宫求解 入口 出口
迷宫问题 示例 4 3 5 2 1 7 6
小型迷宫 迷宫问题 4 3 5 2 1 7 6 路口 动作结果 1 (入口) 正向走 进到 2 2左拐弯 进到 3 3右拐弯 进到 4 4 (堵死) 回溯 退到 3 3 (堵死) 回溯 退到 2 2正向走 进到 5 5 (堵死) 回溯 退到 2 2右拐弯 进到 6 6左拐弯 进到7 (出口) 6 左 0 直 2右 0 行 3 5 6 0 0 4 0 0 0 0 0 0 7 0 0 7 小型迷宫的数据
设计算法判断一个算术表达式的圆括号是否正确配对设计算法判断一个算术表达式的圆括号是否正确配对 • 提示: 对表达式进行扫描,凡遇到‘(’就进栈,遇‘)’就退掉栈顶的‘(’,表达式被扫描完毕,栈应为空。 • 根据提示,可以设计算法如下:#include <string.h>#include “stack.h”int PairBracket( char *S){//检查表达式中括号是否配对int i;SeqStack openings; //定义一个栈InitStack (&T);for (i=0; i<strlen(S) ; i++){ if ( S[i]==‘(’ ) Push(& openings, S[i]); //遇‘(’时进栈if ( S[i]==‘)’ ) Pop(& openings); //遇‘)’时出栈 }return !EmptyStack(&T); // 由栈空否返回正确配对与否}
int main( ) {Seq_Stack openings; InitStack (&openings); char symbol; bool is matched = true; while (is matched && (symbol = cin.get( )) != ‘\n’) { if (symbol == ‘{‘ || symbol ==‘(‘ || symbol == ‘[‘) Push(&openings, symbol); if (symbol == ‘}’ || symbol == ‘)’ || symbol == ‘]’) { if (EmptyStack(&openings )) { cout << "Unmatched closing bracket " << symbol << " detected." << endl; is matched = false; } else ……
else { char match; Get_top(openings, match); Pop(openings); is matched = (symbol == ‘}’ && match ==‘{‘) || (symbol == ‘)’ && match == ‘(‘) || (symbol == ‘]’ && match == ‘[‘); if (!is matched) cout << "Bad match " << match << symbol << endl; } } } if (!openings.empty( )) cout << "Unmatched opening bracket(s) detected." << endl; }
递归的概念 • 递归的定义 若一个对象部分地包含它自己, 或用它自己给自己定义, 则称这个对象是递归的;若一个过程直接地或间接地调用自己, 则称这个过程是递归的过程。 • 在以下三种情况下,常常用到递归方法。 • 定义是递归的 • 数据结构是递归的 • 问题的解法是递归的
定义是递归的 例如,阶乘函数 求解阶乘函数的递归算法 long Factorial (long n ) { if ( n == 0 )return 1; else return n*Factorial (n-1); }
计算斐波那契数列的函数Fib(n)的定义 求解斐波那契数列的递归算法 longFib (longn ) { if ( n <= 1 )return n; else returnFib (n-1) + Fib (n-2); }
数据结构是递归的 例如,单链表结构
Towers of Hanoi Rules: Move only one disk at a time. No larger disk can be on top of a smaller disk.
问题的解法是递归的 汉诺塔(Tower of Hanoi)问题 (64) 规则:
Program for HanoiMain program: const int disks = 64; // Make this constant much smaller to run program. void move(int count, int start, int nish, int temp); main( ) { move(disks, 1, 3, 2); }
递归函数 void move(int count, int start, int finish, int temp) { if (count > 0) { move(count - 1, start, temp, finish); cout << "Move disk " << count << " from " << start<< " to " << nish << "." << endl; move(count - 1, temp, finish, start); } }
Ackerman 函数定义如下:请写出递归算法。 n+1 当m=0时 AKM ( m , n ) = AKM( m-1 ,1) 当m≠0 ,n=0时 AKM( m-1, AKM( m,n-1)) 当m≠0, n ≠ 0时 解:算法如下 int AKM( int m, int n){ if ( m== 0) return n+1; if ( m<>0 && n==0 ) return AKM( m-1, 1); if ( m<>0 && n<>0 ) return AKM( m-1, AKM( m, n-1));}
习题 1、设将整数1、2、3、4依次进栈,但只要出栈时栈非空,则可将出栈操作按任何次序夹入其中,请回答下有问题: (1)若入栈次序为push(1),pop(),push(2,push(3),pop(),pop( ),push(4),pop( ),则出栈的数字序列为什么? (2)能否得到出栈序列为1423和1432?并说明为什么不能得到或如何得到。 (3)请分析1、2、3、4的24种排列中,哪些序列可以通过相应的入出栈得到。