收藏 分享(赏)

DS03_栈与队列.ppt

上传人:scg750829 文档编号:9434848 上传时间:2019-08-07 格式:PPT 页数:92 大小:3.10MB
下载 相关 举报
DS03_栈与队列.ppt_第1页
第1页 / 共92页
DS03_栈与队列.ppt_第2页
第2页 / 共92页
DS03_栈与队列.ppt_第3页
第3页 / 共92页
DS03_栈与队列.ppt_第4页
第4页 / 共92页
DS03_栈与队列.ppt_第5页
第5页 / 共92页
点击查看更多>>
资源描述

1、王亚沙 2010.92011.1基于 张铭,王腾蛟,赵海燕三位老师的胶片修改,第三章 栈与队列,目录,3.1 栈、队列与线性表 3.2 栈 3.3 队列 3.4 深入探讨,操作受限的线性表,栈(Stack) 运算只在表的一端进行 队列(Queue) 运算只在表的两端进行,目录,3.1 栈、队列与线性表 3.2 栈 3.3 队列 3.4 深入探讨,3.2.1 栈的概念,后进先出(LastInFirstOut) 一种限制访问端口的线性表 栈存储和删除元素的顺序与元素到达的顺序相反 也称为“下推表” 栈的主要元素 栈顶(top)元素:栈的唯一可访问元素 元素插入栈称为“入栈”或“压栈”(push)

2、删除元素称为“出栈”或“弹出”(pop) 栈底:另一端,思考,若入栈的顺序为1,2,3,4的话,则出栈的顺序可以有哪些? 1234 1243 1324 1342 1423 1432 2134 2143 ,栈的示意图,每次取出(并被删除)的总是刚压进的元素,而最先压入的元素则被放在栈的底部 当栈中没有元素时称为“空栈”,栈的主要操作,入栈( push ) 出栈(pop):取出并移除 取栈顶元素( top ):取出不移除 判断栈是否为空( isEmpty ),3.2.2 栈的抽象数据类型,template / 栈的元素类型为 T class Stack public: / 栈的运算集void cl

3、ear(); / 变为空栈bool push(const T item); / item入栈,成功则返回真,否则返回假bool pop(T ,3.2.3 栈的实现,顺序栈(Array-based Stack) 使用顺序表(向量)实现,是顺序表的简化版 能够确定栈大小的情况适用(顺序表的特点) 应该选择顺序表的哪一端作为栈顶? 链式栈(Linked Stack) 用单链表方式存储,其中指针的方向是从栈顶向下链接,1)顺序栈,template class arrStack : public Stack private: / 栈的顺序存储int mSize; / 栈中最多可存放的元素个数int to

