栈(Stack)是一个数据集合,可以理解为只能在一端进行插入或删除操作的列表。
栈的特点:后进先出(last-in, first-out)
栈可以分为
- 顺序栈: 数组实现
- 链式栈: 链表实现
栈的概念:
- 栈顶
- 栈底
栈的空间复杂度:
有一个n个元素的栈, 在入栈和出栈过程中, 只需要存储一个临时变量存储空间, 所以空间复杂度是O(1)
并不是说栈有n个元素, 空间复杂度就是O(n), 而是指除了原本的空间外, 算法需要的额外空间
一、栈的线性表实现
- 线性表,后端插入和删除的时间复杂度是O(1),表尾作为栈顶;
- 链接表,前端插入和删除的时间复杂度是O(1),表首作为栈顶;
1.栈的顺序表实现
栈的基本操作
- 进栈(压栈):push
- 出栈:pop
- 取栈顶:top
- 判断为空:isEmpty
- 返回栈大小:size
先定义一个异常类
class StackUnderFlow(ValueError):
pass
class SStack:
def __init__(self):
self._elems = [] # 先在初始化函数里面定义一个空列表
def is_empty(self):
return self._elems == []
def size(self):
return len(self._elems)
def push(self, elem):
self._elems.append(elem)
def pop(self):
if self.is_empty():
raise StackUnderFlow(" in SStack.pop()")
return self._elems.pop()
def top(self):
if self.is_empty():
raise StackUnderFlow(" in SStack.top()")
return self._elems[-1]
- pop和peek函数都有先检查栈是否为空的情况,否则会报错;
栈的reverse
思路:把所有元素按照原来的顺序全部入栈,再顺序取出,就能得到反序后的序列
st = SStack()
for i in lis:
st.push(i)
lis = []
while not st.is_empty:
lis.append(st.pop())
2.栈的链接表实现
class LStack:
def __init__(self):
self._top = None
def is_empty(self):
return self._top == None
def push(self,elem):
self._top = LNode(elem,self._top)
def pop(self):
if self.is_empty():
raise StackUnderFlow("in LStack.pop()")
p = self._top.elem
self._top = p.next
return p
def top(self,elem):
if self.is_empty():
raise StackUnderFlow("in LStack.top()")
return self._top.elem
二、 栈的应用
1. 括号匹配问题
给一个字符串,其中包含小括号、中括号、大括号,求该字符串中的括号是否匹配。
def check_parentheses(text):
open_parens = "([{"
parens = "([{}])"
opposite = { ')':'(', ']':'[','}':'{'}
def parentheses(text):
i,n = 0, len(text)
while True:
while i<n and text[i] not in parens:
i += 1
if i >= n:
return
yield text[i],i
i += 1
st = SStack()
for pr,i in parentheses(text):
if pr in open_parens:
st.push(pr)
elif st.pop() != opposite[pr]:
pass
else:
return False
print("all parentheses are correctly matched")
return True
text = "test([{test}])"
print(check_parentheses(text))
'''
all parentheses are correctly matched
True
'''
2. 求二进制
如数字6(110), 分别用2除6, 求余数, 最后余数反转就是110(先除2求余,再整除2)
def binary(num):
s = SStack()
while num > 0:
n = num % 2
s.push(n)
num = num // 2
# 反转
res = ""
while not s.isEmpty():
res += str(s.pop()) # 栈是后进后出,一个个pop出来就是倒序的,转成字符串
return res
if __name__ == "__main__":
assert binary(5) == '101'
assert binary(8) == '1000'
assert binary(9) == '1001'
divmod:计算除数和被除数的结果,并返回一个包含商和余数的元组。但是会使速度变慢。
n,num = divmod(num, 2)
divmod(5,2) # (2,1)
三、 栈与递归
将递归转换为非递归,只需要一个栈保存递归函数执行时每层调用的局部信息。
递归
def fact(n):
if n == 0:
return 1
else:
return n * fact(n-1)
非递归
def norec_fact(n):
st = SStack()
res = 1
while n>0:
st.push(n)
n -= 1
while not st.is_empty():
res *= st.pop()
return res
简单背包问题
假设有n件质量分配为w1,w2,…,wn的物品和一个最多能装载总质量为maxW的背包,能否从这n件物品中选择若干件物品装入背包,使得被选物品的总质量“恰好”等于背包所能装载的最大质量,即wi1+wi2+…+wik=maxW。若能,则背包问题有解,否则无解。
def knap_rec(weight,wlist,n) :
if weight == 0 :
return True
if weight < 0 or (weight >0 and n < 1) :
return False
if knap_rec(weight - wlist[n-1],wlist,n-1) :
print("Item" + str(n) + ":" , wlist[n-1])
return True
if knap_rec(weight,wlist,n-1) :
return True
else :
return False
拓展:栈的Python实现
不需要自己定义,使用列表结构即可。
- 进栈函数:append
- 出栈函数:pop
- 查看栈顶函数:li[-1]
进栈顺序是ABC,哪个不可能是它的出栈顺序。
ABC
ABC A进A出,B进B出,C进C出
ACB A进A出,B进C进,C出B出
BCA A进栈,B进栈,B出栈,C进栈,C出栈,A出栈
BAC A进栈,B进栈,B出栈,A出栈,C进栈,C出栈
CBA A进栈,B进栈,C进栈,C出栈,B出栈,A出栈
CAB
n个元素序列的合法出栈顺序有多少种?就是卡特兰数的第n项。
卡特兰数又称卡塔兰数,英文名Catalan number,是组合数学中一个常出现在各种计数问题中出现的数列。以比利时的数学家欧仁·查理·卡塔兰 (1814–1894)的名字来命名,其前几项为(从第零项开始) : 1, 1, 2, 5, 14, 42, 132, 429, 1430, 4862, ...
卡特兰数Cn满足以下递推关系