web-dev-qa-db-fra.com

Récursion de base, contrôle de la parenthèse équilibrée

Dans le passé, j'ai déjà écrit un logiciel qui utilise une pile pour vérifier les équations équilibrées, mais je suis maintenant invité à écrire un algorithme similaire de manière récursive pour rechercher les parenthèses et les crochets correctement imbriqués.

Bons exemples: () [] () ([] () [])

Mauvais exemples: ((] ([)]

Supposons que ma fonction s'appelle: isBalanced. 

Chaque passage doit-il évaluer une sous-chaîne plus petite (jusqu'à atteindre un cas de base de 2 à gauche)? Ou, devrais-je toujours évaluer la chaîne complète et déplacer les index vers l'intérieur?

40
pws5068

Il y a plusieurs façons de le faire, mais l'algorithme le plus simple consiste simplement à traiter en avant-gauche, en passant la pile en tant que paramètre.

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"

Ici, il est implémenté en Java. Notez que je l'ai changé maintenant pour que la pile insère front au lieu de back de la chaîne, pour plus de commodité. Je l'ai également modifié pour qu'il ignore les symboles autres que des parenthèses au lieu de le signaler comme une erreur.

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

Harnais de test:

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

Sortie:

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

Tout d’abord, pour répondre à votre question initiale, sachez que si vous travaillez avec de très longues chaînes, vous ne voulez pas faire de copies exactes moins une lettre à chaque appel de fonction. Vous devez donc privilégier l'utilisation d'index ou vérifier que la langue de votre choix ne permet pas de faire des copies en arrière-plan.

Deuxièmement, j'ai un problème avec toutes les réponses ici qui utilisent une structure de données de pile. Je pense que le but de votre mission est que vous compreniez qu'avec la récursion, votre fonction appelle crée une pile . Vous n'avez pas besoin d'utiliser une structure de données de pile pour conserver vos parenthèses, car chaque appel récursif est une nouvelle entrée sur une pile implicite.

Je vais démontrer avec un programme C qui correspond à ( et ). Ajouter les autres types comme [ et ] est un exercice pour le lecteur. Tout ce que je maintiens dans la fonction est ma position dans la chaîne (transmise en tant que pointeur) car la récursivité est ma pile. 

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

Testé avec ce code:

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

Sortie:

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

L'idée est de conserver une liste des crochets ouverts, et si vous trouvez un crochet de fermeture, vérifiez s'il ferme le dernier ouvert: 

  • Si ces crochets correspondent, supprimez le dernier ouvert de la liste des openBrackets et continuez à vérifier de manière récursive le reste de la chaîne.
  • Sinon, vous avez trouvé des crochets qui ferment un nerver ouvert une fois et qui ne sont donc pas équilibrés.

Lorsque la chaîne est finalement vide, si la liste des crochets est également vide (donc tous les crochets ont été fermés), retournez true, sinon false

ALGORITHME(en 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));
    }
}

SORTIE:

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

Notez que j'ai importé les classes suivantes:

import Java.util.HashMap;
import Java.util.LinkedList;
import Java.util.Map;
3
Luca Mastrostefano
 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

Cela n’a pas vraiment d’importance sur le plan logique: si vous conservez une pile de tous les parens non équilibrés que vous passez à chaque étape de la récursivité, vous n’aurez plus besoin de regarder en arrière. Peu importe si vous coupez la chaîne à chaque appel récursif ou si vous incrémentez simplement un index et ne regardez que le premier caractère actuel.

Dans la plupart des langages de programmation comportant des chaînes non mutables, il est probablement plus coûteux (en termes de performances) de raccourcir la chaîne que de transmettre une chaîne légèrement plus grande sur la pile. D'autre part, dans un langage comme C, vous pouvez simplement incrémenter un pointeur dans le tableau de caractères. Je suppose que la langue dépend beaucoup de laquelle de ces deux approches est la plus «efficace». Ils sont tous deux équivalents d'un point de vue conceptuel.

1
Adrian Petrescu

Je dirais que cela dépend de votre conception. Vous pouvez utiliser deux compteurs ou empiler avec deux symboles différents ou vous pouvez le gérer en utilisant la récursivité, la différence réside dans l'approche de conception.

0
Gabriel Ščerbák
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

Dans le langage de programmation Scala, je le ferais comme ceci:

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

Veuillez éditer pour le rendre parfait. 

Je ne faisais que suggérer une conversion en Scala.

0
MrOnyancha

Solution PHP pour vérifier les parenthèses équilibrées

<?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