980 likes | 1.25k Vues
第 3 章栈和队列. 栈和队列是操作受限的线性表。栈:后进先出;队列:先进先出。. 线性表 栈 队列 Insert(L, i , x) Insert(S, n+1 , x) Insert(Q, n+1 , x) 1≤i≤n+1 Delete(L, i ) Delete(S, n ) Delete(Q, 1 ) 1≤i≤n. 栈和队列是两种常用的数据类型. 3.1 栈. 3.2 栈的应用举例. 3.3 栈与递归实现. 3.4 队列. 3.1 栈.
E N D
栈和队列是操作受限的线性表。栈:后进先出;队列:先进先出。栈和队列是操作受限的线性表。栈:后进先出;队列:先进先出。 线性表 栈 队列 Insert(L,i, x) Insert(S, n+1, x) Insert(Q, n+1, x) 1≤i≤n+1 Delete(L, i) Delete(S, n) Delete(Q, 1) 1≤i≤n 栈和队列是两种常用的数据类型
3.1 栈 3.2 栈的应用举例 3.3 栈与递归实现 3.4 队列
3.1栈 3.1.1 栈的抽象数据类型定义 3.1.2 栈的表示与实现
3.1.1 栈的抽象数据类型定义 ADT Stack{ 数据对象: D={ ai | ai ∈ElemSet, i=1,2,...,n, n≥0 } 数据关系: R1={ <ai-1, ai >| ai-1, ai∈D, i=2,...,n } 约定an端为栈顶,a1 端为栈底。 基本操作: } ADT Stack
InitStack(&S) DestroyStack(&S) GetTop(S, &e) StackEmpty(S) Push(&S, e) Pop(&S, &e)
InitStack(&S)操作结果:构造一个空栈 S。 Return
DestroyStack(&S)初始条件:栈 S 已存在。操作结果:栈 S 被销毁。 Return
StackEmpty(S)初始条件:栈 S 已存在。 操作结果:若栈 S 为空栈,则返回 TRUE,否则 FALE。 Return
GetTop(S, &e)初始条件:栈 S 已存在且非空。操作结果:用 e 返回 S 的栈顶元素。 … … a1 a2 an Return
Push(&S, e)初始条件:栈 S 已存在。操作结果:插入元素 e 为新的栈顶元素。 … … a1 a2 an e Return
Pop(&S, &e)初始条件:栈 S 已存在且非空。操作结果:删除 S 的栈顶元素,并用 e 返回其值。 … … a1 a2 an-1 an Return
3.1.2 栈的表示与实现 1.顺序栈 2.链 栈
利用顺序存储方式实现的栈称为顺序栈。 //----- 顺序栈的类型描述----- #define STACK_INIT_SIZE 100; typedef int ElemType; typedef struct { ElemType*data; int top; int stacksize; }SqStack;
通常将数组的0下标作为栈底,这样空栈时栈顶指针top=-1;进栈时,栈顶指针加1,即top++;出栈时,栈顶指针减1,即top--。栈操作的示意图如图3-1所示。通常将数组的0下标作为栈底,这样空栈时栈顶指针top=-1;进栈时,栈顶指针加1,即top++;出栈时,栈顶指针减1,即top--。栈操作的示意图如图3-1所示。
int InitStack (SqStack &S){ // 构造一个最大空间为STACK_INIT_SIZE的空顺序栈 S S.data = newElemType[STACK_INIT_SIZE]; if (!S.data) exit (OVERFLOW); //存储分配失败 S.top = -1; S.stacksize = STACK_INIT_SIZE; return OK; }
int Push (SqStack &S, ElemType e) { //若栈不满,则将e插入栈顶 if (S.top >= S.stacksize-1) //栈满 return OVERFLOW; S.data[++S.top]= e; return OK; }
int Pop (SqStack &S, ElemType &e) { // 若栈不空,则删除S的栈顶元素, // 用 e 返回其值,并返回OK; // 否则返回ERROR if(S.top== -1)return ERROR; e = S.data[S.top--]; return OK; }
Elemtype GetTop (SqStack S) { //栈已存在且不空,返回栈顶元素 if(S.top== -1)return ERROR; return S.data[S.top]; }
int StackEmpty (SqStack S) { //判栈是否为空栈 if(S.top== -1)return OK; return ERROR; }
需要注意的是,对顺序栈,入栈时应首先判断栈是否满了,栈满的条件是S.top>=S.stacksize-1。栈满时,不能入栈,否则将出现空间溢出,引起错误,这种现象称为上溢。解决的办法是追加存储空间。出栈和读取栈顶元素时,应先判断栈是否为空,为空时不能操作,否则将产生错误,通常栈空作为控制转移的条件。需要注意的是,对顺序栈,入栈时应首先判断栈是否满了,栈满的条件是S.top>=S.stacksize-1。栈满时,不能入栈,否则将出现空间溢出,引起错误,这种现象称为上溢。解决的办法是追加存储空间。出栈和读取栈顶元素时,应先判断栈是否为空,为空时不能操作,否则将产生错误,通常栈空作为控制转移的条件。
链栈 用链式存储结构实现的栈称为链栈。通常链栈用单链表表示,因此其结点结构与单链表的结点结构相同。在此用LinkStack表示,即有: typedef struct node{ ElemTypedata; struct node *next; }StackNode,*LinkStack; LinkList top;
因为栈中的主要运算是在栈顶插入、删除,显然在链表的头部做栈顶是最方便的,而且没有必要象单链表那样为了运算方便附加一个头结点。如图3-2:因为栈中的主要运算是在栈顶插入、删除,显然在链表的头部做栈顶是最方便的,而且没有必要象单链表那样为了运算方便附加一个头结点。如图3-2:
void InitStack (LinkStack &top){ // 构造一个空顺序栈 ,栈顶指针top top=NULL; }
int Push (LinkStack &top, ElemType x) { S=new StackNode; if (!S)exit(OVERFLOW); S->data=x;S->next=top;top=s; return OK; }
int Pop (LinkStack &top, ElemType &x) { if(S.top== NULL)return ERROR; else{ x = top->data; p=top; top=top->next; delete p; return OK; } }
int StackEmpty (ListStack S) { //判栈是否为空栈 if(S.top== NULL)return OK; return ERROR; }
3.2栈的应用举例 3.2.1 数制转换 3.2.2 括号匹配的检验 3.2.3 表达式求值 3.2.4 命题公式求值
例如:(1348)10 = (2504)8,其运算过程如下:N N div 8 N mod 81348 168 4 168 21 0 21 2 5 2 0 2 计算顺序 输出顺序
void conversion () { InitStack(S); scanf ("%d",&N); while (N) { Push(S, N % 8); N = N/8; } while (!StackEmpty(S)) { Pop(S,e); printf ( "%d", e ); } } // conversion
3.2.2 括号匹配的检验 假设在表达式中 ([]())或[([ ][ ])] 等为正确的格式, [( ])或([( ))或 (( )]) 均为不正确的格式。 则 检验括号是否匹配的方法可用 “期待的急迫程度”这个概念来描述。
分析可能出现的不匹配的情况: 到来的右括弧非是所“期待”的; 例如:考虑下列括号序列: [ ( [ ] [ ] ) ] 1 2 3 4 5 6 7 8 • 到来的是“不速之客”; • 直到结束,也没有到来所“期待”的括弧;
算法的设计思想: 1)凡出现左括弧,则进栈; 2)凡出现右括弧,首先检查栈是否空 若栈空,则表明该“右括弧”多余 否则和栈顶元素比较, 若相匹配,则“左括弧出栈” 否则表明不匹配 3)表达式检验结束时, 若栈空,则表明表达式中匹配正确 否则表明“左括弧”有余
int matching(char exp[]) { int state = 1;i=0;InitStack(S); while (i<Length(exp) && state) { switch of exp[i] { case ‘(‘:{Push(S,exp[i]); i++; break;} case”)”: {e=GetTop(S); if(!StackEmpty(S)&&e=‘(‘ {Pop(S,e); i++;} else {state = 0;} break; } }} if (StackEmpty(S)&&state) return OK; else return ERROR; }
3.2.3 表达式求值 限于二元运算符的表达式定义: 表达式 ::= (操作数) + (运算符) + (操作数) 操作数 ::= 简单变量 | 表达式 简单变量 :: = 标识符 | 无符号整数
表达式的三种标识方法: 设 Exp = S1 +OP+ S2 则称OP+ S1 + S2为前缀表示法 S1 +OP+ S2为中缀表示法 S1 + S2 +OP为后缀表示法
例如: Exp = a b+(c d / e) f 前缀式: + a b c / d e f 中缀式: a b+c d / e f 后缀式: a b c d e / f + 结论: • 5)后缀式的运算规则为: • 运算符在式中出现的顺序恰为 • 表达式的运算顺序; • 每个运算符和在它之前出现且 • 紧靠它的两个操作数构成一个最小 • 表达式; • 4)前缀式的运算规则为: • 连续出现的两个操作数和在它们 • 之前且紧靠它们的运算符构成一 • 个最小表达式; • 3)中缀式丢失了括弧信息, • 致使运算的次序不确定; 1)操作数之间的相对次序不变; 2)运算符的相对次序不同;
如何从后缀式求值? 例如: a b c d e / f + 先找运算符, 再找操作数 ab d/e c-d/e (c-d/e)f
如何从原表达式求得后缀式? 分析 “原表达式” 和 “后缀式”中的运算符: 原表达式: a + b c d / e f 后缀式: a b c + d e / f 每个运算符的运算次序要由它之后的一个运算符来定,在后缀式中,优先数高的运算符领先于优先数低的运算符。
从原表达式求得后缀式的规律为: • 1) 设立暂存运算符的栈; • 2) 设表达式的结束符为“#”, • 予设运算符栈的栈底为“#” • 3) 若当前字符是操作数, • 则直接发送给后缀式;
从原表达式求得后缀式的规律为: • 4) 若当前运算符的优先数高于栈顶运算符,则进栈; • 否则,退出栈顶运算符发送给后缀式; 5) “(” 对它之前后的运算符起隔离作用,“)”可视为自相应左括弧开始的表达式的结束符。
void transform(char suffix[ ], char exp[ ] ) { InitStack(OPTR); Push(OPTR, #); while (*exp!=‘#’) { if (*exp>=‘0’&&*exp<=‘9’){…} else { … } } while(!StackEmpty(OPTR)) {Pop(OPTR,ch);suffix[i++]=ch;} } // CrtExptree
do {//不是运算符,则处理连续出现的数字 Suffix[i++]=*exp; exp++; } while(*exp>=‘0’&&*exp<=‘9’); Suffix[i++]=‘$’; //操作数之间的间隔符 Return
switch (*exp) { case(: Push(OPTR, *exp); break; case):Pop(OPTR, ch); while (ch!=( ) {Suffix[i++]=ch; Pop(OPTR,ch) } break; defult : while(precede(GetTop(OPTR),*exp)!=‘<‘) {Pop(OPTR, ch);Suffix[i++]=ch; } Push(OPTR, *exp); break; } // switch exp++; Return
求后缀式表示的表达式的值: int calcul_exp(char *Suffix ) {//返回后缀表达式的运算结果 InitStack(OPND); while (*Suffix! = # ){ d=0; if (*Suffix>=‘0’&&*Suffix<=‘9’) {…} else {…} } Pop(OPND,result); return result; }
While(*Suffix!=‘$’) {//不是运算符,则处理连续出现的数字 d=10*d+*Suffix-’0’; Suffix++; } Push(OPND,d); //操作数进栈 Suffix++; Return
Pop(OPND,b); Pop(OPND,a); switch (*Suffix) { casech== +: c=a+b; break; case ch== - : c=a-b; break; case ch== * : c=a*b; break; case ch== / : c=a/b; break; }// switch Push(OPND,c); Suffix++; Return
3.2.4 命题公式求值 利用计算机求命题公式真值的关键是:①给出命题变元的每一组赋值;②计算命题公式在每一组赋值下的真值,其计算类似于中缀表达式的求值。
设定两个全局变量:字符数组myopnd[10]和整型数组A[1024][11],分别存储命题公式中的命题变元和命题公式的真值表。全局变量n表示命题公式的变元数,m为其对应的赋值数。设定两个全局变量:字符数组myopnd[10]和整型数组A[1024][11],分别存储命题公式中的命题变元和命题公式的真值表。全局变量n表示命题公式的变元数,m为其对应的赋值数。 真值表中命题变元的取值具有如下规律:每列中0和1是交替出现的,且0和1连续出现的个数相同。n个命题变元的每组赋值的生成算法可基于这种思想。