4、p; / 栈顶位置,应小于mSizeT *st; / 存放栈元素的数组 public: / 栈的运算的顺序实现arrStack(int size) / 创建一个给定长度的顺序栈实例mSize = size; top = -1;st = new TmSize;arrStack() / 析构函数delete st;void clear() / 清空栈内容top = -1;bool push(const T item); / item入栈,成功则返回真,否则返回假bool pop(T ,顺序栈示意,按压入先后次序,最后压入的元素编号为4,然后依次为3,2,1,顺序栈示意,顺序栈的溢出,上溢(Over

5、flow) 当栈中已经有maxsize个元素时,如果再Push,所产生的现象 下溢(Underflow) 对空栈进行Pop,所产生的现象,push,bool arrStack:push(const T item) if (top = mSize-1) / 栈已满 cout “栈满溢出“ endl;return false;else / 新元素入栈并修改栈顶指针st+top = item;return true; ,pop,bool arrStack:pop(T ,top,bool arrStack: top(T ,其他算法,把栈清空 void arrStack:clear() top = -1

6、; 栈满时,返回非零值(真值true) bool arrStack: isFull() return (top = maxsize-1) ; ,2)链式栈,用单链表方式存储 指针的方向从栈顶向下链接,ki+2,ki+1,ki,k0,top,链式栈的创建,template class lnkStack : public Stack private: / 栈的链式存储Link* top; / 指向栈顶的指针int size; / 存放元素的个数 public: / 栈运算的链式实现lnkStack(int defSize) / 构造函数top = NULL;size = 0;lnkStack()

7、/ 析构函数clear();. ,push,/ 入栈操作的链式实现 bool lnksStack: push(const T item) Link* tmp = new Link(item, top);top = tmp;size+;return true; ,pop,/ 出栈操作的链式实现 bool lnkStack: pop(T ,3.2.4 栈的应用,栈的特点:后进先出 常用来处理具有递归结构的数据 网页访问历史 编辑器的Undo序列 深度优先搜索 表达式求值 子程序函数调用的管理 消除递归,1)表达式求值,内容提要 中缀表达式 23+(34*45)/(5+6+7) 后缀表达式 23 3

8、4 45 * 5 6 + 7 + / + 中缀表达式到后缀表达式的转换 后缀表达式求值, = + | | = * | / | = | ( )= | = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9,先执行括号内的计算,后执行括号外的计算。在具有多层括号时,按层次反复地脱括号,左右括号必须配对 在无括号或同层括号时,先乘(*) 、除(),后加(+)、减(-) 在同一个层次,若有多个乘除(*、)或加减 (+,-)的运算,那就按自左至右顺序执行,23+(34*45)/(5+6+7) = ?, = + | - | = * | / | = = | = 0 | 1 | 2 |

9、 3 | 4 | 5 | 6 | 7 | 8 | 9,23 34 45 * 5 6 + 7 + / + =?,中缀和后缀表达式的主要异同? 23+34*45/(5+6+7) = ?23 34 45 * 5 6 + 7 + / + =?,从左到右扫描中缀表达式。用栈存放表达式中的操作数、开括号以及在此开括号后暂不确定计算次序的其他符号: (1) 当输入的是操作数时,直接输出到后缀表达式序列; (2) 当遇到开括号时,把它压入栈; (3) 当输入遇到闭括号时,先判断栈是否为空,若为空(括号不匹配),应该作为错误异常处理,清栈退出。 若非空,则把栈中元素依次弹出,直到遇到第一个开括号为止,将弹出的元

10、素输出到后缀表达式序列中(弹出的开括号不放到序列中),若没有遇到开括号,说明括号不配对,做异常处理,清栈退出。,从左到右扫描中缀表达式。用栈存放表达式中的操作数、开括号以及在此开括号后暂不确定计算次序的其他符号: (4)当输入为运算符op( 四则运算 + - * / 之一)时 (a)循环 当(栈非空 and 栈顶不是开括号 and 栈顶运算符的优先级不低于输入的运算符的优先级)时,反复操作将栈顶元素弹出,放到后缀表达式序列中; (b)将输入的运算符压入栈内; (5)最后,当中缀表达式的符号全部读入时,若栈内仍有元素,把它们全部依次弹出,放在后缀表达式序列的尾部。若弹出的元素遇到开括号,则说明括

11、号不匹配,做异常处理,清栈退出。,待处理中缀表达式:,输出后缀表达式:,栈的状态,23,/,45,5,6,7,*,(,),(,),34,循环:依次从左至右顺序读入表达式的符号序列(假设以作为输入序列的结束),并根据读入的元素符号逐一分析: 1.当遇到的是一个操作数,则压入栈顶; 2.当遇到的是一个运算符, 就从栈中两次取出栈顶,按照运算符对这两个操作数进行计算。然后将计算结果压入栈顶 如此继续,直到遇到符号, 这时栈顶的值就是输入表达式的值,待处理后缀表达式:,23,/,45,5,6,7,*,34,栈状态的变化,1530,11,18,85,108,/ Class Declaration 类的说

12、明 (参见算法3.5) class Calculator private:Stack s; / 这个栈用于压入保存操作数/ 从栈顶弹出两个操作数opd1和opd2bool GetTwoOperands(double,具体的算法见教材P56P8,请大家课后仔细阅读!,2)递归,内容提要 函数的递归定义 主程序和子程序的参数传递 栈在实现函数递归调用中所发挥的作用,递归是指:函数/过程/子程序在运行过程中直接或间接调用自身而产生的重入现象 作为数学和计算机科学的基本概念,递归是解决复杂问题的一个有力手段 符合人类解决问题的思维方式,递归能够描述和解决很多问题,为此许多程序设计语言都提供了递归的机制

13、 而计算机则只能按照指令集来顺序地执行。 计算机内(编译程序)是如何将程序设计中的便于人类理解的递归程序转换为计算机可处理的非递归程序的?,整数n的阶乘(n!)的递归定义如下:,递归定义由两部分组成: 递归基础/出口 递归规则,/ 递归定义的计算阶乘n!的函数 long factorial(long n) if (n = 0)return 1;elsereturn n * factorial( n-1) ; / 递归调用 ,回推: 递推:4!= 4*3! 4!= 4*3! = 243!= 3*2! 3!= 3*2!= 62!= 2*1! 2!= 2*1!= 21!= 1*0! 1!=1*0!=

14、 10!= 1,已知,栈的一个最为广泛的应用:函数调用机制 运行时环境指的是目标计算机上用来管理存储器并保存指导执行过程所需的信息的寄存器及存储器的结构。 函数调用 被调函数的参数、返回地址、局部变量在被调用时将在运行栈(runtime stack,or execution stack, control stack, function stack, call stack)中分配内存,例如:DrawSquare(Point p, int Width, int Hight )int color=0;Point p2;p2.x=p1.x+Width;Drawline(Point p, Point p

15、2, int color);. Drawline(Point p, Point p2, int color)int i;. ,函数活动记录(activation record),用作动态数据分配的存储区可按多种方式组织。典型的组织是将这个存储器分为栈(stack)区域和堆(heap)区域 栈区域用于分配发生在后进先出LIFO风格中的数据(诸如函数的调用) 堆区域则用于不符合LIFO(诸如指针的分配)的动态分配,运行栈随着程序执行时发生的调用链或生长或缩小一个函数在运行栈上可以有若干不同的活动记录,每个都代表了一个不同的调用 对于递归函数来说,递归的深度就决定了其在运行栈中活动记录的数目。 当函

16、数递归调用时,函数体的同一个局部变量,在不同的递归层次被分配给不同的存储空间,放在内部栈的不同位置,函数的调用实现方式: 将参数、返回地址和局部变量压栈 将控制转向新的函数地址 函数返回的实现方式: 返回值 退栈 将控制转向栈中的返回地址,以阶乘为例: #include main() int x;scanf(“%d”, / 递归调用 ,计算阶乘的运行栈,第1次调用factorial时的活动记录,主程序main的活动记录,栈生长方向,第2次调用factorial时的活动记录,主程序main的活动记录,栈生长方向,第1次调用factorial时的活动记录,函数调用与返回,第2次调用factoria

17、l时的活动记录,主程序main的活动记录,栈生长方向,第1次调用factorial时的活动记录,第3次调用factorial时的活动记录,第4次调用factorial时的活动记录,第5次调用factorial时的活动记录,第1次返回时的 栈顶指针,第2次返回时的 栈顶指针,第3次返回时的 栈顶指针,第4次返回时的 栈顶指针,第5次返回时的 栈顶指针,以阶乘为例,非递归方式方法一: / 使用循环迭代方法,计算阶乘n!的程序 long factorial(long n) int m = 1;int i ;if (n0)for ( i = 1; i = n; i+ )m = m * i ;retur

18、n m ; ,以阶乘为例,非递归方式方法二:long factorial(long n) Stack s;int m = 1;while (n0) s.push(n-);while (!isEmpty(s) m *= s.pop(s);return m ; ,是否每一种递归到非递归的转换都这么简单?,稍微复杂一点的例子: 简化的背包问题 设有一个背包可以放入的物品重量为s,现有n件物品,重量分别为w1, w2, ,wn。 问能否从这n件物品中选择若干件放入此背包,使得放入的重量之和正好为s?(不多也不少) 如果存在一种符合上述要求的选择,则称此背包问题有解(或称解为真),否则此问题无解(或称解

19、为假)。,背包问题的递归算法,int knap(int s,int n)if ( s = 0 )return true;else if (s0),递归算法的组成,递归出口(递归基础): 是递归定义的最基本情况,也是保证递归结束的前提 问题的最基本的停止条件 递归规则: 确定了又简单情况构筑复杂情况需要遵循的原则 减小问题规模的规则,背包问题的递归算法,int knap(int s,int n)if ( s = 0 )return true;else if (s0),递归基础,递归规则,递归出口1:背包承重为0,问题有解 递归出口2:背包承重为负,或者大于0但是物品个数小于1(0或负数),问题无

20、解,递归规则1:若wn-1包含在解中,求解knap(s-wn-1, n-1) 递归规则2:若wn-1不包含在解中,求解knaps,n-1,机械方法 设置一工作栈当前工作记录 设置 t+2个语句标号 t个规则,每个一标号; 1个用于函数入口; 1个用于函数出口分流处(标号t+1); 增加非递归入口 替换第 i (i = 1, , t)个递归规则 所有递归出口处增加语句:goto label t+1; 标号为t+1的语句的格式 改写循环和嵌套中的递归 优化处理,switch (x=S.top ().rd) case 0 : goto label 0;break;case 1 : goto labe

21、l 1;break;.case t+1 : item = S.pop() / 返回处理break;default : break; ,在函数中出现的所有参数和局部变量都必须用栈中相应的数据成员代替 返回语句标号域 (t+2个数值) 函数参数(值参、引用型) 局部变量typedef struct elem / 栈数据元素类型int rd; / 返回语句的标号Datatypeofp1 p1; / 函数参数 Datatypeofpm pm;Datatypeofq1 q1; / 局部变量 Datatypeofqn qn; ELEM;,label 0 :第一个可执行语句 label t+1 :设在函数体

22、结束处 label i (1=i=t): 第i个递归返回处,/ 入栈 S.push(t+1,p1, ,pm,q1,qn);,假设函数体中第i (i=1, , t)个递归调用语句为:recf(a1, a2, ,am); 则用以下语句替换: S.push(i, a1, , am); / 实参进栈 goto label 0; label i: x = S.pop();/* 退栈,然后根据需要,将x中某些值赋给栈顶的工作记录S.top () 相当于把引用型参数的值回传给局部变量 */,goto label t+1;,switch (x=S.top ().rd) case 0 : goto label

23、0;break;case 1 : goto label 1;break;.case t+1 : item = S.pop() / 返回处理break;default : break; ,对于循环中的递归,改写成等价的goto型循环 对于嵌套递归调用 譬如,recf ( recf() 改为: exmp1 = recf ( ); exmp2 = recf (exmp1); exmpk = recf (exmpk-1) 然后应用规则 4 解决,经过上述变换所得到的是一个带goto语句的非递归程序。可以进一步优化去掉冗余进栈/出栈根据流程图找出相应的循环结构,从而消去goto语句,背包问题的非递归算法

24、,bool nonRecKnap(int s, int n)tmp.s=s, temp.n=n, tmp.rd=0;stack.push(tmp); label0:stack.pop(,int knap(int s,int n)if ( s = 0 )return true;else if (s0),label1:stack.pop( ,int knap(int s,int n)if ( s = 0 )return true;else if (s0),目录,3.1 栈、队列与线性表 3.2 栈 3.3 队列 3.4 深入探讨,3.3.1 队列的概念,先进先出(FirstInFirstOut)

25、限制访问点的线性表 按照到达的顺序来释放元素 所有的插入在表的一端进行,所有的删除都在表的另一端进行 主要元素 队头(front) 队尾(rear),队头,队尾,进队,出队,k0 k1 k2 . kn-1,入队列(enQueue) 出队列(deQueue) 取队首元素(getFront) 判断队列是否为空(isEmpty),3.3.2 队列的抽象数据类型,template class Queue public: / 队列的运算集void clear(); / 变为空队列bool enQueue(const T item); / 将item插入队尾,成功则返回真,否则返回假bool deQueu

26、e(T,3.3.3 队列的实现方式,顺序队列 关键是如何防止假溢出 链式队列 用单链表方式存储,队列中每个元素对于链表中的一个结点,1)顺序队列,使用顺序表来实现队列 用向量存储队列元素,并用两个变量分别指向队列的前端和尾端,7,6,5,4,3,2,1,0,Q.front Q.rear,k0,k1,k2,k5,队列空,再进队一个元素如何?,队列示意:普通,k4,上溢 当队列满时,再做进队操作,所出现的现象 下溢 当队列空时,再做删除操作,所出现的现象 假溢出 当 rear = MAXNUM时,再作插入运算就会产生溢出,如果这时队列的前端还有许多空的(可用的)位置,这种现象称为假溢出,k4,k5

27、,k6,k6,k5,队列满,队列示意:环形,k0,k1,k2,k3,Q.front,队列空,k4,空队列,队列满,判断 (Q.rear+1) = Q.front,队列示意:环形,class arrQueue: public Queue private: int mSize; / 存放队列的数组的大小int front; / 表示队头所在位置的下标int rear; / 表示队尾所在位置的下标T *qu; / 存放类型为T的队列元素的数组 public: / 队列的运算集arrQueue(int size) / 创建队列的实例 mSize = size +1; / 浪费一个存储空间,以区别队列空

28、和队列满qu = new T mSize;front = rear = 0;arrQueue() / 消除该实例,并释放其空间delete qu; ,bool arrQueue : enQueue(const T item) / item入队,插入队尾if (rear + 1 ) % mSize) = front) cout “队列已满,溢出“ endl;return false;qurear = item;rear = (rear +1) % mSize; / 循环后继return true; ,bool arrQueue : deQueue(T,单链表队列 链接指针的方向是从队列的前端向尾

29、端链接,2)链式队列,template class lnkQueue: public Queue private: int size; / 队列中当前元素的个数Link* front; / 表示队头的指针Link* rear; / 表示队尾的指针 public: / 队列的运算集lnkQueue(int size) / 创建队列的实例size = 0;front = rear = NULL;lnkQueue() / 消除该实例,并释放其空间clear(); ,/ item入队,插入队尾 bool lnkQueue : enQueue(const T item) if (rear = NULL)

30、 / 空队列front = rear = new Link (item, NULL);else / 添加新的元素rear- next = new Link (item, NULL); rear = rear -next;size+;return true;,/ 返回队头元素并从队列中删除 bool lnkQueue : deQueue(T ,目录,3.1 栈、队列与线性表 3.2 栈 3.3 队列 3.4 深入探讨,栈的两种存储结构比较,时间效率 所有操作都只需常数时间 顺序栈和链式栈在时间效率上难分伯仲 空间效率 顺序栈须说明一个固定的长度 链式栈的长度可变,但增加结构性开销,多栈管理,两个独立的栈 底部相连:双栈 迎面增长当两个栈此消彼长时特别有效,迎面增长的栈,top1,top2,队列两种存储结构的比较,顺序队列 固定的存储空间 方便访问队列内部元素 链式队列 可以满足浪涌大小无法估计的情况 访问队列内部元素不方便,只要满足先来先服务特性的应用均可采用队列作为其数据组织方式或中间数据结构。 调度或缓冲 消息缓冲器 邮件缓冲器 计算机的硬设备之间的通信也需要队列作为数据缓冲 操作系统的资源管理 宽度优先搜索,双端队列 双栈 超队列 超栈,栈 栈的特点 栈的实现 栈的应用 队列 队列的特点 队列的实现 队列的应用,

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 企业管理 > 管理学资料

本站链接:文库   一言   我酷   合作


客服QQ:2549714901微博号:道客多多官方知乎号:道客多多

经营许可证编号: 粤ICP备2021046453号世界地图

道客多多©版权所有2020-2025营业执照举报