Explication détaillée de l'arbre rouge-noir (implémentation C/C++)

L'arbre rouge-noir a un large éventail d'utilisations, telles que map\epoll\timer\Nginx\CFS\memory management, l'arbre rouge-noir est utilisé pour gérer les nœuds

L'arbre rouge-noir est un arbre de recherche binaire presque équilibré. Il n'a pas le concept de facteur d'équilibre de l'arbre AVL. Il ne maintient une structure presque équilibrée qu'en satisfaisant cinq propriétés, garantissant que le chemin le plus long ne dépasse pas le double du plus court. chemin.

Applicable aux scénarios qui nécessitent un tri , l'arbre rouge-noir n'a pas besoin de former une liste chaînée comme un arbre de recherche binaire dans les cas extrêmes, ce qui entraîne une complexité temporelle O(n), et n'a pas besoin de maintenir l'équilibre de l'arbre comme un arbre de recherche équilibré consommation de performances

insérez la description de l'image ici

nature

  • Les nœuds sont rouges ou noirs

  • la racine est noire

  • Les nœuds feuilles sont tous noirs (les nœuds feuilles font référence aux nœuds vides les plus bas, les nœuds nuls)

  • Les nœuds enfants du nœud rouge sont tous noirs

    • Le nœud parent du nœud rouge est noir

    • Il n'y aura pas deux nœuds rouges consécutifs sur le chemin du nœud racine au nœud feuille

  • Tous les chemins de n'importe quel nœud à chacun de ses nœuds feuilles contiennent le même nombre de nœuds noirs

gaucher droitier

insérez la description de l'image ici

Main gauche:

  • Connectez le sous-arbre gauche d'origine de y au sous-arbre droit de x

  • Pointez le nœud parent de y vers le nœud parent de x, et le nœud parent de x pointe vers x au lieu de pointer vers y

  • Connectez x au nœud de sous-arbre gauche de y

Rotation à droite :

  • Connectez le sous-arbre droit d'origine de x au sous-arbre gauche de y
  • Pointez le nœud parent de x vers le nœud parent de y, et le nœud parent de y pointe vers y au lieu de pointer vers x
  • Connectez y au nœud droit du sous-arbre de x

Code

// 左旋
void leftRotate(RbTree *T, RbTreeNode *x) {
    RbTreeNode *y = x->right;

    // 1.将y原来的左子树接到x的右子树下
    x->right = y->left;
    if (y->left != T->nil) {
        y->left->parent = x;
    }

    // 2.将y的父节点指向x的父节点,x的父节点原来指向x改为指向y
    y->parent = x->parent;
    if (x->parent == T->nil) {
        // 此时Y是根节点
        T->root = y;
    } else if (x == x->parent->left) {
        x->parent->left = y;
    } else {
        x->parent->right = y;
    }

    // 3.将x接到y的左子树下
    y->left = x;
    x->parent = y;
}

// 右旋
void rightRotate (RbTree *T, RbTreeNode *y) {
    RbTreeNode* x = y->left;

    // 1.将x原来的右子树接到y的左子树下
    y->left = x->right;
    if (x->right != T->nil) {
        x->right->parent = y;
    }

    // 2.将x的父节点指向y的父节点,y的父节点原来指向y改为指向x
    x->parent = y->parent;
    if (y->parent == T->nil) {
        T->root = x;
    } else if (y->parent->left == y) {
        y->parent->left = x;
    } else {
        y->parent->right = x;
    }

    // 3.将y接到x的右子树下
    x->right = y;
    y->parent = x;
}

Dans le code gaucher droitier, seules gauche et droite sont échangées, x et y sont échangés, et tout le reste est identique

augmenter le nœud

Tous sont ajoutés aux nœuds feuilles, et finalement ajustés

Les nœuds ajoutés sont rouges par défaut et n'affecteront pas le nombre de nœuds noirs sur le chemin

Raison : Pour éviter d'avoir à tourner à chaque fois que vous rejoignez

void rbTreeInsert(RbTree *T, RbTreeNode *newNode) {
    RbTreeNode* node = T->root;
    RbTreeNode* parent = T->nil;

    // 寻找可以插入的位置 找到叶子节点
    while (node != T->nil) {
        parent = node;
        if (newNode->key < node->key) {
            node = node->left
        } else if (newNode->key > node->key) {
            node = node->right;
        } else {
            return; // 具体相同的值要不要改动看需求
        }
    }

    newNode->parent = parent;
    if (parent == T->nil) {
        T->root = newNode;
    } else if (parent->key > newNode->key) {
        parent->left = newNode;
    } else {
        parent->right = newNode;
    }
    newNode->left = T->nil;
    newNode->right = T->nil;
    newNode->color = RED;

    rbTreeInsertFixup(T,newNode);
}

