从栈的操作特性上来看,栈是一种“操作受限”的线性表,只允许在一端插入和删除数据,满足后进先出、先进后出的特性。
- 数组实现的栈:顺序栈
- 链表实现的栈:链式栈
// 基于数组实现的顺序栈
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;
}
}
下面是让chatgpt写出的分别基于Java和C++的链式栈
在下面的代码中,我们使用内部类 Node 表示链式栈的节点,节点包含一个数据域 data 和一个指向下一个节点的指针 next。链式栈本身包含一个指向栈顶节点的指针 top 和一个表示栈元素个数的变量 size。根据链式栈的特点,入栈操作在链表头插入新节点,出栈操作从链表头删除节点。
// Java基于链表实现的链式栈
public class LinkedListStack<T> {
// 定义链式栈的节点类
private static class Node<T> {
private T data; // 节点存储的数据
private Node<T> next; // 指向下一个节点的指针
// 构造方法
public Node(T data, Node<T> next) {
this.data = data;
this.next = next;
}
}
private Node<T> top; // 栈顶节点
private int size; // 栈的元素个数
// 构造方法
public LinkedListStack() {
top = null;
size = 0;
}
// 判断栈是否为空
public boolean isEmpty() {
return top == null;
}
// 获取栈的大小
public int size() {
return size;
}
// 入栈操作
public void push(T data) {
Node<T> newNode = new Node<>(data, top); // 创建新节点,指向当前栈顶节点
top = newNode; // 更新栈顶节点为新节点
size++; // 元素个数加1
}
// 出栈操作
public T pop() {
if (isEmpty()) {
throw new RuntimeException("Stack is empty!");
}
T data = top.data; // 获取栈顶节点的数据
top = top.next; // 更新栈顶节点为下一个节点
size--; // 元素个数减1
return data;
}
// 获取栈顶元素
public T peek() {
if (isEmpty()) {
throw new RuntimeException("Stack is empty!");
}
return top.data;
}
// 打印栈中的元素
public void printStack() {
Node<T> curr = top;
System.out.print("Stack: ");
while (curr != null) {
System.out.print(curr.data + " ");
curr = curr.next;
}
System.out.println();
}
}
// C++基于链表实现的链式栈
#include <iostream>
#include <stdexcept> // 包含 std::out_of_range 异常的定义
template <typename T>
class LinkedStack {
private:
// 定义内部的节点结构体
struct Node {
T data; // 存储数据的字段
Node* next; // 存储下一个节点的指针
Node(T data) : data(data), next(nullptr) {} // 节点构造函数,初始化数据和指针
};
Node* top; // 栈顶指针
int size; // 栈的元素个数
public:
LinkedStack() : top(nullptr), size(0) {} // 栈的构造函数,初始化栈顶指针为 nullptr,元素个数为 0
// 将元素压入栈中
void push(T data) {
Node* newNode = new Node(data); // 创建一个新节点
newNode->next = top; // 新节点指向原先的栈顶节点
top = newNode; // 栈顶指针指向新节点
size++; // 栈的元素个数加一
}
// 弹出并返回栈顶元素
T pop() {
if (isEmpty()) { // 如果栈为空,则抛出一个 std::out_of_range 异常
throw std::out_of_range("Stack is empty.");
}
Node* poppedNode = top; // 获取栈顶节点
T poppedData = poppedNode->data; // 获取栈顶节点的数据
top = top->next; // 栈顶指针指向下一个节点
delete poppedNode; // 释放栈顶节点的内存空间
size--; // 栈的元素个数减一
return poppedData; // 返回栈顶节点的数据
}
// 返回但不弹出栈顶元素
T peek() {
if (isEmpty()) { // 如果栈为空,则抛出一个 std::out_of_range 异常
throw std::out_of_range("Stack is empty.");
}
return top->data; // 返回栈顶节点的数据
}
// 检查栈是否为空
bool isEmpty() {
return size == 0;
}
// 返回栈的元素个数
int getSize() {
return size;
}
};
入栈的时间空间复杂度
- 正常来讲是O(1),时间复杂度和空间复杂度都是。
- 支持动态扩容的栈时间复杂度,最好情况时间复杂度是O(1),最坏情况时间复杂度是O(n),平均时间复杂度为O(1),按照均摊方法分析,n个正常入栈操作O(1)之后,存在1个特殊的扩容操作,涉及到内存的申请与搬迁,搬迁过程显然为O(n),将1个特殊的操作均摊至n个正常入栈操作,因此均摊之后为O(1)。
- 空间复杂度都是O(1),不涉及到内存的申请和搬迁。
栈的应用
函数调用
一个main主函数中嵌套一个add函数:
为什么函数调用要用“栈”来保存临时变量呢?用其他数据结构不行吗?
- 其实,我们不一定非要用栈来保存临时变量,只不过如果这个函数调用符合后进先出的特性,用栈这种数据结构来实现,是最顺理成章的选择。
- 函数调用之所以用栈,是因为函数调用中经常嵌套,栗子:A调用B,B又调用C,那么就需要先把C执行完,结果赋值给B中的临时变量,B的执行结果再赋值给A的临时变量,嵌套越深的函数越需要被先执行,这样刚好符合栈的特点,因此每次遇到函数调用,只需要压栈,最后依次从栈顶弹出依次执行即可
- 函数调用和返回符合后进先出原则,而局部变量的生命周期应该和函数一致,所以用栈保存局部变量是合适的,函数出栈的时候同时销毁局部变量
编译器表达式求值
需要两个栈X,Y。从左至右依次遍历表达式,例如3+5*8-6
- 遇见数字则压入操作数栈X,遇见运算符则压入运算符栈;
- 遇见的运算符优先级高于栈顶的运算符则继续压入,低于或等于栈顶运算符,则取出该栈顶运算符,并另取出X栈两操作数,计算完毕将结果压入操作数栈X,再继续比较运算符,直至计算完成。
- 清空栈
括号匹配
栈中保存未匹配的左括号。从左到右依次扫描字符串。当扫描到左括号时,则将其压入栈中;当扫描到右括号时,从栈顶取出一个左括号。如果能够匹配,则继续扫描剩下的字符串。如果扫描的过程中,遇到不能配对的右括号,或者栈中没有数据,则说明为非法格式。
浏览器的前进、后退
- 首次浏览,压入栈X
- 后退:依次出栈X,入栈Y
- 前进:出栈Y,入栈X
- 回退后再打开新页面:新页面入栈X,Y中无法前进后退,清空Y
我们都知道,JVM 内存管理中有个“堆栈”的概念。栈内存用来存储局部变量和方法调用,堆内存用来存储 Java 中的对象。那 JVM 里面的“栈”跟我们这里说的“栈”是不是一回事呢?如果不是,那它为什么又叫作“栈”呢?
尚未解答