数据结构——栈与队列的应用:括号匹配、表达式求值、循环队列

栈和队列虽然只是限制了操作位置的线性表,但它们在计算机科学中的应用极广。这篇把 408 中最常考的栈和队列应用讲透。

一、括号匹配

括号匹配是栈最经典的应用之一,编译器检查代码语法时就是用它。

public class BracketMatcher {

    /**
     * 判断括号字符串是否合法
     * 测试:isValid("({[]})") → true
     *       isValid("({[}])") → false
     */
    public boolean isValid(String s) {
        Stack<Character> stack = new Stack<>();

        for (char c : s.toCharArray()) {
            if (c == '(' || c == '[' || c == '{') {
                stack.push(c);
            } else {
                if (stack.isEmpty()) return false;

                char top = stack.pop();
                if (c == ')' && top != '(') return false;
                if (c == ']' && top != '[') return false;
                if (c == '}' && top != '{') return false;
            }
        }

        return stack.isEmpty();
    }
}

原理: 遇到左括号入栈,遇到右括号检查栈顶是否匹配。最终栈为空则合法。

二、表达式求值

1. 中缀、前缀、后缀

中缀表达式(人看的):  A + B × C
前缀表达式(波兰式):  + A × B C
后缀表达式(逆波兰式):A B C × +

计算机更适合处理后缀表达式,因为不需要括号和优先级判断。

2. 中缀转后缀

public class InfixToPostfix {

    /**
     * 中缀表达式转后缀表达式
     * 输入:"A+B*C-(D+E)"
     * 输出:["A", "B", "C", "*", "+", "D", "E", "+", "-"]
     */
    public List<String> convert(String infix) {
        // 运算符优先级
        Map<Character, Integer> prec = new HashMap<>();
        prec.put('+', 1);
        prec.put('-', 1);
        prec.put('*', 2);
        prec.put('/', 2);

        Stack<Character> ops = new Stack<>();
        List<String> output = new ArrayList<>();

        for (int i = 0; i < infix.length(); i++) {
            char c = infix.charAt(i);

            if (Character.isLetterOrDigit(c)) {
                // 操作数直接输出
                output.add(String.valueOf(c));
            } else if (c == '(') {
                ops.push(c);
            } else if (c == ')') {
                // 弹出直到左括号
                while (!ops.isEmpty() && ops.peek() != '(') {
                    output.add(String.valueOf(ops.pop()));
                }
                ops.pop(); // 丢弃左括号
            } else {
                // 运算符:弹出优先级不低于当前的操作符
                while (!ops.isEmpty() && ops.peek() != '('
                        && prec.get(ops.peek()) >= prec.get(c)) {
                    output.add(String.valueOf(ops.pop()));
                }
                ops.push(c);
            }
        }

        // 剩余操作符出栈
        while (!ops.isEmpty()) {
            output.add(String.valueOf(ops.pop()));
        }

        return output;
    }
}

3. 计算后缀表达式

public class PostfixCalculator {

    /**
     * 计算后缀表达式的值
     * 输入:["2", "3", "+", "4", "*"] → (2+3)*4 = 20
     */
    public int evaluate(List<String> postfix) {
        Stack<Integer> stack = new Stack<>();

        for (String token : postfix) {
            if (token.matches("\\d+")) {
                stack.push(Integer.parseInt(token));
            } else {
                int b = stack.pop();
                int a = stack.pop();

                switch (token) {
                    case "+": stack.push(a + b); break;
                    case "-": stack.push(a - b); break;
                    case "*": stack.push(a * b); break;
                    case "/": stack.push(a / b); break;
                }
            }
        }

        return stack.pop();
    }
}

整体流程:

中缀表达式 → 中缀转后缀 → 计算后缀表达式 → 结果
"A+B*C-(D+E)" → ["A","B","C","*","+","D","E","+","-"] → 计算

三、循环队列

1. 为什么需要循环队列

普通顺序队列的问题:
  enqueue(A): front=0, rear=1  [A,  ,  ,  ,  ]
  enqueue(B): front=0, rear=2  [A, B,  ,  ,  ]
  dequeue():  front=1, rear=2  [ , B,  ,  ,  ]  ← 下标0浪费了

循环队列通过取模运算,让数组空间可以复用

2. 完整实现

class CircularQueue {
    private int[] data;
    private int front;  // 队头指针
    private int rear;   // 队尾指针(指向下一个插入位置)
    private int size;   // 当前元素个数
    private int capacity;

