48 votes

Ce que supplémentaires de rotation est nécessaire pour la suppression de l'un de Haut en Bas 2-3-4 gauchisants Rouge Noir de l'arbre?

J'ai été mise en œuvre d'un LLRB paquet qui doit être en mesure de fonctionner dans les deux modes, Bas-2-3 ou de Haut en Bas 2-3-4 décrit par Sedgewick (code - amélioration du code, bien que ne traitant qu'avec 2-3 arbres ici, grâce à RS pour un pointeur).

Sedgewick fournit une description très claire de l'arbre des opérations pour les 2-3 mode, même si il passe beaucoup de temps à parler de l'2-3-4 mode. Il montre aussi comment une simple modification de l'ordre de la couleur de retournement lors de l'insertion peut modifier le comportement de l'arbre (split sur le chemin vers le bas pour 2-3-4 ou split sur le chemin pour 2-3):

private Node insert(Node h, Key key, Value value)
{
    if (h == null)
        return new Node(key, value);

    // Include this for 2-3-4 trees
    if (isRed(h.left) && isRed(h.right)) colorFlip(h);

    int cmp = key.compareTo(h.key);

    if (cmp == 0)     h.val = value;
    else if (cmp < 0) h.left = insert(h.left, key, value);
    else              h.right = insert(h.right, key, value);

    if (isRed(h.right) && !isRed(h.left))    h = rotateLeft(h);
    if (isRed(h.left) && isRed(h.left.left)) h = rotateRight(h);

    // Include this for 2-3 trees
    if (isRed(h.left) && isRed(h.right)) colorFlip(h);

    return h;
}

Cependant, il glisse sur la suppression dans 2-3-4 LLRBs avec les éléments suivants:

Le code sur la page suivante est la mise en œuvre complète de supprimer() pour LLRB 2-3 arbres. Il est basé sur l'inverse de l'approche utilisée pour les insérer dans le haut-vers le bas 2, 3 et 4 arbres: nous avons effectuer des rotations et de la couleur de la retourne sur le chemin vers le bas le chemin de recherche pour assurer que la recherche ne s'arrête pas sur un 2-noeud, de sorte que nous pouvons simplement de supprimer le nœud dans le bas. Nous utilisons la méthode de correction() pour partager le code pour la couleur flip et les rotations suivant les appels récursifs dans l'insert() code. Avec fixUp(), nous pouvons laisser de droite liens rouges et asymétrique à 4 nœuds le long du chemin de recherche, sûr que ces conditions seront fixées sur le chemin jusqu'à l'arbre. (L'approche est également efficace 2-3-4 arbres, mais nécessite une rotation lorsque le droit nœud hors du chemin de recherche est un 4-nœud.)

Son delete() fonction:

private Node delete(Node h, Key key)
{
    if (key.compareTo(h.key) < 0)
        {
            if (!isRed(h.left) && !isRed(h.left.left))
            h = moveRedLeft(h);
            h.left = delete(h.left, key);
        }
    else
        {
            if (isRed(h.left))
                h = rotateRight(h);
            if (key.compareTo(h.key) == 0 && (h.right == null))
                return null;
            if (!isRed(h.right) && !isRed(h.right.left))
                h = moveRedRight(h);
            if (key.compareTo(h.key) == 0)
                {
                    h.val = get(h.right, min(h.right).key);
                    h.key = min(h.right).key;
                    h.right = deleteMin(h.right);
                }
            else h.right = delete(h.right, key);
        }
    return fixUp(h);
}

Mon de mise en œuvre de l'entretient correctement LLRB 2-3 invariants pour tous les arbres des opérations sur 2-3 arbres, mais échoue pour une sous-classe de la côté droit les suppressions sur les arbres 2-3-4 (à défaut de ces suppressions résultat de droit en s'appuyant noeuds rouges, mais la boule de neige à l'arbre de déséquilibre et enfin un déréférencement de pointeur null). À partir d'une enquête de l'exemple de code qui traite de LLRB arbres et inclut des options pour la construction d'arbres en soit le mode, il semble qu'aucun correctement met en œuvre la suppression d'un 2-3-4 LLRB (c'est à dire qu'aucun n'a le supplément de rotation fait allusion, par exemple, Sedgewick java ci-dessus et ici).

J'ai du mal à comprendre exactement ce qu'il entend par "extra rotation lorsque le droit nœud hors du chemin de recherche est un 4-nœud"; on suppose que c'est une rotation à gauche, mais où et quand?

Si je tourne à gauche en passant vers le haut passé un 4-nœud équivalent (c'est à dire RR nœud) ou d'un droit en s'appuyant 3-nœud équivalent (BR nœud), soit avant l'appel de correction() ou à la fin de la correction de la fonction, je reçois toujours le même invariant de la contradiction.

Voici l'arbre des états de la plus petite à défaut d'exemples que j'ai trouvé (séquentiel généré par l'insertion d'éléments de 0 à la valeur maximale).

La première paire d'arbres montre la transition de l'invariant conforme de l'état avant la suppression de l'élément de 15 à cassé évidemment de l'état après.

A 2-3-4 LLRB containing 0..15

State after deleting item 15

La seconde est essentiellement le même que ci-dessus, mais avec la suppression de 16 de 0..16 (suppression de l'15 résultats dans la même topologie). Notez que l'invariant contradiction parvient à traverser le nœud racine.

A 2-3-4 LLRB containing 0..16

State after deleting item 16

La clé va être de comprendre comment revenir sur les violations générées lors de la marche en bas de l'arbre pour le nœud cible. Les deux arbres de montrer comment le premier arbre au-dessus de l'air après une promenade vers le bas, la gauche et la droite respectivement (sans suppression et avant de marcher jusqu'à fixUp()).

Après la tentative de suppression '-1', sans correction: After attempt to delete '-1' without fixUp

Après la tentative de suppression '16', sans correction: After attempt to delete '16' without fixUp

Essayer tourner à gauche sur le chemin de retour vers le haut quand le nœud n'a qu'un rouge à droite de l'enfant semble être une partie de la solution, mais elle ne traite pas correctement avec les deux red droit des enfants dans une rangée, précédant le présent avec un flipColor lorsque les deux enfants sont rouge semble améliorer la situation, mais laisse encore certains invariants violé.

Si j'ai plus de vérifier si le droit de l'enfant d'un droit de l'enfant est rouge quand son frère est noir et tourner à gauche si c'est vrai que je n'échouera une fois, mais à ce point je me sens comme je suis besoin d'une nouvelle théorie plutôt qu'une nouvelle epicycle.

Des idées?

Pour référence, mon application est disponible ici (Non, ce n'est pas Java).

Suivi:

Partie de la raison qui m'a intéressé dans ce a été de confirmer la demande par beaucoup que 2-3 LLRB arbres sont plus efficaces que les 2-3-4 LLRB arbres. Mon analyse comparative a confirmé l'existence de ce pour l'insertion et la suppression (2-3 sont environ 9% plus rapide), mais je trouve que la récupération est très légèrement plus rapide pour les arbres 2-3-4.

Les temps suivants sont représentatifs et cohérent à travers les pistes:

BU23:
BenchmarkInsert        1000000        1546 ns/op
BenchmarkDelete        1000000        1974 ns/op
BenchmarkGet           5000000         770 ns/op

TD234:
BenchmarkInsert        1000000        1689 ns/op
BenchmarkDelete        1000000        2133 ns/op
BenchmarkGet           5000000         753 ns/op

La première colonne est banc nom, le second est le nombre d'opérations, le troisième est le résultat. De référence sur i5M 2.27.

J'ai eu un coup d'oeil à la branche longueurs pour les 2-3 arbre et les arbres 2-3-4 et il y a peu que pour expliquer la récupération de la différence (distance moyenne de la racine au nœud et S. D. de 1000 arbres avec 10000 insertions aléatoires):

Means:
TD234 leafs  BU23 leafs 
 12.88940     12.84681 
TD234 all    BU23 all 
 11.79274     11.79163 

StdDev:
TD234 leafs  BU23 leafs 
 1.222458     1.257344 
TD234 all    BU23 all 
 1.874335     1.885204 

10voto

Tawnos Points 1556

Mises à jour et vérifiées

D'une importance clé pour les tests c'est que la mise en œuvre ne prend pas en charge la suppression d'un inexistante ou supprimés noeud! J'ai passé beaucoup trop de temps à essayer de comprendre pourquoi ma solution de travail a été "cassé". Ceci peut être résolu en faisant une recherche préliminaire pour la clé et retourner false si ce n'est pas dans l'arbre, et que la solution a été employé dans les liens code en bas.

Il ne semble pas Sedgewick a écrit une suppression pour 2-3-4 suppression qui est disponible publiquement. Ses résultats traitent spécifiquement 2-3 arbres (il ne fait que superficielle mention de 2-3-4 arbres qui leur moyenne de longueur de chemin d'accès (et donc les coûts de recherche), ainsi que d'autres arbres rouge-noir, est indissociable de l'2-3 cas). Personne d'autre ne semble avoir un facilement trouver, soit, voici ce que j'ai trouvé après de débogage, le problème:

Pour commencer, prenez Sedgewick du code et de fixer le sort de la date de bits. Dans les diapositives ici (page 31), vous pouvez voir que son code utilise encore l'ancienne représentation de 4 nœuds où elle a été faite par le fait d'avoir deux gauche rouges dans une rangée, plutôt que de l'équilibre. Le premier bit à écrire un 2-3-4 suppression de routine, alors, est de résoudre ce problème de manière que l'on peut faire un test de cohérence qui nous aidera à vérifier nos corrections plus tard:

private boolean is234(Node x)
{         
   if (x == null)
      return true;
   // Note the TD234 check is here because we also want this method to verify 2-3 trees
   if (isRed(x.right))
      return species == TD234 && isRed(x.left);

   if (!isRed(x.right))
      return true;

   return is234(x.left) && is234(x.right);
} 

Une fois que nous avons cela, nous le savons, un couple de choses. L'un, à partir de l'étude, nous voyons que 4 nœuds ne doivent pas être brisé à l'aller lors de l'utilisation d'un arbre 2-3-4. De deux, il y a un cas particulier pour un droit à 4 nœuds sur le chemin de recherche. Il y a un troisième cas particulier qui n'est pas mentionné, et c'est quand que vous allez sauvegarder un arbre, vous pourriez vous retrouver où vous avez h.right.left rouge, ce qui vous laisserait pas valide avec juste une rotation à gauche. C'est le miroir de l'affaire décrite pour l'insérer sur la page 4 du document.

La rotation correctif pour un 4-nœud que vous avez besoin est comme suit:

    private Node moveRedLeft(Node h)
    {  // Assuming that h is red and both h.left and h.left.left
       // are black, make h.left or one of its children red.
       colorFlip(h);
       if (isRed(h.right.left))
       { 
          h.right = rotateRight(h.right);

          h = rotateLeft(h);
          colorFlip(h);

          if (isRed(h.right.right) )
             h.right = rotateLeft(h.right);
       }
      return h;
    }

Et cela supprime le fractionnement sur 2, 3 et 4, ainsi que ajoute le correctif pour le troisième cas spécial

private Node fixUp(Node h)
{
   if (isRed(h.right))
   {      
      if (species == TD234 && isRed(h.right.left))
         h.right = rotateRight(h.right);
      h = rotateLeft(h);
   }

   if (isRed(h.left) && isRed(h.left.left))
      h = rotateRight(h);

   if (species == BU23 && isRed(h.left) && isRed(h.right))
      colorFlip(h);

   return setN(h);
}

Enfin, nous avons besoin de tester cette et assurez-vous qu'il fonctionne. Ils n'ont pas à être le plus efficace, mais comme je l'ai constaté lors du débogage de cela, ils ont à travailler avec les attendus de l'arbre de comportement (c'est à dire de ne pas insérer/supprimer des données en double)! Je l'ai fait avec un test de méthodes d'assistance. Les lignes commentées ont été là quand j'étais débogage, j'avais pause et vérifier l'arbre de déséquilibre évident. J'ai essayé cette méthode avec 100000 nœuds, et il a fonctionné parfaitement:

   public static boolean Test()
   {
      return Test(System.nanoTime());
   }
   public static boolean Test(long seed)
   {
      StdOut.println("Seeding test with: " + seed);
      Random r = new Random(seed);
      RedBlackBST<Integer, Integer> llrb = new RedBlackBST<Integer,Integer>(TD234);
      ArrayList<Integer> treeValues = new ArrayList<Integer>();
      for (int i = 0; i < 1000; i++)
      {
         int val = r.nextInt();
         if (!treeValues.contains(val))
         {
            treeValues.add(val);
            llrb.put(val, val);
         }
         else
            i--;
      }
      for (int i = 0; i < treeValues.size(); i++)
      {
         llrb.delete(treeValues.get(i));
         if (!llrb.check())
         {
            return false;
         }
//         StdDraw.clear(Color.GRAY);
//         llrb.draw(.95, .0025, .008);
      }
      return true;
   }

La source complète peut être trouvée ici.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X