文章目录
1. 栈数据结构定义和特性
- 典型栈结构:先进先出,后进后出。
- 操作特性:栈是“操作受限”的线性表,受限在它只允许在一端插入和删除数据。
2. 数组和链表与栈相比,为什么选择栈呢?
从功能上来说,数组和链表可以代替栈。但是由于数组和链表对外暴露的可操作接口过多,操作灵活自由,导致使用时易出错,风险不可控。总之,特定的数据结构是对特定场景的抽象,需根据实际的业务场景选择合适的数据结构即可。
3. 开发时,判断该选择栈的依据是什么?
当某个数据集合只涉及在一端插入和删除数据,并且需要满足先进后出、后进先出的特性, 此时应首选“栈”这种数据结构。
4. 栈的实现
栈可以用数组和链表实现。用数组实现的称为顺序栈;用链表实现的称为链式栈。
4.1. 顺序栈(array stack)
class ArrayStack(object):
def __init__(self, size: int):
self.__array_stack = []
self.__size = size
self.__count = 0
def push(self, value: int):
if self.__count >= self.__size :
print("stack is already full!")
return
else:
self.__array_stack.append(value)
self.__count += 1
def pop(self):
if self.__count > 0:
self.__count -= 1
return self.__array_stack.pop()
else:
print("stack null!")
ps: 详细代码 array_stack.py
4.2. Dynamic Array Stack(动态扩容顺序栈)
众所周知,底层语言(python属于高级语言)数组实现是大小固定的连续内存,正因为这个特性数组才有了“杀手锏"功能–支持按索引随机访问数组元素,且随机访问的时间复杂度O(1),也就是说不管你数组多大,访问指定位置的元素时间都是固定的。
数组大小固定,那么当数组填满需要加入更多的元素怎么办?此时的做法是,另外再分配一块更大的连续内存空间当作新数组(一般是原来数组的2倍大小),然后将原来数组的内容拷贝搬运到新数组,之后再将新加入的元素尾部添加到该新数组。这个过程称之为数组的动态扩容(Dynamic Array)。
基于动态数组我们就可以得到动态扩容顺序栈了。
最后,python list的底层实现就是动态扩容数组。可以参考:cpython listobject.c
- 复杂度分析
我们从下面几个方面来分析动态扩容顺序栈的算法复杂度:
1). 出栈
这个简单,任何情况都是弹出一个元素。所以时间复杂度是O(1),空间复杂度也是O(1);
2). 入栈
这里有两种情况。
情况一:原来栈还有空闲空间。这是一般情况亦是最好情况。此时入栈,也是只涉及到压入一个元素。所以时间复杂度是O(1),空间复杂度也是O(1);
情况二:原来栈已满。这是最坏情况,此时再想入栈新的元素,需要先扩容原来的数组栈并且将原来栈的内容拷贝过来。算法运行的时间取决于原数组栈的大小,所以时间复杂度是O(n)(后面再压入一个新元素的时间是固定的,影响不了整体时间复杂度);显而易见扩容空间的大小决定了空间复杂度也是O(n)。
入栈的平均时间复杂度呢?
采用均摊分析法
来分析。假定栈满扩容时新栈的大小是原来的2倍。
1)当原栈满需要再入栈一个新元素时。
先申请新栈内存,之后再将原栈数据搬移(K个),最后将新元素入栈,所以此新元素入栈的时间复杂度是O(k+1),由于复杂度计算中常数不影响,故舍去后得O(k)。
2)新栈后续的 k-1 个元素入栈,由于不涉及到申请内存和数据搬移,所以时间复杂度都是O(1);
3)均摊。将栈满后的第一个元素入栈时的K个数据搬移均摊到后续其自身和后续 K-1 个元素,那么这批新的 K 个元素的入栈操作相当于1个数据搬移+1次入栈
。所以入栈平均时间复杂度是O(1)。
总结:绝大部分情况下,顺序栈入栈操作的时间复杂度是O(1),只有栈满扩容时这个特例入栈操作的时间复杂度是O(n),将其均摊到所有元素后,整体入栈的平均时间复杂度是O(1)。
注:一般情况下,平均时间复杂度 == 最好时间复杂度
。
4.3. 链式栈(linked stack)
4.4. 空间复杂度和时间复杂度
- 空间复杂度 O(1)
空间复杂度的计算是指,除了原本的数据存储空间外,算法运行还需要额外的存储空间
。
对于栈,一旦初始化,数据的大小 n 是固定的,运行时入栈出栈所需要的临时变量也就几个,所以栈所需的运行时存储空间是固定的,故空间复杂度为O(1)。 - 时间复杂度 O(1)
栈只有入栈和出栈两个操作,且这两个操作都只涉及个别数据的操作,所以栈的时间复杂度也为O(1).
预告:栈这种基础数据结构在软件工程中应用也很广泛,下一篇《数据结构与算法 – 08 栈 | 实际应用》将介绍一些实际应用。你可以想到一些吗?