1.什么是栈
- 先进后出,后进先出,这就是典型的栈结构
- 从栈的操作特性来看,是一种 操作受限 的线性表,只允许在端插入和删除数据
2.为什么需要栈
- 栈是一种操作受限的数据结构,其操作特性用数组和链表均可实现
- 但任何数据结构都是对特定应用场景的抽象,数组和链表虽然使用起来更加灵活,但却暴露了几乎所有的操作,难免会引发错误操作的风险
- 所以,当某个数据集合只涉及在某端插入和删除数据,且满足后进者先出,先进者后出的操作特性时,这个时候 栈 这种数据结构就能够排得上用场
3.如何自己实现栈
// 这里用到了泛型
public class MyStack<T> {
//判断栈是否满
private boolean isFull() {
}
//入栈
public void push(T value) {
}
//判断栈是否为空
private boolean isEmpty() {
}
//出栈
public T pop() {
}
//获得栈顶元素,但不出栈
public T peek() {
}
}
数组实现
- 时间复杂度分析:数组实现(自动扩容)符合大多数情况是O(1)级别复杂度,个别情况是O(n)级别复杂度,比如在满的时候自动扩容时,会进行完整数据的拷贝
- 空间复杂度分析:在入栈和出栈的过程中,只需要一两个临时变量存储空间,所以O(1)级别。我们说空间复杂度的时候,是指除了原本的数据存储空间外,算法运行还需要额外的存储空间
public class MyStack<T> {
public T[] elem;
public int top;//下标
public MyStack() {
this.elem = (T[]) new Object[10];
this.top = 0;
}
private boolean isFull() {
return this.top == this.elem.length;
}
public void push(T value) {
if (isFull()) {
this.elem = Arrays.copyOf(this.elem,this.elem.length*2);//二倍扩容
System.out.println("扩容成功");
}
this.elem[this.top] = value;
this.top++;
}
private boolean isEmpty() {
return this.top == 0;
}
//出栈
public T pop() {
if (isEmpty()) {
return null;
}
T tmp = this.elem[this.top-1];
this.top--;
return tmp;
}
//获得栈顶元素,但不出栈
public T peek() {
if (isEmpty()) {
return null;
}
return this.elem[this.top-1];
}
}
链表实现
- 时间复杂度分析:入栈和出栈的时间复杂度均为O(1)级别,因为只需更改单个节点的 next
- 空间复杂度分析:在入栈和出栈的过程中,只需要一两个临时变量存储空间,所以O(1)级别。我们说空间复杂度的时候,是指除了原本的数据存储空间外,算法运行还需要额外的存储空间
class MyListStack<T>{
//定义一个内部类 就可以直接使用类型参数
class Node{
T data;
Node next;
}
private Node first;
private int size;
//构造方法
public MyListStack() {
}
//入栈
public void push(T data) {
Node oldfirst = first;
first = new Node();
first.data = data;
first.next = oldfirst;
size++;
}
//出栈
public boolean pop() {
T data = first.data;
first = first.next;
size--;
return true;
}
//获得栈顶元素
public T peek(){
return first.data;
}
public int size() {
return size;
}
}
4.栈的应用
栈在函数中的应用
- 操作系统给每个线程分配了一块独立的内存空间,这块内存被组织成“栈”这种结构,用来存储函数调用时的临时变量。每进入一个函数,就会将其中的临时变量作为栈帧入栈,当被调用函数执行完成,返回之后,将这个函数对应的栈帧出栈
栈在表达式求值中的应用(34 + 13*9 + 44 -12/3)
- 利用两个栈,其中一个用来保存操作数,另一个用来保存运算符,我们从左向右遍历表达式,当遇到数字,我们就直接入操作数栈;当遇到运算符,就与运算符栈的栈顶元素进行比较,若比运算符栈顶元素优先级高,就将当前运算符入栈,若比运算符栈顶元素的优先级低或者相同,从运算符栈中取出栈顶运算符,从操作数栈顶取出2个操作数,然后进行计算,把计算完的结果入操作数栈,继续比较
栈在括号匹配中的应用({ } { [ ( ) ] ( ) })
- 用栈保存为匹配的左括号,从左到右一次扫描字符串,当扫描到左括号时,则将其入栈;当扫描到右括号时,从栈顶取出一个左括号,如果能匹配上,则继续扫描剩下的字符串。如果扫描过程中,遇到不能配对的右括号,或者栈中没有数据,则说明为非法格式
如何实现浏览器的前进后退功能
- 我们使用两个栈X和Y,我们把首次浏览的页面依次压如栈X,当点击后退按钮时,再依次从栈X中出栈,并将出栈的数据一次放入Y栈。当点击前进按钮时,我们依次从栈Y中取出数据,放入栈X中。当栈X中没有数据时,说明没有页面可以继续后退浏览了。当Y栈没有数据,那就说明没有页面可以点击前进浏览了