栈
先进后出逻辑的线性数据结构
- 堆叠元素的顶部叫
栈顶
,底部叫栈底
- 添加元素到栈顶的操作叫
入栈
,删除栈顶元素的操作叫出栈
栈的基本操作
方法 | 描述 | 时间复杂度 |
---|---|---|
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
时
- 基于数组实现的栈在触发扩容时效率会降低,但由于扩容是低频操作,因此平均效率更高
- 基于链表实现的栈可以提供更加稳定的效率表现
空间效率:
- 基于数组实现的栈可能造成一定的空间浪费
- 由于链表节点需要额外存储指针,因此链表节点占用的空间相对较大
不能简单地确定哪种实现更加节省内存,需要针对具体情况进行分析