web-dev-qa-db-ja.com

基本的な再帰、バランスの取れた括弧のチェック

過去にスタックを使用して平衡方程式をチェックするソフトウェアを作成しましたが、今度は同様のアルゴリズムを再帰的に作成して、適切にネストされたブラケットと括弧をチェックするように求められます。

良い例:()[]()([]()[])

悪い例:((]([)]

私の関数が呼び出されたとします:isBalanced。

各パスは、より小さな部分文字列を評価する必要があります(2の基本ケースに達するまで)。または、常に文字列全体を評価し、インデックスを内側に移動する必要がありますか?

40
pws5068

これを行うには多くの方法がありますが、最も単純なアルゴリズムは単純に左から右に処理し、スタックをパラメーターとして渡すことです

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で実装されています。便宜上、スタックが文字列のbackの代わりにfrontをプッシュするように切り替えたことに注意してください。また、エラーとして報告するのではなく、括弧以外の記号をスキップするように修正しました。

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
44

まず、元の質問に対して、非常に長い文字列を使用している場合は、関数呼び出しを行うたびに1文字を除いた正確なコピーを作成したくないことに注意してください。そのため、インデックスの使用を優先するか、選択した言語が舞台裏でコピーを作成していないことを確認する必要があります。

第二に、スタックデータ構造を使用しているすべての回答に問題があります。割り当てのポイントは、再帰によって関数呼び出しスタックの作成を理解することだと思います。各再帰呼び出しは暗黙的なスタック上の新しいエントリであるため、括弧を保持するためにスタックデータ構造を使用する必要はありません。

()に一致する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!
51
indiv

アイデアは、開いた括弧のリストを保持し、閉じ括弧を見つけた場合、最後に開いた括弧を閉じるかどうかを確認することです。

  • これらの括弧が一致する場合、openedBracketsのリストから最後に開いたものを削除し、残りの文字列の再帰的なチェックを続けます
  • それ以外の場合は、一度開いた神経を閉じるブラケットが見つかったため、バランスが取れていません。

文字列が最終的に空になったときに、角かっこのリストも空の場合(したがって、すべての角かっこが閉じられている場合)、trueを返します。それ以外の場合はfalse

[〜#〜] algorithm [〜#〜](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);
    }
}

[〜#〜] test [〜#〜]

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));
    }
}

[〜#〜] output [〜#〜]

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

次のクラスをインポートしたことに注意してください。

import Java.util.HashMap;
import Java.util.LinkedList;
import Java.util.Map;
3
 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;
    }
}
2
jot

論理的な観点からは重要ではありません。現在のすべての不均衡な括弧を再帰の各ステップに渡すと、後方に目を向ける必要がなくなります。再帰呼び出しごとに文字列を切り取るか、単にインデックスをインクリメントして現在の最初の文字だけを見るかは問題です。

可変ではない文字列を持つほとんどのプログラミング言語では、スタック上で少し大きい文字列を渡すよりも、文字列を短くする方が(パフォーマンス面で)おそらくコストがかかります。一方、Cのような言語では、char配列内のポインターをインクリメントするだけで済みます。これら2つのアプローチのどちらがより「効率的」であるかは、言語にかなり依存していると思います。どちらも概念的な観点からは同等です。

1
Adrian Petrescu

これはあなたのデザイン次第です。 2つのカウンタを使用するか、2つの異なるシンボルでスタックするか、再帰を使用して処理できます。違いは設計アプローチにあります。

0
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
}
0
siva k

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での変換のみを提案していました。

0
MrOnyancha

バランスの取れた括弧をチェックする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);
?>
0
Swatantra Kumar