【Java/数据结构】栈(Stack)(图文版)

发布于:2025-04-18 ⋅ 阅读:(27) ⋅ 点赞:(0)

本博客将带大家一起学习基本数据结构之一——栈(Stack),虽然Java当中的Stack集合已经被Deque(双端队列)替代了,但是他的基本思想和实现还是有必要学习的。

一.初识栈

1.基本概念

        堆栈又名栈(stack),它是一种运算受限的线性表。限定仅在表尾进行插入和删除操作的线性表。这一端被称为栈顶,相对地,把另一端称为栈底。

        向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;

        从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。

简单来讲,栈就是一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。

如下是它在Java集合框架中的位置:

ps:由于Vector设计过时,所以继承自他的Stack也被替代了。

2.特性

LIFO:即Last In First Out,后进先出原则。

类似于坐电梯,先走进去的人后出来;或者上子弹,最先进弹夹的子弹最后打出。

3.核心操作

入栈(push)、出栈(pop)、查看栈顶(peek)

二.栈的模拟实现

老规矩,先看源码:

我们不难发现栈的实现相当简单,底层就是一个数组,同时Stack类也相当简单,仅仅只有140余行。

接下来我们不考虑泛型与io,存储的数据默认为int,来实现一个简单的栈,以理解栈的底层原理。

1.经典实现

最经典的就是基于数组的实现:

(1)基本结构

public class MyStack {
    private int[] elements; // 存储元素的数组
    private int top;        // 栈顶指针(初始为-1)
    private static final int DEFAULT_CAPACITY = 10;

    // 构造方法
    public MyStack() {
        this(DEFAULT_CAPACITY);
    }

    public MyStack(int initialCapacity) {
        if (initialCapacity <= 0) {
            throw new IllegalArgumentException("容量必须为正数");
        }
        this.elements = new int[initialCapacity];
        top = -1;
    }
    
    ......

说明:

由于是基于数组实现的,所以不得不考虑动态扩容机制。

我们提供2种构造方法,一种指定初始容量,另一种不指定,使用默认容量,即DEFAULT_CAPACITY这一静态变量。

我们提供一个指针来指示栈顶,即top。

(2)动态扩容

// 动态扩容
private void resize(int newCapacity) {
    int[] newArray = new int[newCapacity];
    System.arraycopy(elements, 0, newArray, 0, top + 1);
    elements = newArray;
}

说明:

System.arraycopy(elements, 0, newArray, 0, top + 1);

复制数组参数(原数组,复制起始位置,复制目的地,目的地起始位置,复制长度)

(3)入栈(push)

// 入栈(带动态扩容)
public void push(int value) {
    // 检查是否需要扩容
    if (top == elements.length - 1) {
        resize(2 * elements.length);
    }
    elements[++top] = value;
}

(4)出栈(pop)

// 出栈
public int pop() {
    if (isEmpty()) {
        throw new IllegalStateException("栈为空");
    }
    return elements[top--];
}

(5)查看栈顶(peek)

// 查看栈顶元素
public int peek() {
    if (isEmpty()) {
        throw new IllegalStateException("栈为空");
    }
    return elements[top];
}

(6)其他

// 判断是否为空
public boolean isEmpty() {
    return top == -1;
}

// 获取元素数量
public int size() {
    return top + 1;
}

(7)完整实现与测试

public class MyStack {
    private int[] elements; // 存储元素的数组
    private int top;        // 栈顶指针(初始为-1)
    private static final int DEFAULT_CAPACITY = 10;

    // 构造方法
    public MyStack() {
        this(DEFAULT_CAPACITY);
    }

    public MyStack(int initialCapacity) {
        if (initialCapacity <= 0) {
            throw new IllegalArgumentException("容量必须为正数");
        }
        elements = new int[initialCapacity];
        top = -1;
    }

    // 入栈(带动态扩容)
    public void push(int value) {
        // 检查是否需要扩容
        if (top == elements.length - 1) {
            resize(2 * elements.length);
        }
        elements[++top] = value;
    }

