思考
首先思考一个问题,浏览器的前进、后退功能是如何实现的呢?
理解栈
栈
结构:先进的后出,后进的先出。类似于洗好的盘子,叠一摞,下次用的时候只能从最上面那个盘子开始拿。- 操作特性:
操作受限
的线性表 - 什么时候用:当某个数据集合只涉及在一端插入和删除数据,并且满足后进先出的特性,就应该首选
栈
这种结构。
如何实现栈
栈既可以用数组实现,也可以用链表来实现。用数组实现的栈,叫作顺序栈,用链表实现的栈,叫作链栈。
-
示例:顺序栈
class Stack(): def __init__(self,size): """初始化""" self.size = size self.num = 0 self.stack = [] def getSize(self): """获取栈的长度""" return self.num def print_all(self): """输出栈元素""" for s in self.stack: print s def append_stack(self,value): """入栈""" if self.num >= self.size: print("the stack is full") return else: self.stack.append(value) self.num += 1 def pop_stack(self): """ 出栈""" if self.num is None: print("the stack is empty") return else: self.stack.remove(self.stack[-1])
复杂度分析
空间复杂度
无论是顺序栈还是链栈,存储数据只需要一个大小为n的数组。在入栈和出栈过程中,只需要一两个临时变量存储空间,所以空间复杂度为O(1)
。
注:存储数据需要一个大小为n的数组,并不是指空间复杂度就为O(n)。因为,这 n 个空间是必须的,无法省掉。
我们说空间复杂度的时候,是指除了原本的数据存储空间外,算法运行还需要的额外的存储空间。
时间复杂度
不管顺序栈还是链栈,入栈、出栈只涉及栈顶个别数据的操作,所以复杂度为O(1)
。
支持动态扩容的顺序栈的入栈、出栈时间复杂度分析
对于出栈操作来说,不会涉及内存的重新申请和数据的搬移,所以出栈的时间复杂度仍然是 O(1)
。但是,对于入栈操作来说,情况就不一样了。当栈中有空闲空间时,入栈操作的时间复杂度为 O(1)
。但当空间不够时,就需要重新申请内存和数据搬移,所以时间复杂度就变成了 O(n)
。
也就是说,对于入栈操作来说,最好情况时间复杂度是 O(1)
,最坏情况时间复杂度是 O(n)
。而平均时间复杂度,由摊还分析法分析可知为 O(1)
。
栈的应用
在函数调用中的应用
操作系统给每个线程分配了一块独立的内存空间,这块内存被组织成“栈”这种结构, 用来存储函数调用时的临时变量。每进入一个函数,就会将临时变量作为一个栈帧入栈,当被调用函数执行完成,返回之后,将这个函数对应的栈帧出栈。
栈在表达式求值中的应用
实现一个表达式求值的功能,编译器就是通过两个栈来实现的。
其中一个保存操作数的栈,另一个是保存运算符的栈。
从左向右遍历表达式,当遇到数字,就直接压入操作数栈;当遇到运算符,就与运算符栈的栈顶元素进行比较。
如果当前运算符优先级高,就将当前运算符压入栈;如果运算符栈顶元素优先级高,就从运算符栈中取栈顶运算符,从操作数栈的栈顶取 2 个操作数,然后进行计算,再把计算完的结果压入操作数栈,继续比较。
栗子
3+5*8-6
表达式的计算过程如下:
栈在括号匹配中的应用
假设表达式中只包含三种括号,圆括号 ()
、方括号 []
和花括号{}
,并且它们可以任意嵌套。比如,{[{}]}
或 [{()}([])]
等都为合法格式,而{[}()]
或 [({)]
为不合法的格式。
问题
给定一个包含三种括号的表达式字符串,如何检查它是否合法呢?
方法
用栈来保存未匹配的左括号,从左到右依次扫描字符串。当扫描到左括号时,则将其压入栈中;当扫描到右括号时,从栈顶取出一个左括号。如果能够匹配,比如(
跟)
匹配,[
跟]
匹配,{
跟}
匹配,则继续扫描剩下的字符串。如果扫描的过程中,遇到不能配对的右括号,或者栈中没有数据,则说明为非法格式。
当所有的括号都扫描完成之后,如果栈为空,则说明字符串为合法格式;否则,说明有未匹配的左括号,为非法格式。
示例
left_brackets = '{[(<'
right_brackets = '}])>'
matching_brackets = {'}': '{', ']': '[', ')': '(', '>': '<'}
def judgment_brackets_matching(rows):
stack = []
label = True
for row in rows:
if row in left_brackets:
stack.append(row)
elif row in right_brackets:
if len(stack) < 1:
label = False
break
elif matching_brackets[row] == stack[-1]:
stack.pop()
else:
label = False
break
else:
continue
if stack:
label = False
return label
解答开篇
用栈实现浏览器的前进、后退功能
方法
使用两个栈,X
和 Y
,把首次浏览的页面依次压入栈 X
,当点击后退按钮时,再依次从栈 X
中出栈,并将出栈的数据依次放入栈 Y
。当我们点击前进按钮时,我们依次从栈 Y
中取出数据,放入栈 X
中。当栈 X
中没有数据时,那就说明没有页面可以继续后退浏览了。当栈 Y
中没有数据,那就说明没有页面可以点击前进按钮浏览了。
示例
顺序查看了a,b,c三个页面,将其依次压入栈,栈中数据情况为:
X: a->b->c
Y: None
点击后退按钮,从c页面推到a页面,栈中数据情况为:
X: None
Y: c->b->a
想再次查看b页面,点击前进按钮到b页面,此时栈中数据情况为:
X: a->b
Y: c
假设,此时在b页面跳转到新页面d,页面c就无法通过前进或后退按钮重复查看了,因而需要清空Y栈:
X: a->b->d
Y: None