ajustements d'arbre

Lorsque le nœud parent est un nœud rouge, il doit être ajusté, car il enfreint la règle 4

Il existe trois situations dans le processus d'ajustement :

  • Les nœuds de l'oncle sont rouges
  • Le nœud oncle est noir et le nœud parent du nœud actuel est le sous-arbre de gauche
  • Le nœud oncle est noir et le nœud parent du nœud actuel est le sous-arbre droit

Si le nœud oncle est rouge, il n'est pas nécessaire de faire la distinction entre gauche et droite, il n'y a pas de différence. À ce moment-là, le nœud grand-père sera changé en rouge, et le nœud parent et le nœud oncle seront changés en noir pour continuer régler vers le haut

Mais s'il est noir, il doit être distingué, car la couleur du nœud parent et du nœud grand-parent entraînera la modification de la hauteur du chemin noir

Situation actuelle connue :

  • Le nœud actuel est rouge
  • le nœud parent est rouge
  • Les nœuds grand-père sont noirs

Les nœuds de l'oncle sont rouges

insérez la description de l'image ici

Changez le père et l'oncle en noir. À ce moment, la longueur du chemin noir change et le grand-père doit être changé en rouge.

Continuez vers le haut depuis le grand-père

le père est l'enfant gauche du grand-père

Self est l'enfant gauche du père :

insérez la description de l'image ici

Changez le père en noir. A ce moment, le chemin noir sur la gauche change et le grand-père doit être changé en rouge

À ce moment, le chemin noir du sous-arbre droit est réduit et le grand-père doit être tourné vers la droite, et le nœud parent devient le nouveau grand-père pour restaurer la hauteur noire du sous-arbre droit.

Le moi est le bon enfant du père :

insérez la description de l'image ici

Changer le père vers la gauche pour changer le père en son propre enfant gauche, cela devient la situation ci-dessus

le père est le droit enfant du grand-père

Le moi est le bon enfant du père :

insérez la description de l'image ici

Transformez le père en noir, à ce moment le chemin noir à droite s'allonge et transformez le grand-père en rouge

A ce moment, le chemin noir sur la gauche devient plus court et la hauteur du sous-arbre gauche est récupérée en effectuant une rotation vers la gauche.

Self est l'enfant gauche du père :

insérez la description de l'image ici

Effectuer une rotation à droite sur le père fait du père son propre enfant de droit et devient la situation ci-dessus

// 调整树
void rbTreeInsertFixup(RbTree *T, RbTreeNode *node) {
    // 如果父节点是黑色的则不需要调整
    while (node->parent->color == RED) {
        // 父节点是祖父节点的左子树
        if (node->parent == node->parent->parent->left) {
            // 叔父节点
            RbTreeNode *uncle = node->parent->parent->right;
            if (uncle->color == RED) {
                // 将父节点改为黑色和叔父节点改为黑色,祖父节点改为红色继续向上调整就可以了
                // 会影响到黑色路径的长度
                node->parent->color = BLACK;
                uncle->color = BLACK;
                node->parent->parent->color = RED;
                // 继续向上调整
                node = node->parent->parent;
            } else {
                if (node == node->parent->right) {
                    // 左旋
                    node = node->parent;
                    leftRotate(T,node);
                }
                // 让父节点变为黑色,祖父节点变为红色(右子树的黑色高度变低了)
                // 对祖父右旋,让父节点成为新祖父,恢复右子树的高度
                // 这种情况只需要两次旋转就可以完成调整了
                node->parent->color = BLACK;
                node->parent->parent->color = RED;
                rightRotate(T,node->parent->parent);
            }
        } else {
            RbTreeNode *uncle = node->parent->parent->left;
            if (uncle->color == RED) {
                node->parent->color = BLACK;
                uncle->color = BLACK;
                node->parent->parent->color = RED;
                node = node->parent->parent;
            } else {
                if (node == node->parent->left) {
                    node = node->parent;
                    rightRotate(T,node);
                }
                // 左子树的高度减小了
                node->parent->color = BLACK;
                node->parent->parent->color = RED;
                leftRotate(T,node->parent->parent);
            }
        }
    }

    T->root->color = BLACK;
}

supprimer le nœud

Définissez trois nœuds :

  • Nœud de superposition Z : le nœud désigné pour être supprimé est en fait réalisé en étant écrasé
  • Supprimer le nœud Y : le nœud qui est réellement supprimé, généralement le nœud successeur
  • Noeud d'axe X : le noeud qui maintient l'arbre rouge-noir

