测试开发基础之算法(5):栈的基础操作及应用
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万+ 关注 他的留言板
- 测试开发基础之算法(10):Hash算法的常见应用
- 测试开发基础之算法(8):二分查找的6种常用应用场景
- 测试开发基础之算法(13):堆、堆排序及三种应用(优先级队列、Top k、中位数)
- 测试开发人员需了解的基础算法系列(一)
- 测试开发基础之算法(12):支持动态数据集合快速插入、删除、查找的二叉查找树
- 测试开发基础之算法(7): 如何编写递归代码
- 一步一步教你使用AgileEAS.NET基础类库进行应用开发-基础篇-演示ORM的基本操作
- Android开发之基础------------测试相关、sdcard操作、SharedPreferences存取
- 一步一步教你使用AgileEAS.NET基础类库进行应用开发-基础篇-演示ORM的基本操作
- 测试开发基础之算法(15):字符串匹配算法——BF算法和RK算法
- 测试开发基础之算法(11):二叉树的三种遍历算法及典型题解
- 一步一步教你使用AgileEAS.NET基础类库进行应用开发-WinForm应用篇-复杂业务的实现(商品入库)-附案例操作视
- 嵌入式内核及驱动开发-03字符设备驱动基础(申请设备号,创建设备节点,实现文件操作对象,应用控制驱动,copy_to_user,ioremapled,灯驱动)
- JAVA操作数据库方式与设计模式应用-Java基础-Java-编程开发
- 一步一步教你使用AgileEAS.NET基础类库进行应用开发-WinForm应用篇-复杂业务的实现(商品入库)-附案例操作视频
- 服务器控件开发基础----应用设计期Attribute
- Java Web应用快速开发平台OpenJWeb(v1.6)增删改查页面生成器操作手册
- 对项目开发过程中几种测试类型的理解和实际操作
- 使用 Rational Functional Tester 测试应用软件的操作响应速度
- openjweb1.8 java web应用快速开发平台操作手册