2、容器~栈和队列
1、在常用的数据结构中,有一批结构被称为容器。一个容器结构里总包含一组其他类型数据对象,称为元素,支持对这些元素的存储、管理和使用。一类容器具有相同的性质,支持同一组操作,可以被定义为一个抽象数据类型。作为容器数据结构,它都保证存入的元素被保存在容器里,尚未明确删除的元素总可以访问,而取出并删除的元素就不再在容器中了。线性表(顺序表(list/tuple)、链表(各种链表))就是一类容器。还有另外两种常用的容器:栈和队列。
2、栈和队列主要用于在计算过程中保存临时数据,这些数据是计算中发现或者产生的,在后面的计算中可能需要使用它们。例如:工作中产生的中间数据暂时不用或者用不完,就有必要把当时不能立刻用掉的数据存起来。如果可能生成的数据项数在编程时就可以确定,问题比较简单,可以设置几个变量作为临时存储。但如果需要存储的数据项数不能确定,就必须采用更复杂的机制存储和管理,这样的存储机制称为缓冲存储或者缓存。栈和队列就是使用最多的缓冲存储结构。
3、栈和队列使用数据的顺序:栈和队列的实现结构只需要保证元素存入和取出的顺序,并不需要记录或保证新存入的元素与容器中已有元素之间的任何关系。也就是这两种结构只需要保证元素的存储时间顺序关系。即:
1)栈,后入先出,先入后出
2)队列,先入先出,后入后出
4、从实现的角度讲,由于计算机存储器的特点,要实现栈或队列,最自然的技术就是用元素存储的顺序表示它们的时间顺序,也就是说,应该用线性表作为栈和队列的实现结构。
5、
1、由于栈和队列在计算机应用中的重要性,Python的基本功能中已经包含了对
栈的支持,可直接用list实现栈的功能。此外,Python标准库还提供了一种支持队列用途的结构deque。
2.1栈
1、栈的抽象数据类型:
2、在用线性表技术实现栈的时候,操作只在表的一端进行,不涉及另一端,更不涉及表的中间部分。所以应该选择实现最方便且保证两个主要操作(加入、弹出)效率最高的那一端作为栈顶。因此:
1)若用顺序表(list/tuple)实现,后端插入和删除是O(1)操作,应该用这一端作为栈顶。
2)若用链表实现,前端插入和删除操作是O(1)操作,应该用这端作为栈顶。
2.2、栈的list实现
1、
2、栈类的list实现
3、在实现的过程中,有2点需要注意:
1)属性_elems是只在内部使用的属性,故采用下划线开头
2)raise语句中包含了一个字符串实参,执行中实际产生的异常对象将携带这个信息。在异常的引发处,可以用这种实参向最终捕获到这个异常的处理器传递一些信息。在这标明异常发生的位置,用于帮助检查程序错误
2.3、栈的链表实现
1、既然顺序表可以实现不满栈,为什么还要考虑用链表实现栈?
1)顺序表扩大存储需要做一次高代价的操作
2)顺序表需要完整的大块存储区
所以,采用连接技术,在这两个问题都有优势。
2、链接实现栈的缺点:
更多依赖于解释器的存储管理,每个结点的链接开销,以及链接结点在实际计算机内存中任意散布可能带来的操作开销。
3、采用链表实现栈,栈顶应该在表头。
4、栈的链表实现:
2.4、栈与递归
1、递归的定义:
在一个定义中引用了被定义的对象本身,这种定义被称为递归定义。如果在一种数据结构中的某几个或某几个部分具有与整体相同的结构,也可以说这是一种递归结构。
2、在递归定义或结构中,递归的部分必须比原来的整体简单,这样才有可能达到某种终结点(出口)。在递归结构中,必须存在非递归的基本结构构成的部分。如果不是这样就会出现无限递归,不能成为良好的定义。
3、一个递归例子(阶乘)
4、递归的调用过程:
5、递归与非递归之间的关系
2.5、队列的list实现
1、队列也是一种容器,可存入元素,访问元素,删除元素。
2、队列中也没有位置的概念,只支持默认方式的元素存入和取出,并且为先进先出(FIFO)结构。
3、队列的ADT:
4、用list实现队列的困难及操作时的状态图
表元素存储区大小是固定的,经过反复的入队和出队操作,一定会在某次入队时出现队尾(表满)的情况。而在出现这种溢出时,表前部通常会有些空位,因此这是一种“假性溢出”,并不是真的用完了整个元素区。假如元素储存区能自动增长(python的list实现),随着操作的进行,表前端会留下越来越大的空区,而且这片空区永远也不会用到,完全浪费了。鉴于此,可以设计一种可以利用前端空位的新顺序表来实现队列----循环顺序表。设计思想:如果入队时队尾已经达到存储区末端,应该考虑转到储存区开始的位置去入队新元素。另外, 要自己管理扩充顺序表。两方面原因:1)队列元素的存储方式与list元素的默认存储方式不一样。List元素总在其存储区的最前面一段;而队列的元素可能是表里的任意一段,有时还分为头尾两段。如果list自动扩充,其中的队列元素就有可能失控。2)list没提供检查元素存储区容量的机制,队列操作中无法判断系统如何扩容。
4、队列类的list实现
2.6、队列的链表实现
1、用最简单的“纯”单链表实现队列,会有几个效率不高的地方。只有在首段操作时才有O(1)复杂度,在尾端进行操作为O(n)复杂度,比如后端入队,访问队尾元素。若用单尾结点的单链表,则可以轻松解决这个问题,从而高效地实现队列。
2、队列ADT
3、队列类的带尾结点的单链表实现:
2.7、与栈或队列相关的结构(deque)
1、取模和取余的区别:
2、Python中的双链表----deque类