web-dev-qa-db-fra.com

Le moyen le plus efficace de tester l'égalité de deux arbres binaires

Comment implémenteriez-vous en Java la classe de nœud d'arbre binaire et la classe d'arbre binaire pour prendre en charge la méthode de contrôle d'égalité la plus efficace (du point de vue de l'exécution) (doit également être implémentée):

    boolean equal(Node<T> root1, Node<T> root2) {}

ou

    boolean equal(Tree t1, Tree t2) {}

Tout d'abord, j'ai créé la classe Node comme suit:

    public class Node<T> {
        private Node<T> left;
        private Node<T> right;
        private T data;

        // standard getters and setters
    }

puis la méthode equals qui prend 2 nœuds racine en tant qu'arguments et exécute la comparaison récursive standard:

    public boolean equals(Node<T> root1, Node<T> root2) {
        boolean rootEqual = false;
        boolean lEqual = false;
        boolean rEqual = false;    

        if (root1 != null && root2 != null) {
            rootEqual = root1.getData().equals(root2.getData());

            if (root1.getLeft()!=null && root2.getLeft() != null) {
                // compare the left
                lEqual = equals(root1.getLeft(), root2.getLeft());
            }
            else if (root1.getLeft() == null && root2.getLeft() == null) {
                lEqual = true;
            }
            if (root1.getRight() != null && root2.getRight() != null) {
                // compare the right
                rEqual = equals(root1.getRight(), root2.getRight());
            }
            else if (root1.getRight() == null && root2.getRight() == null) {
                rEqual = true;
            }

            return (rootEqual && lEqual && rEqual);
        }
        return false;
    } 

Ma deuxième tentative a été d'implémenter les arbres en utilisant des tableaux et des index pour la traversée. La comparaison peut ensuite être effectuée à l'aide des opérations au niveau des bits (AND) sur deux tableaux: lisez le bloc de 2 tableaux et masquez-les l'un après l'autre à l'aide de l'opérateur AND logique. Je n’ai pas réussi à faire fonctionner mon code, je ne l’affiche donc pas ici (je vous serais reconnaissant de mettre en œuvre la deuxième idée ainsi que vos améliorations). 

Avez-vous des idées sur la manière de réaliser le test d’égalité des arbres binaires le plus efficacement possible?

MODIFIER

