博主秋招提前批已拿百度、字节跳动、拼多多、顺丰等公司的offer,可加微信:pcwl_Java 一起交流秋招面试经验,可获得博主的秋招简历和复习笔记。
一、栈的基本介绍
1、栈的基本概念
栈是一种限制在一端进行插入和删除操作的线性表数据结构。栈中有两个比较重要的操作:push(压栈:将元素压入栈顶)和pop(弹栈:从栈顶弹出一个元素)。都满足先进后出、后进先出的特点!
从图中可以看出,我们常把栈的上面称为栈顶,栈顶的第一个元素被称为栈顶元素,相对地,把另外一段称为栈底。向一个栈中插入新元素又称为压栈或者进栈,它是把该元素放到栈顶位置上面,使之成为栈顶元素。那么从一个栈中删除一个元素则被称之为弹栈或者出栈,它会把栈顶元素删除,使其下面的相邻元素成为新的栈顶元素。
虽然栈是一种受限的数据结构,其操作特性均可以用数组和链表实现。但是任何数据结构都是对特定应用场景的抽象,数组和链表虽然灵活,但是却暴露了几乎所有的操作,很容易引发操作失误的风险。所以当某个数据集合只涉及到固定端的插入和删除操作,且满足先进后出,后进先出的特点,那么我们就应该优先考虑使用栈这种数据结构。
其实生活中有很多栈的例子,比如:叠放的盘子:我们要放盘子的时候,只能放在最上面。同时,需要拿盘子的时候,也只能拿最上面的。
2、栈的存储结构
栈是一种线性存储结构,所以线性表的顺序存储(数组)和链式存储(链表)都可以实现栈。下面会具体用这两种方式对栈进行实现。
不管是顺序栈还是链式栈,压栈和弹栈都只涉及栈顶个别元素的操作,所以其时间复杂度均为O(1)。
3、栈的应用
栈在实际程序应用中有很多场景,下面列举几个比较常见的:
(1)栈在二进制表达式求值中的应用
在此类场景中,利用两个栈,一个栈用来保存操作数,另外一个用来保存运算符。我们从左到右遍历表达式,当遇到数字,我们就直接压入操作数栈;当遇到运算符,就与运算符栈的栈顶元素进行比较,若比运算符栈的栈顶元素优先级高,就将当前运算符压入运算符栈,若比运算符栈的栈顶元素优先级低或者相等,则从运算符栈取出栈顶运算符,从操作数栈中取出两个操作数,然后进行计算,把计算完的结果再压入操作数栈,然后依次进行比较。
(2)栈在符号号匹配中的应用
比如:括号匹配:{ [()] })、单引号和双引号的匹配
拿括号匹配举例:从左到右依次扫描字符串,当扫描到左括号时,则将其压入栈中;当扫描到右括号时,则取出栈顶元素中的左括号,如果能够匹配上,则继续扫描;如果不匹配,则说明为非法格式。当所有的括号扫描完成之后,如果栈为空,则说明匹配成功,否则为非法格式。
(3)栈在浏览器中网页的前进后退中的应用
使用A和B两个栈,我们把首次浏览的页面依次压入栈A(比如:网页1-->网页2-->网页3),当点击后退按钮时,再依次从栈A中出栈,并按出栈的顺序将它们压入栈B。所有当我们点击前进按钮时,我们就可以依次从栈B中取出数据放入栈A中了。需要说明的是如果在前进后退期间,点开了新的页面,则栈B清空。当栈A中没有数据时,说明页面不可以后退浏览了,相反当栈B中没有元素的时候,页面不可以前进浏览了。
二、栈的实现(Java实现)
1、数组实现栈
public class ArrayStack {
private Object[] array; // 数组
private int count; // 栈中元素的个数
private int n; // 栈的大小
// 初始化数组,申请一个大小为n的数组空间
public ArrayStack(int n){
this.array = new Object[n];
this.count = count;
this.n = n;
}
// 压栈
public boolean push(Object value){
// 如果数组的空间不够了,直接返回false,入栈失败
if(count == n){
return false;
}
// 将value压入栈顶,即下标为count的位置
array[count] = value;
count++;
return true;
}
// 弹栈
public Object pop(){
// 栈为空,则直接返回-1
if(count == 0){
return null;
}
// 返回栈顶的元素,即下标为count-1的数组元素
Object obj = array[count - 1];
count--;
return obj;
}
// 返回弹中最近添加的元素,而不删除它
public Object peek(){
return array[n-1];
}
// 显示栈中的数据
public void display(){
for(int i = 0; i < count; i++){
System.out.print(array[i] + ", ");
}
}
// 栈中元素的个数
public int size(){
return count;
}
// 判断栈中元素是否为空
public boolean isEmpty(){
return count == 0;
}
}
测试代码:
public class ArrayStackTest {
public static void main(String[] args) {
ArrayStack stack = new ArrayStack(5);
boolean isEmpty = stack.isEmpty();
System.out.println(isEmpty);
// 压栈
stack.push("a");
stack.push("b");
stack.push("c");
stack.push("d");
stack.push("e");
// 显示
stack.display();
System.out.println();
// 弹栈
stack.pop();
stack.pop();
stack.pop();
stack.display();
int size = stack.size();
System.out.println(size);
}
}
2、链表实现栈
public class LinkStack {
private class Node{
Object data;
Node next;
}
private Node first; // 定义头结点
private int n = 0; // 链表中元素的个数
public LinkStack(){
}
// 压栈
public void push(Object data){
Node oldFirst = first;
first = new Node();
first.data = data;
first.next = oldFirst; // 将新的头结点的后继指针指向老的头结点
n++;
}
// 弹栈
public Object pop(){
Object data = first.data;
first = first.next; // 将头结点的后一个结点设为头结点
n--;
return data;
}
// 返回栈中最近添加的元素而不是删除它
public Object peek(){
return first.data;
}
// 显示栈中的元素
public void display(){
for(int i = 0; i < n; i++){
Object obj = peek();
System.out.print(obj + ", ");
}
}
// 栈中元素的数量
public int size(){
return n;
}
// 判断栈是否为空
public boolean isEmpty(){
return n == 0;
}
}
测试代码:
public class LinkStackTest {
public static void main(String[] args) {
LinkStack stack = new LinkStack();
boolean isEmpty1 = stack.isEmpty();
System.out.println(isEmpty1);
// 压栈
stack.push("A");
stack.push("B");
stack.push("C");
stack.push("D");
stack.push("E");
// 显示栈中的元素
stack.display();
int size1 = stack.size();
System.out.println(size1);
// 弹栈
stack.pop();
stack.pop();
stack.pop();
// 显示栈中的元素
stack.display();
int size2 = stack.size();
System.out.println(size2);
boolean isEmpty2 = stack.isEmpty();
System.out.println(isEmpty2);
}
}
在极客时间的《数据结构与算法之美》专栏里有个问题,感觉值得mark:
问题:JVM内存管理中的栈(用来存储局部变量和方法调用的)和这里的栈相同吗?
参考及推荐
2、栈和队列
学习不是单打独斗,如果你也是做Java开发,可以加我微信:pcwl_Java,一起分享经验学习!