数据结构与算法 -- 08 栈

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 栈 | 实际应用》将介绍一些实际应用。你可以想到一些吗?

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值