
本文针对使用自定义栈实现括号平衡检测时常见的逻辑错误进行深入分析与修复。文章详细阐述了原始代码在弹出操作中因循环条件不当导致的问题,并提供了一种基于`while`循环的改进方案,确保了栈操作的正确性及平衡性判断的准确性,同时兼容了在不导入任何库的限制下使用自定义栈的场景。
括号平衡检测是计算机科学中一个经典的算法问题,广泛应用于编译器、代码编辑器等领域,用于验证表达式或代码块的语法正确性。其核心思想是确保每个开括号(如 (, [, {)都有一个对应的闭括号(如 ), ], }),并且它们的嵌套顺序是正确的。栈(Stack)作为一种“后进先出”(LIFO)的数据结构,天然适合解决这类问题。
在某些特定场景下,我们可能被要求使用自定义的栈实现,而非Java标准库中的 java.util.Stack。这通常发生在教学环境或对内存、性能有极致要求的嵌入式系统中。在这种情况下,开发者需要自行实现 Node 类来构建链式栈。
原始代码尝试通过两个自定义栈来检测括号平衡。其基本思路是将所有开括号 ( 压入 stack1,将所有闭括号 ) 压入 stack2。随后,计划通过一个循环将两个栈中的元素逐一弹出,如果最终两个栈都为空,则认为表达式是平衡的。
以下是原始 isBalanced 方法的关键部分:
立即学习“Java免费学习笔记(深入)”;
  static boolean isBalanced(String expr){
      // base case: length of the expression must be even
        if (expr == null || expr.length() % 2 == 1) {
            return false;
        }
        Stack stack1 = new Stack();
        Stack stack2 = new Stack();
        // traverse the input expression and push characters
        for (int i = 0; i< expr.length(); i++){ 
            if (expr.charAt(i) == '(') {
                stack1.push(expr.charAt(i));
            } 
            if (expr.charAt(i) == ')') {
               stack2.push(expr.charAt(i));
            }
        }
        // Problematic popping loop
        for(int i = 0; i< expr.length(); i++) {
            stack1.pop(); // May cause issues if stack1 is empty
            stack2.pop(); // May cause issues if stack2 is empty
        }
        return (stack1.isEmpty() && stack2.isEmpty()) ; 
    }错误根源:不当的弹出循环条件
