基本递归,检查平衡括号

Posted

技术标签:

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

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

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

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

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

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

【问题讨论】:

【参考方案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】:

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

【讨论】:

以上是关于基本递归,检查平衡括号的主要内容,如果未能解决你的问题,请参考以下文章

LeetCode 856. 括号的分数

9.9递归和动态规划——打印n对括号的所有有效组合(即左右括号正确配对)

二叉树之AVL树的平衡实现(递归与非递归)

尾递归和JAVA

需要帮助解决一个不平衡递归树问题

04-树5 Root of AVL Tree