咱们能够用栈实现浏览器的前进后退功能
那么栈又是什么?
栈就像一叠盘子,从下一个一个往上放。
后进先出,先进后出,就是典型栈的结构。
从操做特性上来看,栈是十分受限制的一种数据结构,只有一端可以操做,但由于只暴露了一端的操做接口,便不容易出错,更可控。
所以当数据集合知足先进后出,后进先出的特色时,就应该选择栈。
顺序栈:用数组实现的栈
链式栈:用链表实现的栈
Java实现的顺序栈:
1 // 基于数组实现的顺序栈
2 public class ArrayStack {
3 private String[] items; // 数组
4 private int count; // 栈中元素个数
5 private int n; // 栈的大小
6
7 // 初始化数组,申请一个大小为 n 的数组空间
8 public ArrayStack(int n) {
9 this.items = new String[n];
10 this.n = n;
11 this.count = 0;
12 }
13
14 // 入栈操做
15 public boolean push(String item) {
16 // 数组空间不够了,直接返回 false,入栈失败。
17 if (count == n) return false;
18 // 将 item 放到下标为 count 的位置,而且 count 加一
19 items[count] = item;
20 ++count;
21 return true;
22 }
23
24 // 出栈操做
25 public String pop() {
26 // 栈为空,则直接返回 null
27 if (count == 0) return null;
28 // 返回下标为 count-1 的数组元素,而且栈中元素个数 count 减一
29 String tmp = items[count-1];
30 --count;
31 return tmp;
32 }
33 }
无论顺序栈仍是链表栈,存储数据是只须要一个大小为n的数组就够了,而出栈入栈的操做,只须要一两个临时变量空间,所以
空间复杂度为O(1)。
这里要注意,
空间复杂度并非指 本来存储数据的空间,而是指算法执行时还须要额外的存储空间。
出栈入栈的时间复杂度均为O(1)。
支持动态扩容的顺序栈
前面讲的数组若要实现动态扩容即是将数组数据复制到更大的内存去,对于动态扩容的顺序栈,咱们只须要底层是一个动态扩容的数组。
对于动态扩容的数组平时开发用到很少,但咱们主要对其进行复杂度分析。
它出栈复杂度都是O(1)。
对于入栈(push),最好状况时间复杂度是O(1),最坏时间复杂度是O(n)。那么它的均摊时间复杂度是多少呢?
便于分析假设
- 栈空间不够时,从新申请原来两倍大小的空间。
- 没有出栈操做
- 不涉及内存搬移的入栈操做为simple_push,时间复杂度为O(1)。
假设当前栈大小为K,若栈已满,push须要申请两倍内存,并进行K个数据搬移,再进行push,操做复杂度为O(K)。但有2K的空间后,接下来K-1次push都为simple-push,时间复杂度为O(1)。若是还有push,再次循环下去。
讲须要数据搬移的push均摊下到k-1次的simple的入栈,即可知push的均摊时间复杂度为O(1)。
栈在函数调用的应用
操做系统给每一个线程分配了一块独立的内存空间,这个内存会被组织成”栈”,用来存放临时变量。
1 int main() {
2 int a = 1;
3 int ret = 0;
4 int res = 0;
5 ret = add(3, 5);
6 res = a + ret;
7 printf("%d", res);
8 reuturn 0;
9 }
10
11 int add(int x, int y) {
12 int sum = 0;
13 sum = x + y;
14 return sum;
15 }
16
上面代码变量会以下图入栈
栈在表达式中的应用
在第5步到第6步时,“-”的优先级小于“*”,便先进行出栈乘法运算,而“-”又小于从左到右的“+”的优先级,加法也出栈运算。
栈在括号匹配中的运用
例如“{[()]}”
从左到右,遇到左括号便入栈,当遇到“)”时,便从栈顶取出一个“(”,看是否匹配,若不匹配则为非法,若匹配,则继续向右扫描。
最后当扫描完全部的括号时,若栈为空,则格式合法。非空,则非法。
用栈实现浏览器前进后退功能
使用连个栈,将首次浏览的页面存入X
后退两次回到a页面
使用前进又回到了b页面
这时候,你浏览了d页面,则c页面会在Y被清空,没法回到c页面了