Il existe trois situations possibles pour les nœuds supprimés :

  • Pas de sous-arbres gauche et droit : supprimer directement
  • Il n'y a qu'un seul sous-arbre : suspendez le seul sous-arbre du nœud au nœud parent, puis supprimez le nœud.
  • Il existe à la fois des sous-arbres gauche et droit : trouvez un nœud successeur Y pour couvrir le nœud spécifié Z, puis supprimez le nœud Y, Y est l'un des cas 1 ou 2

Si le nœud courant est le sous-arbre gauche du nœud parent, quatre situations peuvent être résumées. De même, si le nœud courant est le sous-arbre droit du nœud parent, nous pouvons également résumer quatre situations. Mais la différence entre ces quatre cas est la différence de sens de rotation (miroir). Il y a un total de 8 situations, mais elles sont en fait de 4 sortes lorsqu'elles sont additionnées.

Le nœud courant est le sous-arbre gauche du nœud parent

Cas 1 : Le nœud frère du nœud actuel est rouge

Changez le frère en noir et le père en rouge. À ce moment, la hauteur du côté gauche est réduite et la hauteur du sous-arbre gauche est restaurée en tournant à gauche vers le nœud parent. À ce moment, l'enfant gauche du frère devient un nouveau frère, ce qui devient la situation 2, 3 et 4

