本系列是学习 数据结构与算法之美 08 栈:如何实现浏览器的前进和后退功能
栈:FILO;是一种操作受限的线性表,只允许一端插入和删除数据;后进先出、先进后出;
一、如何实现一个 栈
// 基于数组实现的顺序栈
public class ArrayStack {
private String[] items; // 数组
private int count; // 栈中元素个数
private int n; // 栈的大小
// 初始化数组,申请一个大小为 n 的数组空间
public ArrayStack(int n) {
this.items = new String[n];
this.n = n;
this.count = 0;
}
// 入栈操作
public boolean push(String item) {
// 数组空间不够了,直接返回 false,入栈失败。
if (count == n) return false;
// 将 item 放到下标为 count 的位置,并且 count 加一
items[count] = item;
++count;
return true;
}
// 出栈操作
public String pop() {
// 栈为空,则直接返回 null
if (count == 0) return null;
// 返回下标为 count-1 的数组元素,并且栈中元素个数 count 减一
String tmp = items[count-1];
--count;
return tmp;
}
}
空间复杂度:O(1),不管是顺序栈还是链式栈,存储数据只需要大小为n的数组;在入栈和出栈过程中,只需要一两个临时变量存储空间;
时间复杂度:O(1),入栈、出栈只涉及栈顶个别数据的操作。
/**
* 基于链表实现的栈。
*
* Author: Zheng
*/
public class StackBasedLinkedList {
private Node top = null;
public void push(int value) {
Node newNode = new Node(value, null);
// 判断是否栈空
if (top == null) {
top = newNode;
} else {
newNode.next = top;
top = newNode;
}
}
/**
* 我用-1表示栈中没有数据。
*/
public int pop() {
if (top == null) return -1;
int value = top.data;
top = top.next;
return value;
}
public void printAll() {
Node p = top;
while (p != null) {
System.out.print(p.data + " ");
p = p.next;
}
System.out.println();
}
private static class Node {
private int data;
private Node next;
public Node(int data, Node next) {
this.data = data;
this.next = next;
}
public int getData() {
return data;
}
}
}
二、支持动态扩容的顺序栈
需要底层依赖一个动态扩容的数组即可。当栈满后,就申请一个更大的数组,将原来的数据搬移到新数组中。
出栈不变 时间复杂度仍然是O(1),入栈操作涉及内存的重新申请和数据的搬移,最好情况的时间复杂度是O(1),最坏情况时间复杂度是O(n),平均时间复杂度使用 摊还分析法 分析:
K次入栈操作,总共涉及K个数据的搬移,以及K次simple-push操作。将K个数据搬移均摊到K次入栈操作,那每个入栈操作只需要一个数据搬移和一个simple-push操作。以此类推,入栈操作的均摊时间复杂度就为O(1).
均摊时间复杂度一般都等于最好情况时间复杂度。因为大部分情况下,入栈操作的时间复杂度都是O(1),只有在个别时刻才会退化为O(n),所以把耗时多的入栈操作的时间均摊到其他入栈操作上,平均情况下的耗时就接近O(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;
}
四:栈在表达式求值中的应用
如算术表达式 34+13*9+44-12/3
编译器就是通过两个栈来实现的。其中一个保存操作数的栈,另一个保存运算符的栈。
从左向右遍历表达式,当遇到数字,就直接压入操作数栈;当遇到运算符,就与运算符栈的栈顶元素进行比较。
如果比运算符栈顶元素的优先级高,就将当前运算符压入栈;如果优先级低或相同,从运算符栈中取栈顶运算符,从操作数栈的栈顶取2个操作数,然后进行运算,再把计算完的结果压入操作数栈,继续比较。
这个部分的图推荐看《码农翻身》里的机器手来回压栈出栈的示意图更形象
五、栈在括号匹配中的应用
我们假设表达式中只包含三种括号,圆括号 ()、方括号 [] 和花括号{},并且它们可以任意嵌套。比如,{[{}]}或 [{()}([])] 等都为合法格式,而{[}()] 或 [({)] 为不合法的格式。那现有一个包含三种括号的表达式字符串,如何检查它是否合法呢?
用栈来保存未匹配的左括号,从左到右以此扫描字符串。当扫描到左括号时,则将其压入栈中;当扫描到右括号时,从栈顶取出一个左括号。如果能匹配,则仅需扫描剩下的字符串。如果扫描的过程中,遇到不能配对的右括号,或者栈中没有数据,则说明为非法格式。
当所有的括号都扫描完成之后,如果栈为空,则说明字符串为合法格式;否则,说明有未匹配的左括号,为非法格式。
六、如何实现浏览器的前进和后退功能?
用两个栈就可以完美解决
X\Y,首次浏览的页面依次压入栈X,当点击后退按钮时,再依次从栈X中出栈,并将出栈的数据依次放入栈Y。当点击前进按钮时,依次从栈Y中取出数据,放入栈X中。当栈X中没有数据时,说明么有页面可以继续后退浏览了。当栈Y中没有数据,说明灭有页面可以点击前进按钮浏览了。代码是这样
https://github.com/wangzheng0822/algo/blob/master/java/08_stack/SampleBrowser.java
package algo.lesson08;
/**
* 使用前后栈实现浏览器的前进后退。
*
* @author chinalwb
*/
public class SampleBrowser {
public static void main(String[] args) {
SampleBrowser browser = new SampleBrowser();
browser.open("http://www.baidu.com");
browser.open("http://news.baidu.com/");
browser.open("http://news.baidu.com/ent");
browser.goBack();
browser.goBack();
browser.goForward();
browser.open("http://www.qq.com");
browser.goForward();
browser.goBack();
browser.goForward();
browser.goBack();
browser.goBack();
browser.goBack();
browser.goBack();
browser.checkCurrentPage();
}
private String currentPage;
private LinkedListBasedStack backStack;
private LinkedListBasedStack forwardStack;
public SampleBrowser() {
this.backStack = new LinkedListBasedStack();
this.forwardStack = new LinkedListBasedStack();
}
public void open(String url) {
if (this.currentPage != null) {
this.backStack.push(this.currentPage);
this.forwardStack.clear();
}
showUrl(url, "Open");
}
public boolean canGoBack() {
return this.backStack.size() > 0;
}
public boolean canGoForward() {
return this.forwardStack.size() > 0;
}
public String goBack() {
if (this.canGoBack()) {
this.forwardStack.push(this.currentPage);
String backUrl = this.backStack.pop();
showUrl(backUrl, "Back");
return backUrl;
}
System.out.println("* Cannot go back, no pages behind.");
return null;
}
public String goForward() {
if (this.canGoForward()) {
this.backStack.push(this.currentPage);
String forwardUrl = this.forwardStack.pop();
showUrl(forwardUrl, "Foward");
return forwardUrl;
}
System.out.println("** Cannot go forward, no pages ahead.");
return null;
}
public void showUrl(String url, String prefix) {
this.currentPage = url;
System.out.println(prefix + " page == " + url);
}
public void checkCurrentPage() {
System.out.println("Current page is: " + this.currentPage);
}
/**
* A LinkedList based Stack implementation.
*/
public static class LinkedListBasedStack {
// public static void main(String[] args) {
// LinkedListBasedStack stack = new LinkedListBasedStack();
// stack.push("A");
// stack.push("B");
// stack.push("C");
// stack.pop();
// stack.push("D");
// stack.push("E");
// stack.pop();
// stack.push("F");
// stack.print();
//
//// String data = stack.getTopData();
//// System.out.println("Top data == " + data);
// }
private int size;
private Node top;
static Node createNode(String data, Node next) {
return new Node(data, next);
}
public void clear() {
this.top = null;
this.size = 0;
}
public void push(String data) {
Node node = createNode(data, this.top);
this.top = node;
this.size++;
}
public String pop() {
Node popNode = this.top;
if (popNode == null) {
System.out.println("Stack is empty.");
return null;
}
this.top = popNode.next;
if (this.size > 0) {
this.size--;
}
return popNode.data;
}
public String getTopData() {
if (this.top == null) {
return null;
}
return this.top.data;
}
public int size() {
return this.size;
}
public void print() {
System.out.println("Print stack:");
Node currentNode = this.top;
while (currentNode != null) {
String data = currentNode.getData();
System.out.print(data + "\t");
currentNode = currentNode.next;
}
System.out.println();
}
public static class Node {
private String data;
private Node next;
public Node(String data) {
this(data, null);
}
public Node(String data, Node next) {
this.data = data;
this.next = next;
}
public void setData(String data) {
this.data = data;
}
public String getData() {
return this.data;
}
public void setNext(Node next) {
this.next = next;
}
public Node getNext() {
return this.next;
}
}
}
}