    // 出栈
    public int pop() {
        if (isEmpty()) {
            throw new IllegalStateException("栈为空");
        }
        return elements[top--];
    }

    // 查看栈顶元素
    public int peek() {
        if (isEmpty()) {
            throw new IllegalStateException("栈为空");
        }
        return elements[top];
    }

    // 判断是否为空
    public boolean isEmpty() {
        return top == -1;
    }

    // 获取元素数量
    public int size() {
        return top + 1;
    }

    // 动态扩容
    private void resize(int newCapacity) {
        int[] newArray = new int[newCapacity];
        System.arraycopy(elements, 0, newArray, 0, top + 1);
        elements = newArray;
    }

    // 测试代码
    public static void main(String[] args) {
        MyStack stack = new MyStack(3);

        // 测试入栈和扩容
        stack.push(10);
        stack.push(20);
        stack.push(30);
        stack.push(40);  // 触发扩容到6

        System.out.println("栈顶元素: " + stack.peek()); // 输出40
        System.out.println("元素数量: " + stack.size()); // 输出4

        // 测试出栈
        System.out.println("出栈: " + stack.pop()); // 40
        System.out.println("出栈: " + stack.pop()); // 30
        System.out.println("剩余元素数量: " + stack.size()); // 2
    }
}

2.链表实现

除了使用数组存储数据,使用链表也是可以的,并且使用链表不用考虑动态扩容。

(1)基本结构

public class MyLinkedStack {
    private static class Node {
        int data;
        Node next;

        Node(int data) {
            this.data = data;
        }
    }

    private Node top;   // 栈顶节点
    private int size;   // 元素数量

    ......

(2)入栈(push)

public void push(int value) {
    Node newNode = new Node(value);
    newNode.next = top; // 新节点指向原栈顶
    top = newNode;     // 更新栈顶
    size++;
}

(3)出栈(pop)

public int pop() {
    if (isEmpty()) {
        throw new IllegalStateException("栈为空");
    }
    int value = top.data;
    top = top.next; // 移动栈顶指针
    size--;
    return value;
}

特别注意栈为空时会报错,所以要检查栈是否为空。

(4)查看栈顶(peek)

public int peek() {
    if (isEmpty()) {
        throw new IllegalStateException("栈为空");
    }
    return top.data;
}

(5)其他

public boolean isEmpty() {
    return top == null;
}

public int size() {
    return size;
}

(6)完整实现与测试

public class MyLinkedStack {
    private static class Node {
        int data;
        Node next;

        Node(int data) {
            this.data = data;
        }
    }

    private Node top;   // 栈顶节点
    private int size;   // 元素数量

    public void push(int value) {
        Node newNode = new Node(value);
        newNode.next = top; // 新节点指向原栈顶
        top = newNode;     // 更新栈顶
        size++;
    }

    public int pop() {
        if (isEmpty()) {
            throw new IllegalStateException("栈为空");
        }
        int value = top.data;
        top = top.next; // 移动栈顶指针
        size--;
        return value;
    }

    public int peek() {
        if (isEmpty()) {
            throw new IllegalStateException("栈为空");
        }
        return top.data;
    }

    public boolean isEmpty() {
        return top == null;
    }

    public int size() {
        return size;
    }

