您的位置:首页 > 其它

测试开发基础之算法(5):栈的基础操作及应用

2020-05-10 04:10 405 查看

1. 栈的概念

栈是一种“操作受限”的线性表,支持两种基础操作,入栈和出栈。特点是先进后出,后进先出,也就说是先入栈的数据后出栈,后入栈的数据先出栈。

栈有几个概念需要我们了解:

  • 栈大小:就是栈的容量,表示最多可以放多少个数据。
  • 栈中元素:栈中的数据
  • 栈顶:栈的最上面的元素
  • 栈底:栈中最下面的元素
  • 入栈:将新的数据放入栈顶
  • 出栈:将数据从栈顶取出来

2.实现一个栈

栈可以用数组来实现,也可以用链表来实现。用数组实现的栈叫顺序栈,用链表实现的栈叫链式栈。栈只支持两种操作,入栈和出栈。下面用Python的列表来实现一个栈。

class Stack:
"""
用Python列表实现,其实是一个支持动态扩容的栈。如果是用Java的数组实现的话,需要手动扩容数组。
入栈和出栈时间复杂度是 O(1)
入栈和出栈空间复杂度是 O(1)
"""
def __init__(self):
self._stack = list()

def push(self, ele):
# 不会没空间,因为Python的list会动态扩容
self._stack.append(ele)

def pop(self):
if self.is_empty():
return None
return self._stack.pop()

def top(self):
return self._stack[-1]

def min(self):
return min(self._stack)

def max(self):
return max(self._stack)

def is_empty(self):
return self._stack.__len__() == 0

def length(self):
return self._stack.__len__()

def reverse(self):
self._stack = self._stack[::-1]

def clear(self):
while not self.is_empty():
self._stack.pop()

def __repr__(self):
return str(self._stack)

if __name__ == '__main__':
s = Stack()
s.push("a")
s.push(1)
s.push(2)
print(s)
s.reverse()
print(s)
print(s.pop())
print(s.pop())

3.栈的应用场景

3.1 栈在函数调用中的应用

操作系统给每一个线程分配一块内存空间,这块内存被组织成栈这种数据结构,用来存储函数调用时的临时变量。每进入一个函数,就会将这个函数的临时变量放入栈中,当函数执行完成后,再把这些临时变量从栈中去除。比如下面这块代码:

int main() {
int a = 1;
int ret = 0;
int res = 0;
ret = add(3, 5);
res = a + ret;
printf("%d", res);
reuturn 0;
}

int add(int x, int y) {
int sum = 0;
sum = x + y;
return sum;
}

a,ret,res这三个是main的临时变量,x,y,sum是add的临时变量,main函数调用了add函数。当进入到add函数时,内存的栈数据如下:

当add函数执行完成后,sum,x,y从栈中出栈,当main执行完成后,res,ret,a依次出栈。

3.2 栈在表达式求值中的应用

计算机是如何求表达式

3+5*8-6
的值的呢?编译器是采用两个栈来实现的,一个栈存放数据的操作数栈,一个栈用来存放操作符的运算符栈。

从左到右遍历表达式,遇到操作数则放入操作数栈。当遇到运算符,就与运算符栈的栈顶元素进行比较。

如果比运算符栈顶的运算符优先级高,则将运算符放入操作符栈。如果比运算符栈顶的运算符优先级低或者相等,则从操作符栈pop出两个操作数进行运算,再将结果push到操作数栈。继续比较。
看下

3+5*8-6
的运算过程:

3.3 用栈判断括号表达式是否合法

我们常见的括号有(),[],{},在使用这些括号时,括号可以嵌套单必须成对出现,比如{[] ()[{}]}或 [{()}([])] 等都为合法格式,而{[}()] 或 [({)] 为不合法的格式。

那如何判断一个包含三种括号的表达式字符串,它的括号用法是否合法呢?用栈就可以方便的解决这个问题。

从左到右扫描字符串,遇到左括号则放入栈,遇到右括号,则与栈顶元素对比,如果匹配则pop掉栈顶的元素,如果匹配,继续扫描剩下的字符串。

如果扫描过程中,遇到不能匹配的右括号,或者栈中没有数据,则字符串不合法。
如果扫描完字符串后,栈为空,则表示字符串合法。否则,说明有未匹配的左括号,字符串非法。

def match_bracket(strings):
open_bracket = "({["
close_bracket = ")]}"
bracket_map = {'(': ')', '[': ']', '{': '}'}
label = True
stack = Stack()  # 创建空栈
if strings == "":
return True
for char in strings:
if char not in (open_bracket + close_bracket):  # 只处理左右括号
continue
if char in open_bracket:
stack.push(char)  # 左括号入栈
continue
if char in close_bracket:
if stack.is_empty():   # 第一个出现的是右括号
label = False
break
if bracket_map[stack.pop()] == char:   # 出栈
continue
else:
label = False
break
if not stack.is_empty():
label = False
print(stack)
return label
if __name__ == '__main__':
st = "[([{}][])]"
print(match_bracket(st))

3.4 用栈实现浏览器的前进后退功能

浏览器的前进、后退功能,大家经常使用。当你依次访问完一串页面 a-b-c 之后,点击浏览器的后退按钮,就可以查看之前浏览过的页面 b 和 a。当你后退到页面 a,点击前进按钮,就可以重新查看页面 b 和 c。但是,如果你后退到页面 b 后,点击了新的页面 d,那就无法再通过前进、后退功能查看页面 c 了。
这个功能就可以用栈实现。代码参考

class Browser:
def __init__(self):
self.forward_stack = Stack()  # 前进栈: 保存后退的页面
self.backward_stack = Stack()  # 后退栈: 保存新开页面或者前进的页面

def open(self, url):
self.backward_stack.push(url)  # 新页面加入到后退栈
print(f"Open new url: {url}")
self.forward_stack.clear()  # 清空前进栈

def back(self):
if not self.backward_stack.is_empty():
top = self.backward_stack.pop()  # 从后退栈中弹出
self.forward_stack.push(top)
print(f"Back to {top}")
else:
print("Cannot backward")

def forward(self):
if not self.forward_stack.is_empty():
top = self.forward_stack.pop()  # 从前进栈中弹出
self.backward_stack.push(top)
print(f"Forward to {top}")
else:
print("Cannot forward")

if __name__ == '__main__':
browser = Browser()
browser.open('a')
browser.open('b')
browser.open('c')
browser.back()
browser.back()
browser.open('d')
browser.back()
browser.back()
browser.forward()
liuchunming033 博客专家 原创文章 192获赞 323访问量 178万+ 关注 他的留言板
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