数据结构与算法Python语言描述栈和队列课件.ppt
《数据结构与算法Python语言描述栈和队列课件.ppt》由会员分享,可在线阅读,更多相关《数据结构与算法Python语言描述栈和队列课件.ppt(91页珍藏版)》请在三一办公上搜索。
1、4,栈和队列,栈和队列的概念数据的生成,缓存,使用和顺序栈的实现和问题栈应用实例栈与递归,递归和非递归队列的实现和问题队列应用实例搜索问题相关问题,概述,栈(stack)和队列(queue)是两种使用最广泛的数据结构,它们都是保存数据元素的容器,可以将元素存入其中,或者从中取出元素使用(查看,或弹出,即在取得元素的同时将其从容器里删除)容器是一大类能保存数据元素的数据结构,它们都保证存入的元素可以在将来取得,而被删除的元素不再存在于容器之中栈和队列主要用于在计算过程中临时性地保存元素这些元素是前面计算中发现或产生的中间数据,需要在后面使用如果产生的中间数据暂时不用或者用不完,但将来可能要用到,
2、就有必要临时保存起来栈和队列常用于生成和使用之间的缓冲,称为缓冲存储或缓存栈和队列的操作都比较简单最重要的就是存入元素和取出元素两个操作还可能有另外一些辅助操作,如创建,检查,判断空/满等,概述,中间数据的生成有早有晚,时间上有先后。在实际应用中,使用这些元素也可能需要按时间顺序。有两种最典型的顺序如下:按数据生成的顺序,后生成的数据需要先处理按数据生成的先后顺序处理,先生成的数据先处理如去银行办事,先到的应先得到服务,具体等待方式不重要,如直接排在一个等待队列上拿(顺序)号后等叫号,每次叫尚未服务的最早来的顾客在这两种情况下,访问(并可能删除)都按默认方式确定元素栈和队列就是支持按这两种顺序
3、使用元素的缓存数据结构栈和队列存入操作只需要保证元素存入和将来取出的顺序,不记录也不保证新存入元素与容器中已有元素之间的任何关系,概述,栈和队列保证元素存取之间的时间关系,特点是:栈是保证缓存元素后进先出(Last In First Out,LIFO)的结构队列是保证缓存元素的先进先出(先存者先用,First In First Out,FIFO)关系的结构对于栈和队列,任何时候,下次访问或删除的元素都默认地唯一确定。只有新的存入或删除(弹出)操作可能改变下次的默认元素栈和队列的性质完全是抽象的、逻辑的,对实现方式(如何实现相应时间顺序)并没有任何约束,任何能满足要求的技术均可使用最方便的技术是
4、利用元素的排列顺序表示其先来后到关系,也就是说,可以用线性表作为栈和队列的“实现结构”例如:把元素进入实现为表的后端插入,这样最老的元素总是最前面的元素(作为队列下次应访问和删除)最新的元素总是最后一个元素(作为栈下次应访问和删除),概述,栈和队列是计算中使用最广的缓存结构,其使用环境可以总结如下:计算过程分为一些顺序进行的操作步骤执行的操作时会产生一些后面可能需要用的中间数据产生的某些数据无法立即用掉,需要保存起来以备后面使用需要保存的数据的项数不能事先确定这种情况下,通常就需要用一个栈或一个队列作为缓存栈和队列是许多重要算法的基础,后面有许多实例。栈和队列的性质和操作效率,也是许多算法设计
5、中的关键因素由于栈和队列在应用中的重要性,Python 的基本功能中包括了对栈的支持(可直接用 list),标准库提供了支持队列用途的结构下面分别讨论这两种结构的情况,包括性质和模型;实现和问题;一些应用;一些一般性问题,栈:概念,栈(stack),有的书籍里称为堆栈,是一种容器,可存入数据元素、访问元素、删除元素。在这里没有位置的概念栈保证任何时刻可以访问、删除的元素都是在此之前最后存入的那个元素。因此确定了一种默认的访问顺序栈的基本操作是一个封闭集合(与表不同),通常包括:创建空栈判断栈是否为空(还可能需要判断满),is_empty()向栈中插入(通常称推入/压入,push)一个元素,pu
6、sh(.)从栈中删除(弹出,pop)一个元素,空栈弹出报错,pop()取当前(最新)元素的值(并不删除),top(),栈:特性和基本实现考虑,栈可以实现为(可以看作)只在一端插入和删除的表因此有人(有书籍中)把栈称为后进先出(LIFO)表进行插入或删除操作的一端称为栈顶,另一端称为栈底用线性表技术实现栈时,由于只需要在一端操作,自然应该利用实现最方便,而且能保证两个主要操作的效率最高的那一端采用连续表方式实现,在后端插入删除都是 O(1) 操作(!)采用链接表方式实现,在前端插入删除都是 O(1) 操作栈通常都采用这两种技术实现实现栈之前,先定义一个自己的异常类(Python 的内部异常是一组
7、类,都是 Exception 的子类,可以继承已有异常类定义自己的异常类)class StackUnderflow(ValueError): # 栈下溢(空栈访问) pass,栈:实现,采用连续表技术实现,也会遇到连续表的各种相关问题用简单连续表,还是采用动态连续表(分离式实现的连续表)?如果用简单连续表,就可能出现栈满的情况采用动态连续表,栈存储区满时可以换一块更大的存储区。这时又会出现置换策略问题,以及分期付款式的 O(1) 复杂性Python list 及其操作实际上提供了一种栈功能,可以作为栈使用建立空栈,对应于创建一个空表 ,判空栈对应于判空表由于 list 采用动态连续表技术(分离
8、式实现),作为栈的表不会满压入元素操作应在表尾端进行,对应于 lst.append(x)弹出操作也应在尾端进行,无参的 lst.pop() 默认弹出表尾元素由于采用动态连续表技术,压入操作具有分期付款式的 O(1) 复杂性,其他操作都是 O(1) 操作,栈:实现,为了概念清晰、操作名更易理解、排除不符合栈性质的其他操作,应该基于连续表定义一个栈类,用 list 作为其实现的基础class SStack(): # 基于顺序表技术实现的栈类 def _init_(self): # 用list对象 _elems存储栈中元素 self._elems = # 所有栈操作都映射到list操作 def is
9、_empty(self): return self._elems = def top(self): if self._elems = : raise StackUnderflow(in SStack.top() return self._elems-1 def push(self, elem): self._elems.append(elem) def pop(self): if self._elems = : raise StackUnderflow(in SStack.pop() return self._elems.pop(),栈:链接表实现,采用链接技术,可以借用 LNode 类实现一
10、个链接栈:class LStack (): # 基于链接表技术实现的栈类,用LNode作为结点 def _init_(self): self._top = None def is_empty(self): return self._top is None def top(self): if self._top is None: raise StackUnderflow(in LStack.top() return self._top.elem def push(self, elem): self._top = LNode(elem, self._top) def pop(self): if s
11、elf._top is None: raise StackUnderflow(in LStack.pop() p = self._top self._top = p.next return p.elem,栈的应用,栈是算法和程序里最常用的辅助结构,基本用途基于两方面:用栈可以很方便地保存和取用信息,因此常作为算法或程序里的辅助存储结构,临时保存信息,供后面的操作使用利用栈后进先出的特点,可以得到特定的存储和取用顺序许多实际运用结合了这两方面的特性作为最简单的应用实例,栈可用于颠倒一组元素的顺序将一组元素依次全部存入后取出,就能得到反序的序列通过不同的存入取出操作序列,可以得到不同的元素序列。但
12、请注意,这种做法不能得到任意的排列,结果序列有一定的规律性下面介绍栈的若干典型应用有些比较具体,是特殊的应用有些实例代表一大类应用,更具典型性,简单应用:括号配对问题,在许多正文中都有括号,特别是在表示程序、数学表达式的正文片段里。括号有正确配对问题。作为例子,考虑 Python 程序里的括号存在不同的括号:如圆括号、方括号和花括号每种括号都有开括号和闭括号,括号括起的片段可能相互嵌套,各种括号需要分别正确嵌套配对配对的原则遇到的闭括号应该匹配此前遇到的最近的尚未匹配的对应开括号由于多种/多次/可能嵌套,为检查配对,遇到的开括号必须保存由于括号可能嵌套,需要逐对匹配,闭括号应与前面最近的尚未有
13、匹配的开括号匹配,后面括号应与更前面次近的括号匹配可以删除匹配的括号,为后面的匹配做好准备后遇到并保存的开括号应该先删除,这就是后进先出,而且要按照出现顺序,显然应该/可以用一个栈保存开括号,简单应用:括号配对问题,问题处理的线索已经清楚了:顺序检查被处理正文(是一个字符串)里的一个个字符跳过无关字符遇到开括号时将其压入一个栈遇到闭括号时弹出栈顶元素与之匹配括号匹配则继续遇到不匹配时检查以失败结束下面考虑定义一个函数完成这个检查,其中先定义一些检查中需要使用的数据和变量,简单应用:括号配对问题,函数定义(很简单):def check_pares(text): pares = () open_p
14、ares = ( opposite = ):(, :, : # 表示配对关系的字典 def paretheses(text): . # 括号生成器,定义见后 st = SStack() for pr, i in paretheses(text): # 对 text 里各括号和位置迭代 if pr in open_pares: # 开括号,压进栈并继续 st.push(pr) elif st.pop() != oppositepr: # 不匹配就是失败,退出 print(Unmatching is found at, i, for, pr) return False # else 是一次括号配对
15、成功,什么也不做,继续 print(All paretheses are correctly matched.) return True,简单应用:括号配对问题,括号生成器定义为(局部)函数def paretheses(text): i, text_len = 0, len(text) while True: while i = text_len: return yield texti, i i += 1生成器(回忆一下):用 yield 语句产生结果可以用在需要迭代器的地方函数结束导致迭代结束,简单应用:括号配对问题,总结:这个函数利用了栈的性质,这是关键函数开始先准备好处理中需要用的数据。
16、虽然各种数据在程序里都只有一处使用,但这样定义使程序清晰易读,更易修改利用了 Python 丰富的数据结构和操作,如 in,not in 和字典等定义理一个独立的括号生成器函数,使代码功能清晰。例如,要检查实际 Python 程序里的括号,还需要考虑跳过程序里的注释和字符串等,修改括号生成规则时只需要修改这个函数请自己考虑和练习,表达式的表示、计算和变换,小学生就开始写表达式,先是算术表达式,后来是代数表达式对二元运算符,通常把它们写在两个运算对象中间,这种写法称为中缀表示,按中缀表示法写出的表达式称为中缀表达式中缀表示很难统一贯彻,一元和多元运算符很难用中缀表示代数表达式里的函数符号通常写在
17、运算对象前面,这种写法称为前缀表示。为界定函数参数的范围,通常用括号将它们括起可见,常见的表达式习惯表示是前缀表示和中缀表示的组合实际上,表达式并不一定采用这种习惯写法可以用纯粹的前缀表示,这样写出的表达式称为前缀表达式还有一种表达式写法称为后缀表示,其中运算符(函数)总写在它们的运算对象之后,这样写出的表达式称为后缀表达式。实际上,后缀表达式特别适合计算机处理前缀表达式也称为波兰表达式(由波兰数学家 J. Lukasiewicz 于1929提出),后缀表达式也称为逆波兰表达式,表达式的表示、计算和变换,首先假设每个运算符有确定的元数(运算对象个数),而且元数唯一写表达式时,最重要的问题是准确
18、描述计算的顺序中缀表达式的一个重要缺点是不能直接表示运算的顺序,需要借助于辅助性约定和/或辅助描述机制,实际情况:必须引进括号机制,表示括号里的运算先做(显式描述计算顺序)为减少总写括号的麻烦,还引进优先级概念,规定运算符的优先级(或优先级关系),高优先级运算符的结合性强,运算符相邻出现时,优先级高的运算先做还需规定相同优先级的运算符相邻出现时的计算顺序(结合性)与之对应的情况:在上述基本假定下,前缀表达式和后缀表达式都不需要括号,也不需要优先级,就能自然地描述任意复杂的计算顺序可见,中缀表达式的表达能力最弱中缀表达式增加了括号后,几种表达方式具有同等表达能力,表达式的表示、计算和变换,对比几
19、种不同表达式形式。下面三个算术表达式等价中缀:(3 - 5) * (6 + 17 * 4) / 3前缀:/ * - 3 5 + 6 * 17 4 3后缀:3 5 - 6 17 4 * + * 3 /三个表达式描述的是同一个计算过程下面先考虑后缀表达式的求值,假定被处理的是算术表达式其中的运算对象是浮点数形式表示的数运算符只有 +、-、*、/,都是二元运算符后面还要讨论不同表达式形式的转换(考虑中缀形式到后缀形式)研究相应的转换算法,以及中缀表达式的求值,后缀表达式的计算,考虑计算过程,设有函数 nextItem() 得到下一个运算对象或运算符:遇到运算对象,需要记录以备后面使用遇到运算符(或函
20、数名),需要根据其元数取得前面最近遇到的几个运算对象或已做运算得到的结果,实施计算并记录结果问题:应该用什么结构记录信息?看看计算过程的性质:需要记录的是已经掌握但还不能立即使用的中间结果遇到运算符时,要用的是此前最后记录的几个结果(根据元数)显然应该用栈作为缓存结构上面分析也说明了算法的基本结构实际程序就是把这个算法用 Python 的语言写出来,后缀表达式的计算,先考虑算法的框架假定 st 是栈,算法核心是个循环(逐个处理运算符或运算对象):while 还有输入 : x = nextItem() if not is_operator(x): st.push(float(x) else: a
21、 = st.pop() # 第二个运算对象 b = st.pop() # 第一个运算对象 . . # 根据运算符 x 对 a 和 b 计算 . . # 计算结果压入栈这里写了几个辅助过程,其具体实现依赖于一些情况被求值的表达式从哪里获得表达式里的元素如何表示(下面假定用字符串),后缀表达式的计算,假定从函数参数获得输入参数是一个字符串,内容是需要求值的后缀表达式表达式中不同的项之间有空格分隔为程序清晰,定义了一个包装过程:# 定义一个函数把表示表达式的字符串转化为项的表def suffix_exp_evaluator(line): return suf_exp_evaluator(line.s
22、plit()定义一个扩充的栈类,增加一个检查栈深度的方法:class ESStack(SStack): def depth(self): return len(self.elems)下面是核心求值过程的定义(与前面设计相比有一些小修改)由于函数的输入就是一个项的表,后缀表达式的计算,def suf_exp_evaluator(exp): operators = +-*/ st = ESStack() # 扩充功能的栈,可用 depth() 检查栈内元素个数 for x in exp: if not x in operators: st.push(float(x); continue # 不能转
23、换时自动引发异常 if st.depth() 2: raise SyntaxError(Short of operand(s).) a = st.pop() # 第二个运算对象 b = st.pop() # 第一个运算对象 if x = +: c = b + a elif x = -: c = b - a elif x = *: c = b * a elif x = /: c = b / a # 可能引发异常 ZeroDivisionError else: break # 不可能出现 else 分支的情况 st.push(c) if st.depth() = 1: return st.pop(
24、) raise SyntaxError(Extra operand(s).) # 运算对象个数不匹配,后缀表达式的计算,定义一个交互式的驱动函数(主函数):def suffix_exp_calculator(): while True: try: line = input(Suffix Expression: ) if line = end: return res = suffix_exp_evaluator(line) print(res) except Exception as ex: print(Error:, type(ex), ex.args)注意:这里用一个 try 块捕捉用户使用
25、时的异常,保证我们的计算器不会因为用户输入错误而结束except Exception as ex 两个用途:Exception 表示捕捉所有异常保证交互继续;ex 将约束到所捕捉异常,使处理器能用相关信息,至此后缀表达式计算器的开发工作完成,中缀表达式到后缀表达式的转换,中缀表达式的情况比较复杂,直接求值比较复杂可以考虑首先将其转换为等价的后缀表达式,然后借用前面定义的后缀表达式求值器,完成计算考虑这里的情况,例如中缀:(3 - 5) * (6 + 17 * 4) / 3后缀:3 5 - 6 17 4 * + * 3 /分析情况:遇到运算对象应直接输出(因为运算符应该在它们的后面)需要考虑中缀
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 数据结构 算法 Python 语言 描述 队列 课件

链接地址:https://www.31ppt.com/p-1520080.html