上述代码中的第二个 for 循环是导致问题的主要原因:
for(int i = 0; i< expr.length(); i++) {
    stack1.pop();
    stack2.pop();
}这个循环的迭代次数是 expr.length(),即表达式的总长度。然而,stack1 和 stack2 中实际存储的元素数量通常远小于 expr.length(),它们只分别存储了开括号和闭括号。例如,对于输入 ((())),expr.length() 为 6。stack1 和 stack2 各自只包含 3 个元素。当 for 循环执行到第四次迭代时,stack1 和 stack2 都已经为空,但循环仍会继续尝试调用 pop() 方法。由于自定义 Stack 类中的 pop() 方法在栈为空时会打印 "Trying to pop when stack is empty" 并返回 null,这就会导致控制台输出多条错误信息。
尽管最终 stack1.isEmpty() && stack2.isEmpty() 可能会返回 true(因为它们确实被清空了),但这并非通过正确的逻辑实现的,并且伴随着不必要的错误提示。这种方法实际上只是检查了开括号和闭括号的数量是否相等,而不是严格意义上的结构平衡(即 ( 必须在其对应的 ) 之前出现)。但考虑到原始代码的设计意图和问题描述,我们主要关注如何修正其弹出逻辑以实现其特定的“平衡”定义。
为了正确地从两个栈中弹出元素,我们应该在两个栈都还有元素时才进行弹出操作。一旦其中任何一个栈变空,就应该停止弹出,因为这意味着其中一类括号已经用尽。
修正后的 isBalanced 方法如下所示:
public class BalancedParenthesesChecker {
  // Stack class (provided by user, for context)
  static class Node {
      Object info;
      Node next;
      Node(Object info, Node next){
          this.info=info;
          this.next=next;
      }    
  }
  static class Stack {
      private Node top;
      public Stack() {
          top = null;
      }
      public boolean isEmpty(){
          return (top ==null);
      }
      public void push(Object newItem){
          top = new Node(newItem,  top);
      }
      public Object pop(){
          if (isEmpty()){
              System.out.println(
               "Trying to pop when stack is empty");
              return null;
          }else{
              Node temp = top;
              top = top.next;
              return temp.info;
          }
      }
      void popAll(){
          top = null;
      }
      public Object peek(){
          if (isEmpty()){
             System.out.println(
              "Trying to peek when stack is empty");
             return null;
          }else{  
             return top.info;
          }
      }
  }
  /**
   * 检查给定表达式中的括号是否“平衡”。
   * 此方法根据原始问题对“平衡”的定义进行修复:
   * 仅检查开括号 '(' 的数量是否与闭括号 ')' 的数量相等。
   * 它不检查括号的嵌套顺序。
   *
   * @param expr 待检查的字符串表达式。
   * @return 如果开括号和闭括号数量相等且表达式长度为偶数,则返回 true;否则返回 false。
   */
  static boolean isBalanced(String expr){
      // 基本情况:表达式为空或长度为奇数,则不可能平衡
        if (expr == null || expr.length() % 2 == 1) {
            return false;
        }
        Stack stack1 = new Stack(); // 存储开括号 '('
        Stack stack2 = new Stack(); // 存储闭括号 ')'
        // 遍历表达式,将开括号和闭括号分别压入不同的栈
        for (int i = 0; i< expr.length(); i++){ 
            if (expr.charAt(i) == '(') {
                stack1.push(expr.charAt(i));
            } 
            if (expr.charAt(i) == ')') {
               stack2.push(expr.charAt(i));
            }
        }
        // 改进后的弹出循环:只有当两个栈都有元素时才进行弹出操作
        // 这样可以避免在栈为空时继续尝试弹出
        while (!stack1.isEmpty() && !stack2.isEmpty()) {
            stack1.pop();
            stack2.pop();
        }
        // 最终判断:如果两个栈都为空,则认为表达式是平衡的
        // 这意味着开括号和闭括号的数量相等
        return (stack1.isEmpty() && stack2.isEmpty()) ; 
    }
    public static void main(String[] args) {
        System.out.println("Testing ((())): " + isBalanced("((()))")); // Expected: true
        System.out.println("Testing ()): " + isBalanced("(())"));   // Expected: true
        System.out.println("Testing )(: " + isBalanced(")("));     // Expected: true (based on this specific definition)
        System.out.println("Testing (: " + isBalanced("("));       // Expected: false (odd length)
        System.out.println("Testing ): " + isBalanced(")"));       // Expected: false (odd length)
        System.out.println("Testing (()): " + isBalanced("(()"));    // Expected: false (odd length)
        System.out.println("Testing empty string: " + isBalanced("")); // Expected: true (length 0, both stacks empty)
    }
}改进逻辑说明:
为了完整性,这里再次展示自定义的 Stack 和 Node 类的结构。它们是实现上述逻辑的基础,且严格遵循了不导入任何标准库的限制。
Node 类:
public class Node {
    Object info; // 存储节点信息
    Node next;   // 指向下一个节点的引用
    Node(Object info, Node next){
        this.info=info;
        this.next=next;
    }    
}Stack 类:
public class Stack{
    private Node top; // 栈顶指针
    public Stack() {
        top = null; // 构造函数,初始化空栈
    }
    public boolean isEmpty(){
        return (top ==null); // 判断栈是否为空
    }
    public void push(Object newItem){
        top = new Node(newItem,  top); // 压栈操作:新元素成为栈顶
    }
    public Object pop(){
        if (isEmpty()){
            System.out.println(
             "Trying to pop when stack is empty"); // 空栈弹出错误提示
            return null;
        }else{
            Node temp = top; // 暂存当前栈顶
            top = top.next;  // 栈顶指向下一个节点
            return temp.info; // 返回原栈顶元素
        }
    }
    void popAll(){
        top = null; // 清空栈
    }
    public Object peek(){
        if (isEmpty()){
           System.out.println(
            "Trying to peek when stack is empty"); // 空栈窥视错误提示
           return null;
        }else{  
           return top.info; // 返回栈顶元素,不移除
        }
    }
}这些自定义类提供了栈的基本功能,并且在 pop 和 peek 方法中包含了对空栈操作的错误提示,这对于调试非常有帮助。
通过对 isBalanced 方法中弹出循环逻辑的修正,我们成功解决了自定义栈在处理括号平衡问题时出现的 Trying to pop when stack is empty 错误。核心改进在于将固定次数的 for 循环替换为基于栈实际状态的 while 循环,确保了只有在栈非空时才执行弹出操作。这个案例强调了在操作数据结构时,精确控制循环条件和处理边界情况的重要性,尤其是在使用自定义数据结构且无法依赖标准库的健壮性时。理解并正确应用这些原则,是编写高质量、健壮代码的基础。
以上就是优化括号平衡检测:理解与修复Java自定义栈的错误用法的详细内容,更多请关注php中文网其它相关文章!
                        
                        每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
                Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号