La question suppose une égalité structurelle. (Pas l'égalité sémantique)

Cependant, le code qui teste l’égalité sémantique, par ex. "Faut-il considérer que les deux arbres sont égaux si leur contenu est identique, même si leur structure ne l'est pas?" Il suffirait de parcourir l’arbre dans l’ordre et cela devrait être simple.

17
aviad

Pour une chose, vous vérifiez toujours les branches, même si vous constatez que les racines sont inégales. Votre code serait plus simple (IMO) et plus efficace si vous veniez de retourner false dès que vous constatiez une inégalité.

Une autre option pour simplifier les choses consiste à autoriser votre méthode equals à accepter les valeurs null et à comparer deux valeurs NULL égales. De cette façon, vous pouvez éviter tous ces contrôles de nullité dans les différentes branches. Cela ne le rendra pas plus efficace, mais ce sera plus simple:

public boolean equals(Node<T> root1, Node<T> root2) {
    // Shortcut for reference equality; also handles equals(null, null)
    if (root1 == root2) {
        return true;
    }
    if (root1 == null || root2 == null) {
        return false;
    }
    return root1.getData().equals(root2.getData()) &&
           equals(root1.getLeft(), root2.getLeft()) &&
           equals(root1.getRight(), root2.getRight());
} 

Notez qu'actuellement, cela échouera si root1.getData() renvoie null. (Cela peut être impossible avec la façon dont vous ajoutez des nœuds.)

EDIT: Comme indiqué dans les commentaires, vous pouvez utiliser des codes de hachage pour créer un "départ rapide" très rapide - mais cela ajouterait de la complexité.

Soit vous devez rendre vos arbres immuables (ce qui est une toute autre discussion) ou il faut que chaque nœud connaisse son parent, de sorte que lorsque le nœud est changé (par exemple en ajoutant une feuille ou en modifiant la valeur), il doit mettre à jour son code de hachage et lui demander parent à mettre à jour aussi .

29
Jon Skeet

Par curiosité, estimez-vous que les deux arbres sont égaux si leur contenu est identique, même si leur structure ne l’est pas? Par exemple, sont-ils égaux?

  B         C        C      A
 / \       / \      / \      \
A   D     B   D    A   D      B
   /     /          \          \
  C     A            B          C
                                 \
                                  D

Ces arbres ont le même contenu dans le même ordre, mais étant donné que les structures sont différentes, vos tests ne seraient pas égaux.

Si vous voulez tester cette égalité, personnellement, je construirais simplement un itérateur pour l'arbre en utilisant le parcours dans l'ordre et l'itération entre les arbres en les comparant élément par élément.

25
Hounshell

Tout d’abord, je fais quelques hypothèses générales. Ce sont des hypothèses valables pour la plupart des classes de collection basées sur des arbres, mais cela vaut toujours la peine de vérifier:

  1. Vous considérez que deux arbres sont égaux si et seulement s'ils le sont à la fois en termes de arborescence et en valeurs de données sur chaque nœud (comme défini par data.equals (...))
  2. les valeurs de données nulles sont autorisées sur les nœuds d'arborescence (cela peut être soit parce que vous autorisez explicitement la nullité, soit parce que votre structure de données ne stocke que des valeurs non nulles au niveau des nœuds terminaux).
  3. Vous ne pouvez pas tirer parti de faits inhabituels concernant la distribution des valeurs de données (par exemple, si vous saviez que les seules valeurs de données possibles étaient null ou la chaîne "foo", vous n'avez pas besoin de pour comparer deux valeurs de chaîne non nulles)
  4. Les arbres seront généralement de taille moyenne et raisonnablement bien équilibrés. En particulier, cela garantit que les arbres ne seront jamais aussi profonds que vous courez le risque d'exceptions StackOverflowException causées par une récursion profonde.

En supposant que ces hypothèses soient correctes, l'approche que je suggérerais est la suivante:

  • Effectuez d'abord une vérification de l'égalité de la référence racine. ceci élimine rapidement le cas où deux nulls ou le même arbre sont transmis pour comparaison avec lui-même. Les deux cas sont très fréquents et le contrôle d’égalité de référence est extrêmement bon marché.
  • Vérifiez les NULL ensuite. Non-null n'est évidemment pas égal à null, ce qui vous permet de sortir plus tôt plus il établit une garantie non nulle pour le code ultérieur! Un compilateur très intelligent pourrait aussi théoriquement utiliser cette garantie pour optimiser ultérieurement les contrôles du pointeur null (pas sûr que la JVM le fasse actuellement)
  • Vérifiez l'égalité des références de données et les valeurs NULL suivantes. Cela évite de descendre tout le long des branches de l’arbre comme vous le feriez même en cas de données inégales si vous descendiez les branches en premier.
  • Vérifiez data.equals () next. Là encore, vous souhaitez vérifier l’égalité des données avant les branches de l’arbre. Vous faites cela après avoir vérifié les valeurs NULL, car data.equals () est potentiellement plus cher et vous voulez vous assurer que vous n’obtiendrez pas d’exception NullPointerException.
  • Vérifiez l'égalité des branches de manière récursive à la dernière étape. Peu importe si vous faites la gauche ou la droite en premier à moins que / il y ait une plus grande probabilité pour qu'un côté soit inégal, dans ce cas, vous devriez d'abord vérifier ce côté. Cela pourrait être le cas si, par exemple, la plupart des modifications ont été ajoutées à la branche droite de l'arbre ....
  • Faites de la comparaison une méthode statique. En effet, vous voulez l'utiliser récursivement de manière à accepter les valeurs NULL comme l'un des deux paramètres (par conséquent, cette méthode ne convient pas à une méthode d'instance, car this ne peut pas être null). En outre, la machine virtuelle Java est très efficace pour optimiser les méthodes statiques.

Mon implémentation serait donc quelque chose comme:

public static boolean treeEquals(Node a, Node b) {
    // check for reference equality and nulls
    if (a == b) return true; // note this picks up case of two nulls
    if (a == null) return false;
    if (b == null) return false;

    // check for data inequality
    if (a.data != b.data) {
        if ((a.data == null) || (b.data == null)) return false;
        if (!(a.data.equals(b.data))) return false;
    }

    // recursively check branches
    if (!treeEquals(a.left, b.left)) return false;
    if (!treeEquals(a.right, b.right)) return false;

    // we've eliminated all possibilities for non-equality, so trees must be equal
    return true;
}
20
mikera

Pour tout arbre, le moyen le plus efficace de le représenter pour que vous puissiez facilement vérifier l’égalité est la liste parente - conservez un tableau dans lequel, pour chaque sommet, vous vous souvenez de l’index de son parent (en fait, une paire - l’index du père et la valeur de données). Ensuite, vous devriez simplement faire une comparaison de deux blocs de mémoire continus.

Cela ne fonctionnera que si l’arbre est statique (c’est-à-dire que cela ne change pas dans le temps). De plus, les arbres ne seront égaux que si les index des sommets sont les mêmes dans les deux arbres.

Je crois que dans le cas habituel où les deux déclarations ci-dessus ne sont pas vraies, votre mise en œuvre devrait être aussi rapide que possible.

EDIT: en fait, votre mise en oeuvre peut être améliorée si vous suivez les conseils de la réponse de Jon Skeet (au moins, retournez false dès que vous savez que les arbres ne sont pas égaux).

3
Ivaylo Strandjev

Le code donné ci-dessus retournerait vrai pour deux arbres inégaux avec les mêmes valeurs racine Je ne pense pas que c'est ce que tu veux. Ça ne devrait pas être:

si (! a == b) renvoie false;

De cette façon, la méthode effectuerait le reste des vérifications.

(Impossible de se connecter d'ici pour une raison quelconque.)

0
Gerry Howser