    public CircularQueue(int k) {
        data = new int[k];
        capacity = k;
        front = 0;
        rear = 0;
        size = 0;
    }

    public boolean enQueue(int value) {
        if (isFull()) return false;
        data[rear] = value;
        rear = (rear + 1) % capacity;
        size++;
        return true;
    }

    public boolean deQueue() {
        if (isEmpty()) return false;
        front = (front + 1) % capacity;
        size--;
        return true;
    }

    public int Front() {
        if (isEmpty()) return -1;
        return data[front];
    }

    public int Rear() {
        if (isEmpty()) return -1;
        // 返回 rear 前一个位置的元素
        int idx = (rear - 1 + capacity) % capacity;
        return data[idx];
    }

    public boolean isEmpty() {
        return size == 0;
    }

    public boolean isFull() {
        return size == capacity;
    }
}

3. 判空判满的两种方式

方式一:size 字段(推荐)
  isEmpty() → size == 0
  isFull()  → size == capacity
  简单直观

方式二:牺牲一个存储单元
  isEmpty() → front == rear
  isFull()  → (rear + 1) % capacity == front
  不占额外空间,但浪费一个数组元素

四、栈与队列的相互实现

1. 两个栈实现队列

class QueueByTwoStacks {
    private Stack<Integer> inStack;   // 入队栈
    private Stack<Integer> outStack;  // 出队栈

    public QueueByTwoStacks() {
        inStack = new Stack<>();
        outStack = new Stack<>();
    }

    public void push(int x) {
        inStack.push(x);
    }

    public int pop() {
        if (outStack.isEmpty()) {
            while (!inStack.isEmpty()) {
                outStack.push(inStack.pop());
            }
        }
        return outStack.pop();
    }
}

2. 两个队列实现栈

class StackByTwoQueues {
    private Queue<Integer> queue1;
    private Queue<Integer> queue2;

    public StackByTwoQueues() {
        queue1 = new LinkedList<>();
        queue2 = new LinkedList<>();
    }

    public void push(int x) {
        queue1.offer(x);
    }

    public int pop() {
        // 除了队尾元素,全部移到 queue2
        while (queue1.size() > 1) {
            queue2.offer(queue1.poll());
        }
        int result = queue1.poll();

        // 交换两个队列的引用
        Queue<Integer> temp = queue1;
        queue1 = queue2;
        queue2 = temp;

        return result;
    }
}

五、单调栈

单调栈是栈的高级应用,用于解决"下一个更大元素"类问题。

/**
 * 下一个更大元素
 * 输入:[2, 1, 2, 4, 3]
 * 输出:[4, 2, 4, -1, -1]
 * 
 * 维护一个单调递减栈
 */
public int[] nextGreaterElement(int[] nums) {
    int n = nums.length;
    int[] result = new int[n];
    Arrays.fill(result, -1);

    Stack<Integer> stack = new Stack<>();

    for (int i = 0; i < n; i++) {
        while (!stack.isEmpty() && nums[i] > nums[stack.peek()]) {
            int idx = stack.pop();
            result[idx] = nums[i];
        }
        stack.push(i);
    }

    return result;
}

六、408 考研常见考题

题1:栈在括号匹配中的应用

表达式:{[()()]}([])
判断过程:
  { → 入栈
  [ → 入栈
  ( → 入栈
  ) → 弹出 ( → 匹配 ✓
  ( → 入栈
  ) → 弹出 ( → 匹配 ✓
  ] → 弹出 [ → 匹配 ✓
  } → 弹出 { → 匹配 ✓
  ( → 入栈
  [ → 入栈
  ] → 弹出 [ → 匹配 ✓
  ) → 弹出 ( → 匹配 ✓
  栈空 → 完全匹配

题2:循环队列的存储

数组 A[0..m-1] 存放循环队列,front=5, rear=2
队中元素个数 = (rear - front + m) % m = (2-5+8)%8 = 5

题3:表达式求值

中缀:A + B * (C - D) - E / F
后缀:A B C D - * + E F / -

计算后缀时,用栈存操作数
遇到操作数入栈,遇到操作符弹出两个计算后入栈

七、总结

栈的应用:
  括号匹配 → 左入右出,最后判空
  表达式求值 → 中缀转后缀,再计算
  函数调用 → JVM 虚拟机栈

队列的应用:
  循环队列 → 取模运算,空间复用
  任务调度 → 线程池任务队列
  树的遍历 → 层序遍历(BFS)

💡 觉得有用的话,点赞 + 关注【张老师技术栈】吧!每周更新 Java/Python/爬虫 实战干货,不让你白来。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值