【问题标题】:Basic Recursion, Check Balanced Parenthesis基本递归,检查平衡括号
【发布时间】:2011-02-12 06:04:31
【问题描述】:

我过去编写过使用堆栈来检查平衡方程的软件,但现在我被要求编写一个类似的递归算法来检查正确嵌套的括号和括号。

很好的例子:() [] () ([]()[])

不好的例子:( (] ([)]

假设我的函数被调用:isBalanced。

是否应该每次通过评估一个较小的子字符串(直到达到 2 左的基本情况)?或者,我是否应该始终评估整个字符串并将索引向内移动?

【问题讨论】:

    标签: algorithm recursion stack


    【解决方案1】:

    平衡括号 (JS)

    更直观的解决方案是像这样使用堆栈:

    function isBalanced(str) {
      const parentesis = {
        '(': ')',
        '[': ']',
        '{': '}',
      };
      const closing = Object.values(parentesis);
      const stack = [];
    
      for (let char of str) {
        if (parentesis[char]) {
          stack.push(parentesis[char]);
        } else if (closing.includes(char) && char !== stack.pop()) {
          return false;
        }
      }
     
      return !stack.length;
    }
    
    console.log(isBalanced('{[()]}')); // true
    console.log(isBalanced('{[(]]}')); // false
    console.log(isBalanced('([()]'));  // false

    使用递归函数(不使用堆栈),可能看起来像这样:

    function isBalanced(str) {
      const parenthesis = {
        '(': ')',
        '[': ']',
        '{': '}',
      };
    
      if (!str.length) {
        return true;
      }
    
      for (let i = 0; i < str.length; i++) {
        const char = str[i];
    
        if (parenthesis[char]) {
          for (let j = str.length - 1; j >= i; j--) {
            const _char = str[j];
    
            if (parenthesis[_char]) {
              return false;
            } else if (_char === parenthesis[char]) {
              return isBalanced(str.substring(i + 1, j));
            }
          }
        } else if (Object.values(parenthesis).includes(char)) {
          return false;
        }
      }
      return true;
    }
    
    console.log(isBalanced('{[()]}')); // true
    console.log(isBalanced('{[(]]}')); // false
    console.log(isBalanced('([()]'));  // false

    * 正如@Adrian 提到的,您也可以在递归函数中使用堆栈,而无需向后看

    【讨论】:

      【解决方案2】:

      在 Scala 编程语言中,我会这样做:

        def balance(chars: List[Char]): Boolean = {
      
          def process(chars: List[Char], myStack: Stack[Char]): Boolean =
      
            if (chars.isEmpty) myStack.isEmpty
      
            else {
              chars.head match {
                case '(' => process(chars.tail, myStack.push(chars.head))
                case ')' => if (myStack.contains('(')) process(chars.tail, myStack.pop)
                else false
                case '[' => process(chars.tail, myStack.push(chars.head))
                case ']' => {
                  if (myStack.contains('[')) process(chars.tail, myStack.pop) else false
                }
                case _ => process(chars.tail, myStack)
              }
            }
      
          val balancingAuxStack = new Stack[Char]
      
          process(chars, balancingAuxStack)
        }
      

      请进行编辑以使其完美。

      我只是建议在 Scala 中进行转换。

      【讨论】:

        【解决方案3】:

        检查平衡括号的PHP解决方案

        <?php
        /**
         * @param string $inputString
         */
        function isBalanced($inputString)
        {
            if (0 == strlen($inputString)) {
                echo 'String length should be greater than 0';
                exit;
            }
        
            $stack = array();
            for ($i = 0; $i < strlen($inputString); $i++) {
                $char = $inputString[$i];
                if ($char === '(' || $char === '{' || $char === '[') {
                    array_push($stack, $char);
                }
                if ($char === ')' || $char === '}' || $char === ']') {
                    $matchablePairBraces = array_pop($stack);
                    $isMatchingPair = isMatchingPair($char, $matchablePairBraces);
                    if (!$isMatchingPair) {
                        echo "$inputString is NOT Balanced." . PHP_EOL;
                        exit;
                    }
                }
            }
            echo "$inputString is Balanced." . PHP_EOL;
        }
        
        /**
         * @param string $char1
         * @param string $char2
         * @return bool
         */
        function isMatchingPair($char1, $char2)
        {
            if ($char1 === ')' && $char2 === '(') {
                return true;
            }
            if ($char1 === '}' && $char2 === '{') {
                return true;
            }
            if ($char1 === ']' && $char2 === '[') {
                return true;
            }
            return false;
        }
        
        $inputString = '{ Swatantra (() {} ()) Kumar }';
        isBalanced($inputString);
        ?>
        

        【讨论】:

          【解决方案4】:
          func evalExpression(inStringArray:[String])-> Bool{
              var status = false
              var inStringArray = inStringArray
              if inStringArray.count == 0 {
                  return true
              }
          
              // determine the complimentary bracket.
              var complimentaryChar = ""
              if (inStringArray.first == "(" || inStringArray.first == "[" || inStringArray.first == "{"){
                  switch inStringArray.first! {
                  case "(":
                      complimentaryChar = ")"
                      break
                  case "[":
                      complimentaryChar = "]"
                      break
                  case "{":
                      complimentaryChar = "}"
                      break
                  default:
                      break
                  }
              }else{
                  return false
              }
          
              // find the complimentary character index in the input array.
              var index = 0
              var subArray = [String]()
              for i in 0..<inStringArray.count{
                  if inStringArray[i] == complimentaryChar {
                      index = i
                  }
              }
              // if no complimetary bracket is found,so return false.
              if index == 0{
                  return false
              }
              // create a new sub array for evaluating the brackets.
              for i in 0...index{
                  subArray.append(inStringArray[i])
              }
          
              subArray.removeFirst()
              subArray.removeLast()
          
              if evalExpression(inStringArray: subArray){
                  // if part of the expression evaluates to true continue with the rest.
                  for _ in 0...index{
                      inStringArray.removeFirst()
                  }
                  status = evalExpression(inStringArray: inStringArray)
              }
          
              return status
          }
          

          【讨论】:

          • 添加一些解释,说明此答案如何帮助 OP 解决当前问题
          【解决方案5】:

          @indiv 的回答很好,足以解决括号语法问题。如果您想使用堆栈或不想使用递归方法,您可以查看 github 上的 python script。它简单快捷。

          BRACKET_ROUND_OPEN = '('
          BRACKET_ROUND__CLOSE = ')'
          BRACKET_CURLY_OPEN = '{'
          BRACKET_CURLY_CLOSE = '}'
          BRACKET_SQUARE_OPEN = '['
          BRACKET_SQUARE_CLOSE = ']'
          
          TUPLE_OPEN_CLOSE = [(BRACKET_ROUND_OPEN,BRACKET_ROUND__CLOSE),
                              (BRACKET_CURLY_OPEN,BRACKET_CURLY_CLOSE),
                              (BRACKET_SQUARE_OPEN,BRACKET_SQUARE_CLOSE)]
          BRACKET_LIST = [BRACKET_ROUND_OPEN,BRACKET_ROUND__CLOSE,BRACKET_CURLY_OPEN,BRACKET_CURLY_CLOSE,BRACKET_SQUARE_OPEN,BRACKET_SQUARE_CLOSE]
          
          def balanced_parentheses(expression):
              stack = list()
              left = expression[0]
              for exp in expression:
                  if exp not in BRACKET_LIST:
                      continue
                  skip = False
                  for bracket_couple in TUPLE_OPEN_CLOSE:
                      if exp != bracket_couple[0] and exp != bracket_couple[1]:
                          continue
                      if left == bracket_couple[0] and exp == bracket_couple[1]:
                          if len(stack) == 0:
                              return False
                          stack.pop()
                          skip = True
                          left = ''
                          if len(stack) > 0:
                              left = stack[len(stack) - 1]
                  if not skip:
                      left = exp
                      stack.append(exp)
          
              return len(stack) == 0
          if __name__ == '__main__':
              print(balanced_parentheses('(()())({})[]'))#True
              print(balanced_parentheses('((balanced)(parentheses))({})[]'))#True
              print(balanced_parentheses('(()())())'))#False
          

          【讨论】:

            【解决方案6】:
             public static boolean isBalanced(String str) {
                if (str.length() == 0) {
                    return true;
                }
                if (str.contains("()")) {
                    return isBalanced(str.replaceFirst("\\(\\)", ""));
                }
            
                if (str.contains("[]")) {
                    return isBalanced(str.replaceFirst("\\[\\]", ""));
                }
                if (str.contains("{}")) {
                    return isBalanced(str.replaceFirst("\\{\\}", ""));
                } else {
                    return false;
                }
            }
            

            【讨论】:

              【解决方案7】:

              这个想法是保留一个打开的括号列表,如果你找到一个右括号,检查它是否关闭了最后一个打开的:

              • 如果这些括号匹配,则从打开的括号列表中删除最后打开的,并继续递归检查字符串的其余部分
              • 否则,您发现一个支架关闭了曾经打开过的神经,因此它不平衡。

              当字符串最终为空时,如果括号列表也为空(因此所有括号都已关闭)返回true,否则返回false

              算法(Java):

              public static boolean isBalanced(final String str1, final LinkedList<Character> openedBrackets, final Map<Character, Character> closeToOpen) {
                  if ((str1 == null) || str1.isEmpty()) {
                      return openedBrackets.isEmpty();
                  } else if (closeToOpen.containsValue(str1.charAt(0))) {
                      openedBrackets.add(str1.charAt(0));
                      return isBalanced(str1.substring(1), openedBrackets, closeToOpen);
                  } else if (closeToOpen.containsKey(str1.charAt(0))) {
                      if (openedBrackets.getLast() == closeToOpen.get(str1.charAt(0))) {
                          openedBrackets.removeLast();
                          return isBalanced(str1.substring(1), openedBrackets, closeToOpen);
                      } else {
                          return false;
                      }
                  } else {
                      return isBalanced(str1.substring(1), openedBrackets, closeToOpen);
                  }
              }
              

              测试

              public static void main(final String[] args) {
                  final Map<Character, Character> closeToOpen = new HashMap<Character, Character>();
                  closeToOpen.put('}', '{');
                  closeToOpen.put(']', '[');
                  closeToOpen.put(')', '(');
                  closeToOpen.put('>', '<');
              
                  final String[] testSet = new String[] { "abcdefksdhgs", "[{aaa<bb>dd}]<232>", "[ff{<gg}]<ttt>", "{<}>" };
                  for (final String test : testSet) {
                      System.out.println(test + "  ->  " + isBalanced(test, new LinkedList<Character>(), closeToOpen));
                  }
              }
              

              输出

              abcdefksdhgs  ->  true
              [{aaa<bb>dd}]<232>  ->  true
              [ff{<gg}]<ttt>  ->  false
              {<}>  ->  false
              

              请注意,我已导入以下类:

              import java.util.HashMap;
              import java.util.LinkedList;
              import java.util.Map;
              

              【讨论】:

                【解决方案8】:

                首先,对于您最初的问题,请注意,如果您使用的是非常长的字符串,您不希望每次进行函数调用时都制作精确的副本减去一个字母。因此,您应该倾向于使用索引或验证您选择的语言不是在幕后复制。

                其次,我对这里所有使用堆栈数据结构的答案都有疑问。我认为您分配的重点是让您了解通过递归您的函数调用创建堆栈。您不需要使用堆栈数据结构来保存括号,因为每个递归调用都是隐式堆栈上的新条目。

                我将演示一个匹配() 的C 程序。添加[] 等其他类型是读者的练习。我在函数中维护的只是我在字符串中的位置(作为指针传递),因为递归是我的堆栈。

                /* Search a string for matching parentheses.  If the parentheses match, returns a
                 * pointer that addresses the nul terminator at the end of the string.  If they
                 * don't match, the pointer addresses the first character that doesn't match.
                 */
                const char *match(const char *str)
                {
                        if( *str == '\0' || *str == ')' ) { return str; }
                        if( *str == '(' )
                        {
                                const char *closer = match(++str);
                                if( *closer == ')' )
                                {
                                        return match(++closer);
                                }
                                return str - 1;
                        }
                
                        return match(++str);
                }
                

                用这段代码测试过:

                    const char *test[] = {
                            "()", "(", ")", "", "(()))", "(((())))", "()()(()())",
                            "(() ( hi))) (())()(((( ))))", "abcd"
                    };
                
                    for( index = 0; index < sizeof(test) / sizeof(test[0]); ++index ) {
                            const char *result = match(test[index]);
                
                            printf("%s:\t", test[index]);
                            *result == '\0' ? printf("Good!\n") :
                                    printf("Bad @ char %d\n", result - test[index] + 1);
                    }
                

                输出:

                (): Good!
                (:  Bad @ char 1
                ):  Bad @ char 1
                :   Good!
                (())):      Bad @ char 5
                (((()))):   Good!
                ()()(()()): Good!
                (() ( hi))) (())()(((( )))):    Bad @ char 11
                abcd:       Good!
                

                【讨论】:

                • +1 用于展示如何利用调用堆栈而不是显式调用堆栈。我认为没有人提供答案表明这很奇怪......不过,这在 Lisp 中看起来会更好;)
                • @Sid:当我在())( 上运行测试程序时,我得到“Bad @ char 3”,这对我来说看起来不错。 ideone.com/e.js/VBM8IU
                • +1,尽管最好有一个(非递归)包装函数is_balanced 委托给这个函数。整个“返回指向第一个不匹配的指针”实际上是调用者不必理解的实现细节。
                【解决方案9】:

                应该是栈的简单使用..

                private string tokens = "{([<})]>";        
                    Stack<char> stack = new Stack<char>();   
                
                    public bool  IsExpressionVaild(string exp)
                    {
                        int mid = (tokens.Length / 2)  ;  
                
                        for (int i = 0; i < exp.Length; i++)
                        {
                            int index = tokens.IndexOf(exp[i]);
                            if (-1 == index) { continue; }
                
                            if(index<mid ) stack .Push(exp[i]);
                            else 
                            {
                                if (stack.Pop() != tokens[index - mid]) { return false; }       
                
                            }          
                
                        }
                        return true;       
                
                    }
                

                【讨论】:

                • 这是错误的解决方案!你不能假设第一个中单会完美匹配其他中单!您的解决方案仅适用于 (())、((()))、(((())))...等等... ((())()()) 怎么样?使用 Stack 是一个完美的主意,但你只需要推动左大括号,每当遇到右大括号时,你就会从堆栈中弹出。如果堆栈为空,则它是无效字符串。在字符串的末尾,您确保堆栈为空。如果不是,则为无效字符串,否则为有效字符串。
                【解决方案10】:

                有很多方法可以做到这一点,但最简单的算法是简单地从左到右处理,将堆栈作为参数传递

                FUNCTION isBalanced(String input, String stack) : boolean
                  IF isEmpty(input)
                    RETURN isEmpty(stack)
                  ELSE IF isOpen(firstChar(input))
                    RETURN isBalanced(allButFirst(input), stack + firstChar(input))
                  ELSE IF isClose(firstChar(input))
                    RETURN NOT isEmpty(stack) AND isMatching(firstChar(input), lastChar(stack))
                      AND isBalanced(allButFirst(input), allButLast(stack))
                  ELSE
                    ERROR "Invalid character"
                

                这里是用Java实现的。请注意,为了方便起见,我现在已将其切换为将堆栈推入 front 而不是 back 字符串。我还对其进行了修改,使其只是跳过非括号符号而不是将其报告为错误。

                static String open  = "([<{";
                static String close = ")]>}";
                
                static boolean isOpen(char ch) {
                    return open.indexOf(ch) != -1;
                }
                static boolean isClose(char ch) {
                    return close.indexOf(ch) != -1;
                }
                static boolean isMatching(char chOpen, char chClose) {
                    return open.indexOf(chOpen) == close.indexOf(chClose);
                }
                
                static boolean isBalanced(String input, String stack) {
                    return
                        input.isEmpty() ?
                            stack.isEmpty()
                        : isOpen(input.charAt(0)) ?
                            isBalanced(input.substring(1), input.charAt(0) + stack)
                        : isClose(input.charAt(0)) ?
                            !stack.isEmpty() && isMatching(stack.charAt(0), input.charAt(0))
                              && isBalanced(input.substring(1), stack.substring(1))
                        : isBalanced(input.substring(1), stack);
                }
                

                测试工具:

                    String[] tests = {
                        "()[]<>{}",
                        "(<",
                        "]}",
                        "()<",
                        "(][)",
                        "{(X)[XY]}",
                    };
                    for (String s : tests) {
                        System.out.println(s + " = " + isBalanced(s, ""));
                    }
                

                输出:

                ()[]<>{} = true
                (< = false
                ]} = false
                ()< = false
                (][) = false
                {(X)[XY]} = true
                

                【讨论】:

                • 如果问题是检查括号是否使用递归平衡,您不想显式传递堆栈作为递归函数的参数
                • 这种三元嵌套使得这很难阅读。这不是代码高尔夫。您可以使用显式控制流。 .
                【解决方案11】:

                从逻辑的角度来看,这并不重要——如果你保留一个堆栈,所有当前不平衡的括号传递给递归的每一步,你就永远不需要向后看,所以它如果您在每次递归调用时剪切字符串,或者只是增加一个索引并只查看当前的第一个字符,这并不重要。

                在大多数具有非可变字符串的编程语言中,缩短字符串可能比在堆栈上传递稍大的字符串更昂贵(在性能方面)。另一方面,在像 C 这样的语言中,您可以在 char 数组中增加一个指针。我想这两种方法中哪一种更“有效”取决于语言。从概念的角度来看,它们都是等价的。

                【讨论】:

                  【解决方案12】:

                  我会说这取决于您的设计。您可以使用两个计数器或堆栈两个不同的符号,或者您可以使用递归处理它,区别在于设计方法。

                  【讨论】:

                    猜你喜欢
                    • 2013-11-18
                    • 1970-01-01
                    • 2012-09-15
                    • 1970-01-01
                    • 2015-10-18
                    • 2020-08-09
                    • 1970-01-01
                    • 2015-05-01
                    • 2022-08-13
                    相关资源
                    最近更新 更多