数据结构 | 栈

47 阅读3分钟

先进后出逻辑的线性数据结构

  • 堆叠元素的顶部叫栈顶,底部叫栈底
  • 添加元素到栈顶的操作叫入栈,删除栈顶元素的操作叫出栈

栈的基本操作

方法描述时间复杂度
push()元素入栈(添加至栈顶)O(1)
pop()元素出栈O(1)
peek()访问栈顶元O(1)

基于编程语言内置结构模拟栈

通常编程语言设置有自己内置的栈结构,有些语言没有提供栈类时,可以使用该语言的'数组'或'链表'作为栈结构

# python for example
stack = []

# 入栈
stack.append(1)
stack.append(2)
stack.append(3)

# 访问栈顶元素
print(stack[-1])

# 出栈
for _ in range(len(stack) -1):
    stack.pop()

# 访问栈顶元素
print(stack[-1])
3
1

栈的实现

模拟栈,需要遵循先进后出的原则,限制元素的进出

相对于数组和链表在任意位置添加和删除元素,栈视作一种受限制的数组或链表

定义一个栈节点的结构,可以使用基本数据类型,同时也可以自己定义数据类型,实现栈有两种基本方式,可以在之前的链表学习的基础上,将节点定义为一个链表节点和基本数据结构数组

# 定义链表节点
class ListNode():
    def __init__(self, val):
        self.val = val
        self.next = None

基于链表实现栈

使用链表实现栈时,可以将头节看作为栈顶,尾结点看作栈底

  • 入栈操作只需要将新元素插入头节点前,该方法称为头插法,反之也可以,称为尾插法
  • 出栈操作只需要将头节点从链表中删除,相反只需要删除尾节点
class linkedlist_stack:
    def __init__(self):
        # 类型
        self.point = None
        # 容量
        self._size = 0

    def size(self):
        # 获取当前容量大小
        return self._size

    def is_empty(self):
        # 判断是否为空
        return self._size == 0

    def push(self, val):
        # 入栈操作,将元素插入链表头部
        node = ListNode(val)
        node.next = self.point
        self.point = node
        self._size += 1

    def pop(self):
        # 出栈从头节点删除
        # 取出栈顶元素并返回
        number = self.peek()
        self.point = self.point.next
        self._size -= 1
        return number

    def peek(self):
        # 访问栈顶元素
        if self.is_empty():
            return '栈为空'
        return self.point.val

    def to_list(self):
        # 转为列表打印显示
        arr = []
        p = self.point
        while p:
            arr.append(p.val)
            p = p.next
        arr.reverse()
        return arr
# test
stack = linkedlist_stack()
print(stack.is_empty())
print(stack.to_list())

for i in range(5):
    stack.push(i)
print(stack.to_list())
for i in range(1,3):
    num = stack.pop()
    print(f'出栈:{num}')
print(stack.size())
print(stack.to_list())
True
[]
[0, 1, 2, 3, 4]
出栈:4
出栈:3
3
[0, 1, 2]

基于数组实现栈

使用数组实现栈时,将数组尾看作栈顶

  • 入栈时,在数组尾添加新元素
  • 出栈时,数组尾删除一个元素

时间复杂度都为O(1)

class ArrayStack:
    """基于数组实现栈"""
    def  __init__(self):
        # 定义数据结构
        self._stack = []

    def size(self):
        """获取栈大小"""
        return len(self._stack)

    def is_empty(self):
        """判断栈空"""
        return self.size() == 0

    def peek(self):
        """获取栈顶元素"""
        if self.is_empty():
            print('栈空')
            raise IndexError('栈空')
        return self._stack[-1]
        
    def push(self, value):
        """入栈"""
        self._stack.append(value)

    def pop(self):
        """出栈"""
        value = self.peek()
        self._stack.pop()
        return value

    def to_list(self):
        """转为列表便于打印查看"""
        return self._stack
# test
stack = ArrayStack()
print(stack.is_empty())
print(stack.to_list())

for i in range(5):
    stack.push(i)
print(stack.to_list())
for i in range(1,3):
    num = stack.pop()
    print(f'出栈:{num}')
print(stack.size())
print(stack.to_list())
True
[]
[0, 1, 2, 3, 4]
出栈:4
出栈:3
3
[0, 1, 2]

两种实现方式比较

时间效率:

当入栈、出栈元素为基本数据类型,比如int,double

  • 基于数组实现的栈在触发扩容时效率会降低,但由于扩容是低频操作,因此平均效率更高
  • 基于链表实现的栈可以提供更加稳定的效率表现

空间效率:

  • 基于数组实现的栈可能造成一定的空间浪费
  • 由于链表节点需要额外存储指针,因此链表节点占用的空间相对较大

不能简单地确定哪种实现更加节省内存,需要针对具体情况进行分析