1.63k likes | 1.88k Views
第三章 栈和队列. 栈 ( Stack) 栈的应用举例 栈与递归 队列 (Queue). 第三章 栈和队列. 从结点间的逻辑关系看, 栈 和 队列 也是 线性表 ,只不过是 操作受限的特殊的线性表 。 但从数据类型角度看,它们是和线性表 不相同的两种数据结构 。在计算机科学和程序设计中有广范的应用。. 3.1 栈 (Stack). 定义 限定 仅在表尾进行插入或删除操作 的线性表 允许进行插入和删除的一端是浮动端,通常被称为 栈顶 (top) ,并用一个“栈顶指针”指示;而另一端是固定端,通常被称为 栈底 (bottom) 。
E N D
第三章 栈和队列 栈(Stack) 栈的应用举例 栈与递归 队列(Queue)
第三章 栈和队列 从结点间的逻辑关系看, 栈和队列也是线性表,只不过是操作受限的特殊的线性表。 但从数据类型角度看,它们是和线性表不相同的两种数据结构。在计算机科学和程序设计中有广范的应用。
3.1 栈(Stack) • 定义 • 限定仅在表尾进行插入或删除操作的线性表 • 允许进行插入和删除的一端是浮动端,通常被称为栈顶(top),并用一个“栈顶指针”指示;而另一端是固定端,通常被称为栈底(bottom)。 • 没有元素时称空栈。 • 特性 • 后进先出(LIFO, Last In First Out)
栈的抽象数据类型定义及基本运算 • 创建一个空栈; • 判断栈是否为空栈; • 往栈中插入(或称推入PUSH)一个元素; • 从栈中删除(或称弹出POP)一个元素; • 求栈顶元素的值。 P45
栈的表示和实现 • 两种存储表示方法: • 顺序栈 • 以一组地址连续的存储单元依次存放自栈底到栈顶的数据元素,同时附设指针top指示栈顶元素在顺序栈中的位置。 • 链栈 • 用链式存储结构表示的栈。通常用一个无头结点的单链表表示。 • 由于栈的插入删除操作只能在一端进行,而对于单链表来说,在首端插入删除结点要比尾端相对地容易一些,所以,将单链表的首端作为栈顶端,即将单链表的头指针作为栈顶指针。
一、顺序栈 • 顺序栈的类型定义: #define STACK_INIT_SIZE 100 //存储空间初始分配量 #define STACKINCREMENT //存储空间分配增量 typedef struct //顺序栈类型定义 { SElemType *base; //栈底顶指针 SElemType *top; //栈顶指针 int stacksize; //当前可使用的最大容量 }SqStack;
顺序栈的基本操作 top base 空栈 1、栈的初始化 Status InItStack(SqStack &S) //构造一个空栈S { S.base=(SElemType *)malloc(STACK_INIT_SIZE*sizeof (SElemType)); if(!base) exit(OVERFLOW); S.top= S.base; S.stacksize=STACK_INIT_SIZE; return OK; }
顺序栈的基本操作 top b a base e=*(S.top-1) 2、取栈顶元素 Status GetTop(SqStack S, SElemType &e) //若栈不空,则用e返回S的栈顶元素,并返回OK;否则返回ERROR { if (S.top==S.base) return ERROR; e=*(S.top-1); return OK; }
顺序栈的基本操作 3、入栈 Status push(SqStack &S,SElemType e) //插入元素e为新的栈顶元素
b a base base base top top top top top top top a 进栈 空栈 b 进栈: *S.top++=e f e e e d d d c c c b b b a a a base base base e 进栈 f 进栈,扩充容量 f 进栈 a *S.top++=e
顺序栈的基本操作 3、入栈的实现 status push(SqStack &S,SElemType e) { //插入元素e为新的栈顶元素 if(S.top-S.base>=S.stacksize) //栈满,增加存储空间 { S.base=(SElemType *)realloc(S.base, (S.stacksize+STACKINCREMENT)*sizeof(SElemType)) If(!S.base) exit(OVERFLOW); //存储分配失败 S.top= S.base+S.stacksize; //重置栈顶指针 S.stacksize+=STACKINCREMENT; //修改栈的当前容量 } *S.top++=e; //将元素e插入栈顶指针top指示位置后,top加1 return OK; }
顺序栈的基本操作 3、出栈 status Pop(SqStack &S,SElemType &e) //若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK; 否则返回ERROR
c d c b b a a b top top top top top a base base base base base d 退栈 a c 退栈 b 退栈 e=* - - S.top a 退栈 空栈: S.top==S.base
顺序栈的基本操作 4、出栈的实现 status Pop(SqStack &S,SElemType &e) /*若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK; 否则返回ERROR */ { if (S.top==S.base) return ERROR; e=* - - S.top; return OK; }
注意: • 由于栈的插入和删除操作具有它的特殊性,所以用顺序存储结构表示的栈并不存在插入删除数据元素时需要移动的问题; • 但栈容量难以扩充的弱点仍旧没有摆脱。
二、链栈 top elem next 栈顶 栈底 • 用链式存储结构表示的栈。通常用一个无头结点的单链表表示。 • 由于栈的插入删除操作只能在一端进行,而对于单链表来说,在首端插入删除结点要比尾端相对地容易一些,所以,将单链表的首端作为栈顶端,即将单链表的头指针作为栈顶指针。
链栈的类型定义 top elem next 栈顶 栈底 type struct node { //链栈的结点结构 StackElem elem; //栈的数据元素类型 struct node *next; //指向后继结点的指针 }NODE; typedef struct stack{ NODE *top; }L_STACK;
链栈的基本操作 1、初始化栈S void InitStack(L_STACK &S) { S.top=NULL; }
链栈的基本操作 2、入栈 void Push(L_STACK &S, StackElem e) { p=(NODE *)malloc(sizeof(NODE)); if (!p) exit(OVERFLOW); else { p->elem = e; p->next=S.top; S.top=p; } }
链栈的基本操作 3、出栈 void Pop(L_STACK &S, StackElem &e) { if (StackEmpty(S)) exit(“Stack is empty”); else { e =S.top-> elem; p=S.top; S.top=p->next; free(p); } }
链栈的基本操作 4、获取栈顶元素内容 void GetTop(L_STACK S, Stackelem &e) { if (StackEmpty(S)) exit(“Stack is empty”); else e=S.top-> elem; }
链栈的基本操作 5、判断栈是否为空 int StackEmpty(L_STACK S) { if (S.top==NULL) return TRUE; else FALSE; }
思考题 编号为1,2,3,4的四辆列车,顺序开进一个栈式结构的站台,问开出车站的顺序有多少种可能?请把它们具体写出来. 4,3,2,1; 3,2,1,4; 3,4,2,1; 3,2,4,1; 2,1,3,4; 2,1,4,3; 2,3,4,1; 2,3,1,4; 2,4,3,1; 1,2,3,4; 1,2,4,3; 1,3,4,2; 1,3,2,4; 1,4,3,2
3.2 栈的应用举例 数制转换 括号匹配的检验 表达式求值
一、数制转换 十进制N和其它进制数的转换是计算机实现计算的基本问题,其解决方法很多。 使用展转相除法将一个十进制数值转换成二进制数值。 • 即用该十进制数值除以2,并保留其余数;重复此操作,直到该十进制数值为0为止。最后将所有的余数反向输出就是所对应的二进制数值。 • 即,N=(ndivd)*d+nmodd 其中: div为整除运算, mod为求余运算。
2 2 19 0 2 1 9 2 4 1 2 2 0 2 1 0 0 1 例如 (38)10 = (???)2 其运算过程如下: (38)10 = (100110)2 38 1 0 0 1 1 0
算法 void Decimal _ Binary_conversion ( ) { //对于输入的任意一个非负十进制整数,打印输出其相应的二进制数 InitStack(S); //初始化栈S scanf(“%d”,N); //输入十进制正整数 while (N) { Push(S, N%2); //余数入栈 N=N/2; //N整除以2,得到新的被除数 } while (!StackEmpty(S)) { //依次从栈中弹出每一个余数,并输出之 Pop(S, e); printf(“%d”, e); } }
二、括号匹配的检验 算术表达式中包含三种括号: • 圆括号“(”和“)”,方括号“[”和“]”和花括号“{”和“}”; • 且这三种括号可按任意的次序嵌套使用; • 如,...[...{...}...[...]...]...[...]...(...)..。 设计算法,用来检验在输入的算术表达式中所使用括号的合法性。 算术表达式中各种括号的使用规则为: • 出现左括号,必有相应的右括号与之匹配,并且每对括号之间可以嵌套,但不能出现交叉情况。
分析 可利用一个栈结构保存每个出现的左括号,当遇到右括号时,从栈中弹出左括号,检验匹配情况。在检验过程中,若遇到以下几种情况之一,就可以得出括号不匹配的结论。 • 当遇到某一个右括号时,栈已空,说明到目前为止,右括号多于左括号; • 从栈中弹出的左括号与当前检验的右括号类型不同,说明出现了括号交叉情况; • 算术表达式输入完毕,但栈中还有没有匹配的左括号,说明左括号多于右括号。
算法 typedef char SElemType; int Check( ) { char ch; InitStack(S); //初始化栈S while ((ch=getchar())!=’\n’) { //以字符序列的形式输入表达式 switch (ch) { case(ch==‘(’||ch==‘[’||ch==‘{’): //遇左括号入栈 Push(S,ch); break; //在遇到右括号时,分别检测匹配情况 case (ch== ‘)’): if (StackEmpty(S)) return FALSE; else { Pop(S,e); if (e!= ‘(’) return FALSE; } break;
case (ch== ‘]’): if (StackEmpty(S)) return FALSE; else { Pop(S,e); if (e!= ‘[’) return FALSE; } break; case (ch== ‘}’): if (StackEmpty(S)) return FALSE; else { Pop(S,e); if (e!= ‘{’) return FALSE; } break; default:break; } } if (StackEmpty(S)) return TRUE; else return FALSE; }
三、表达式求值 一个表达式由操作数(operand)、运算符 (operator) 和界限符(delimiter)组成。称为单词。 • 操作数——可为常数、变量标识符、常量标识符; • 算符: • 运算符:算术、关系、逻辑运算符; • 界限符:括号、表达式结束符等。 算术表达式有三种表示: • 中缀(infix)表示 <操作数> <操作符> <操作数>,如 A+B; • 前缀(prefix)表示(波兰式) <操作符> <操作数> <操作数>,如 +AB; • 后缀(postfix)表示(逆波兰式) <操作数> <操作数> <操作符>,如 AB+。
rst4 rst1 rst5 rst2 rst3 rst6 表达式的中缀表示 a+b*(c-d) -e*f/g • 表达式中相邻两个操作符的计算次序为: • 优先级高的先计算; • 优先级相同的自左向右计算; • 当使用括号时从最内层括号开始计算。
中缀算术表达式求值 • 使用两个栈: • 运算符栈OPTR (operator) • 存放处理表达式过程中的运算符。 • 开始时,在运算符栈中先在栈底压入一个表达式的结束符“#”。 • 操作数栈OPND(operand) • 存放处理表达式过程中的操作数。
中缀算术表达式求值算法基本思想 • 建立并初始化OPTR和OPND栈,在OPTR栈中压入表达式起始符“#”; • 从头扫描表达式,取字符入c,根据运算规则作以下处理: • 当c==“#”时,且OPTR栈顶的运算符也是“#”时,表达式处理结束,在OPND栈的栈顶得到运算结果,结束算法。 • 若c是操作数,进OPND栈,从中缀表达式取下一字符送入c; • 若c是操作符,则和OPTR中的栈顶运算符比较优先级: • 若优先级(c) > 优先级(OPTR),则c进OPTR栈,取下一字符送入c; • 若优先级(c)<优先级(OPTR),则从OPND栈退出a2和a1,从OPTR栈退出θ, 形成运算指令 (a1)θ(a2),结果进OPND栈。此时读出的运算符下次重新考虑(即不读入下一个符号); • 若优先级(c) ==优先级(OPTR) 且ch == “)”,则从OPTR栈退出栈顶的“(”,对消括号,然后从中缀表达式取下一字符送入c。
top top # base base OPND OPTR 中缀算术表达式5+(6-4/2)*3求值
top top # base base OPND OPTR 中缀算术表达式5+(6-4/2)*3求值 5
top top # base base OPND OPTR 中缀算术表达式5+(6-4/2)*3求值 + 5
top top # base base OPND OPTR 中缀算术表达式5+(6-4/2)*3求值 ( + 5
top top # base base OPND OPTR 中缀算术表达式5+(6-4/2)*3求值 ( 6 + 5
top top # base base OPND OPTR 中缀算术表达式5+(6-4/2)*3求值 - ( 6 + 5
top top # base base OPND OPTR 中缀算术表达式5+(6-4/2)*3求值 - 4 ( 6 + 5
top top # base base OPND OPTR 中缀算术表达式5+(6-4/2)*3求值 / - 4 ( 6 + 5
top top # base base OPND OPTR 中缀算术表达式5+(6-4/2)*3求值 / 2 - 4 ( 6 + 5
top top # base base OPND OPTR 中缀算术表达式5+(6-4/2)*3求值 2 - 4 ( 6 + 5
top top # base base OPND OPTR 中缀算术表达式5+(6-4/2)*3求值 - 2 ( 6 + 5
top top # base base OPND OPTR 中缀算术表达式5+(6-4/2)*3求值 2 ( 6 + 5
top top # base base OPND OPTR 中缀算术表达式5+(6-4/2)*3求值 ( 4 + 5
top top # base base OPND OPTR 中缀算术表达式5+(6-4/2)*3求值 4 + 5
top top # base base OPND OPTR 中缀算术表达式5+(6-4/2)*3求值 * 4 + 5