    // 测试代码
    public static void main(String[] args) {
        MyLinkedStack stack = new MyLinkedStack();
        stack.push(100);
        stack.push(200);
        System.out.println(stack.pop()); // 200
        System.out.println(stack.peek()); // 100
    }
}

三.栈的使用

请见以下代码:

import java.util.Stack;

public class StackDemo {
    public static void main(String[] args) {
        // 1. 创建栈对象
        Stack<Integer> stack = new Stack<>();

        // 2. 压栈操作(push)
        System.out.println("----- 压栈操作 -----");
        stack.push(10);
        stack.push(20);
        stack.push(30);
        System.out.println("当前栈内容: " + stack);  // 输出: [10, 20, 30]

        // 3. 查看栈顶(peek)
        System.out.println("\n----- 查看栈顶 -----");
        System.out.println("栈顶元素: " + stack.peek()); // 输出: 30
        System.out.println("查看后栈内容: " + stack);    // 保持原样

        // 4. 弹栈操作(pop)
        System.out.println("\n----- 弹栈操作 -----");
        System.out.println("弹出元素: " + stack.pop());  // 输出: 30
        System.out.println("弹出后栈内容: " + stack);    // 输出: [10, 20]

        // 5. 检查空栈(empty)
        System.out.println("\n----- 检查空栈 -----");
        System.out.println("栈是否为空? " + stack.empty()); // 输出: false
        
        // 6. 搜索元素(search)
        System.out.println("\n----- 搜索元素 -----");
        int target = 20;
        int position = stack.search(target);
        System.out.println("元素 " + target + " 的位置: " + position);  // 输出: 1(栈顶为1)

        // 7. 清空栈
        System.out.println("\n----- 清空栈 -----");
        while (!stack.empty()) {
            System.out.println("弹出: " + stack.pop());
        }
        System.out.println("清空后栈是否为空? " + stack.empty()); // 输出: true
    }
}

更多信息请见官方文档说明:

Stack (Java Platform SE 8 )

四.栈的典型应用

1.括号匹配算法

该算法能自动检验输入的字符串中括号是否正确匹配:

import java.util.Stack;

public class BracketMatcher {
    public static boolean isValid(String s) {
        Stack<Character> stack = new Stack<>();

        for (char c : s.toCharArray()) {
            // 遇到左括号时,将对应的右括号压入栈
            switch (c) {
                case '(':
                    stack.push(')');
                    break;
                case '[':
                    stack.push(']');
                    break;
                case '{':
                    stack.push('}');
                    break;
                default:
                    // 遇到右括号时,检查栈顶是否匹配
                    if (stack.isEmpty() || stack.pop() != c) {
                        return false;
                    }
            }
        }
        // 最终栈必须为空才表示完全匹配
        return stack.isEmpty();
    }
}

原理请见LeetCode:20. 有效的括号 - 力扣(LeetCode)

2.逆波兰表达式(计算机的算数运算)

import java.util.Stack;

public class ReversePolishNotation {
    public static int evalRPN(String[] tokens) {
        Stack<Integer> stack = new Stack<>();
        
        for (String token : tokens) {
            // 遇到运算符时进行计算
            if (isOperator(token)) {
                int b = stack.pop();
                int a = stack.pop();
                stack.push(calculate(a, b, token));
            } 
            // 遇到数字时压栈
            else {
                stack.push(Integer.parseInt(token));
            }
        }
        return stack.pop();
    }

    // 判断是否是运算符
    private static boolean isOperator(String s) {
        return s.equals("+") || s.equals("-") || s.equals("*") || s.equals("/");
    }

    // 执行运算(注意操作数顺序)
    private static int calculate(int a, int b, String op) {
        switch (op) {
            case "+": return a + b;
            case "-": return a - b;
            case "*": return a * b;
            case "/": return a / b;  // 题目通常要求整数除法向零取整
            default: throw new IllegalArgumentException("非法运算符");
        }
    }

    public static void main(String[] args) {
        // 测试案例
        String[][] testCases = {
            {"2","1","+","3","*"},      // (2+1)*3=9
            {"4","13","5","/","+"},      // 4+(13/5)=6
            {"10","6","9","3","+","-11","*","/","*","17","+","5","+"} // 10*(6/((9+3)*-11))+17+5
        };

        for (String[] testCase : testCases) {
            System.out.println("表达式: " + String.join(" ", testCase));
            System.out.println("结果: " + evalRPN(testCase) + "\n");
        }
    }
}

详情请见:150. 逆波兰表达式求值 - 力扣(LeetCode)

结语

关于用Deque替代Stack的事,我们在队列Quque中会讲到,敬请期待!

如果有帮助不妨点个赞吧,下期就是Quque了!( ´∀`)つt[ ]


网站公告

今日签到

点亮在社区的每一天
去签到