(Parce que le frère de x doit être noir, il ne peut donc être obtenu qu'à partir des neveux gauche et droit)

insérez la description de l'image ici

Situation 2 : Le frère est noir et les neveux gauche et droit sont également noirs

À ce moment, le frère et lui-même sont noirs et le nœud parent est rouge ou noir

Changez le frère en rouge et le nœud de l'axe pour devenir le nœud parent (le chemin où se trouve le nœud de l'axe d'origine n'a pas de nœud noir, alors tournez le frère en rouge, et le chemin noir du nœud parent est le même à ce temps, mais le chemin entre le nœud parent Le chemin ci-dessus manque un nœud noir, alors changez le nœud de l'axe en nœud parent et continuez à ajuster)

insérez la description de l'image ici

Cas 3 : le frère est noir, le neveu droit est noir, le neveu gauche est rouge

Transformez le neveu gauche en noir et le frère en rouge. À ce moment, la hauteur noire du sous-arbre droit diminue et le nœud frère continue de tourner à droite pour restaurer la hauteur. À ce moment, le neveu gauche devient le nouveau droit frère

A ce moment, le fils droit du frère est rouge, ce qui répond à la situation 4. Continuez à ajuster selon la méthode de la situation 4

insérez la description de l'image ici

Situation 4 : le frère est noir, le neveu droit est rouge, le neveu gauche est rouge ou noir

Changez la couleur du frère pour qu'elle soit la même que celle du nœud parent, le neveu droit et le nœud parent sont tous les deux noirs

Afin de s'assurer que la hauteur du nœud parent reste inchangée après qu'il soit devenu noir, le nœud parent doit être tourné vers la gauche

x pointe vers le nœud racine, la boucle se termine

Pourquoi changer le neveu droit en noir : parce que le neveu droit devient un nouveau frère après la rotation gauche, remplaçant le frère d'origine, il doit donc être de la même couleur que le frère, c'est-à-dire noir

Pourquoi changer le frère pour qu'il soit identique au nœud parent : parce que le frère remplace le nœud parent après une rotation à gauche

Pourquoi changer le nœud parent en noir : parce que le nœud parent est gaucher pour compenser le nœud noir supprimé

À ce stade, le chemin est restauré

insérez la description de l'image ici

Le nœud courant est le sous-arbre droit du nœud parent

Comme dans le cas ci-dessus, en miroir

Résumer

Il existe deux situations qui provoquent la sortie de la boucle sans ajustement :

Dans le second cas, le nœud de l'axe devient le nœud parent. Si le nœud parent est rouge, alors vous pouvez quitter et changer le nœud parent en noir pour restaurer la hauteur.

Situation 4 : utilisez le nœud parent pour compenser la hauteur du nœud supprimé, puis définissez le nœud d'axe comme nœud racine pour quitter la boucle.

Chaque situation est aussi proche que possible de la quatrième situation, réalisant la situation que le frère est noir et le bon neveu est rouge

le code

// 寻找x节点的最左节点
RbTreeNode* rbTree_mini(RbTree *T, RbTreeNode *x) {
    while (x->left != T->nil) {
        x = x->left;
    }
    return x;
}

// 寻找覆盖节点的右子树的最左节点
RbTreeNode* rbTree_successor(RbTree *T, RbTreeNode* x) {
    RbTreeNode *y = x->parent;

    // 寻找x节点的右子树的最左节点
    if (x->right != T->nil) {
        return rbTree_mini(T,x->right);
    }
}

void rbTree_delete_fixup(RbTree *T, RbTreeNode* x) {
    while ((x != T->root) && (x->color == BLACK)) {
        if (x == x->parent->left) {
            // w为兄弟节点
            RbTreeNode *w = x->parent->right;
            if (w->color == RED) {
                w->color = BLACK;
                x->parent->color = RED;
                leftRotate(T,x->parent);
                w = x->parent->right;
            }
            if ((w->left->color == BLACK) && (w->right->color == BLACK)) {
                w->color = RED;
                x = x->parent;
            } else {
                if (w->right->color == BLACK) {
                    w->left->color = BLACK;
                    w->color = RED;
                    rightRotate(T,w);
                    w = x->parent->right;
                }
                w->color = x->parent->color;
                x->parent->color = BLACK;
                w->right->color = BLACK;
                leftRotate(T,x->parent);
                x = T->root;
            }
        } else {
            RbTreeNode *w = x->parent->left;
            if (w->color == RED) {
                w->color = BLACK;
                x->parent->color = RED;
                rightRotate(T,x->parent);
                w = x->parent->left;
            }
            if ((w->left->color == BLACK) && (w->right->color == BLACK)) {
                w->color = RED;
                x = x->parent;
            } else {
                if (w->left->color == BLACK) {
                    w->right->color = BLACK;
                    w->color = RED;
                    leftRotate(T,w);
                    w = x->parent->left;
                }

                w->color = x->parent->color;
                x->parent->color = BLACK;
                w->left->color = BLACK;
                rightRotate(T,x->parent);

                x = T->root;
            }
        }
    }
    x->color = BLACK;
}

// 删除节点
RbTreeNode* rbTreeDelete(RbTree *T, RbTreeNode *z) {
    // z是被覆盖的节点 y是删除节点 x是轴心节点负责旋转
    RbTreeNode *y = T->nil;
    RbTreeNode *x = T->nil;

    // 此时如果没有子树或只有一颗子树 那么就是要被删除的节点
    if ((z->left == T->nil) || (z->right == T->nil)) {
        y = z;
    } else {
        // 寻找删除节点 也就是覆盖节点的右子树的最左节点
        y = rbTree_successor(T,z);
    }

    // 找轴心节点 一般是被删除节点的左子节点
    if (y->left != T->nil) {
        x = y->left;
    } else if (y->right != T->nil) {
        x = y->right;
    }

    x->parent = y->parent;
    if (y->parent == T->nil) {
        // 如果y是根节点 那么x就成为新的根节点
        T->root = x;
    } else if (y == y->parent->left) {
        y->parent->left = x;
    } else {
        y->parent->right = x;
    }

    // 覆盖节点
    if (y != z) {
        z->key = y->key;
        z->value = y->value;
    }

    // 如果删除节点是黑色节点则需要调整
    if (y->color == BLACK) {
        rbTree_delete_fixup(T,x);
    }

    return y;
}

complexité temporelle

La complexité temporelle de l'arbre rouge-noir :

  • Élément de requête O(logN)
  • Insertion d'éléments : au plus deux rotations sont nécessaires, au moins une rotation peut être effectuée et parfois aucun réglage n'est requis
  • Supprimer des éléments : au plus trois rotations sont nécessaires, au moins une rotation peut être effectuée et parfois aucun ajustement n'est requis

Complexité temporelle de l'arbre AVL :

  • Elément de requête O(logn)
  • Insertion des éléments : jusqu'à deux rotations pour préserver l'équilibre
  • Supprimer des éléments : l'arborescence AVL doit maintenir l'équilibre de tous les nœuds sur le chemin du nœud supprimé au nœud racine, ce qui nécessite beaucoup de rotation

Dans la mesure où l'insertion de nœuds entraîne un déséquilibre de l'arbre, AVL et RB-Tree ont au plus deux rotations d'arbre pour réaliser le rééquilibrage, et l'amplitude de la rotation est O (1)

La suppression d'un nœud provoque un déséquilibre. AVL doit maintenir l'équilibre de tous les nœuds sur le chemin du nœud supprimé à la racine du nœud racine. L'amplitude de la rotation est O (logN), tandis que RB-Tree n'a besoin que de tourner jusqu'à 3 O(1) est nécessaire, donc le rééquilibrage des nœuds de suppression de RB-Tree est plus efficace et moins coûteux !

Cependant, l'arbre rouge-noir évite la rotation en changeant la couleur du nœud.Le coût spécifique dépend du fait que le coût de changement de couleur est faible ou que le coût de changement de pointeur est faible.

Je suppose que tu aimes

Origine blog.csdn.net/blll0/article/details/129478623
conseillé
Classement