black-red-trees.md 9,18 ko
Newer Older
sbalev's avatar
sbalev a validé
# Arbres rouge-noire (ARN)

sbalev's avatar
sbalev a validé
Les arbres équilibrés sont des ABR pour lesquels $`h = O(\log n)`$. Parmi les différents types d'arbres équilibrés on va s'intéresser aux ARN où chaque nœud possède une couleur.
sbalev's avatar
sbalev a validé

## Définitions et propriétés

Pour simplifier les définitions et les algorithmes qui suivent, on va considérer les `null` comme des feuilles externes qu'on notera par ☒. Ainsi chaque nœud interne a deux fils.

sbalev's avatar
sbalev a validé
---

**Définition** Un *arbre rouge-noir (ARN)* est un ABR tel que :
sbalev's avatar
sbalev a validé
  1. Chaque nœud est soit rouge, soit noir.
  2. La racine est noire.
sbalev's avatar
sbalev a validé
  3. Les feuilles ☒ sont noires.
sbalev's avatar
sbalev a validé
  4. Si un nœud est rouge, alors ses deux fils sont noirs.
  5. Pour chaque nœud, tous les chemins le reliant à des feuilles contiennent le même nombre de nœuds noirs.

À partir de (4) et (5) on voit déjà que l'arbre est « équilibré » dans le sens où pour chaque sous-arbre, la branche la plus longue est au plus deux fois plus longue que la branche la plus courte.

sbalev's avatar
sbalev a validé
---

**Définition** La *hauteur noire* d'un nœud $`x`$ (on note $`hn(x)`$) est le nombre de nœuds noirs (sans compter $`x`$) sur le chemin de $`x`$ vers une feuille. La hauteur noire d'un arbre est la hauteur noire de sa racine.

---
sbalev's avatar
sbalev a validé

**Exemple**

```mermaid
graph TD
  50((50)) --- 32 & 80
  32((32)) --- 26 & 40
  80((80)) --- 58 & 92
  26((26)) --- 18 & 30
  40((40)) --- 36 & 44
  58((58)) --- 54 & 74
  92((92)) --- s01[ ] & s02[ ]
  18((18)) --- 12 & 22
  30((30)) --- 28 & s03[ ]
  36((36)) --- s04[ ] & 38
  44((44)) --- s05[ ] & s06[ ]
  54((54)) --- s07[ ] & s08[ ]
  74((74)) --- 68 & 76
  12((12)) --- 4 & s09[ ]
  22((22)) --- s10[ ] & s11[ ]
  28((28)) --- s12[ ] & s13[ ]
  38((38)) --- s14[ ] & s15[ ]
  68((68)) --- s16[ ] & s17[ ]
  76((76)) --- s18[ ] & s19[ ]
  4((4)) --- s20[ ] & s21[ ]


sbalev's avatar
sbalev a validé
  classDef black fill:#000, stroke:#fff;
sbalev's avatar
sbalev a validé
  classDef red fill:#f00, stroke:#fff;
sbalev's avatar
sbalev a validé
  class 50,80,26,40,92,30,36,44,54,74,s01,s02,12,22,s03,s04,s05,s06,s07,s08,s09,s10,s11,s12,s13,s14,s15,s16,s17,s18,s19,s20,s21 black;
  class 32,58,18,28,38,68,76,4 red;
```
sbalev's avatar
sbalev a validé

sbalev's avatar
sbalev a validé
---
sbalev's avatar
sbalev a validé

En plus d'attributs `cle`, `pere`, `gauche` et `droit` des nœuds d'un ABR, on ajoute un attribut supplémentaire `couleur` (1 bit) qui peut prendre les valeurs `N` ou `R`.

Le fait de remplacer `null` par des nœuds ☒ noirs nous permet de simplifier nos algorithmes en écrivant par exemple

```java
if (x.couleur == N) { ... }
```

au lieu de

```java
if (x == null || x.couleur == N) { ... }
```

De l'autre coté, si on utilise un nœud différent pour chaque feuille, on gaspille de la mémoire. C'est pourquoi on peut remplacer tous les ☒ par un nœud unique, la *sentinelle*.

```mermaid
graph TD
  r(( ))
  r -.- f1(( )) & f2(( )) & f3(( ))
sbalev's avatar
sbalev a validé
  r & f1 & f2 & f3 --> sentinelle[ ]
sbalev's avatar
sbalev a validé

sbalev's avatar
sbalev a validé
  style sentinelle fill:#000;
sbalev's avatar
sbalev a validé
```
sbalev's avatar
sbalev a validé

---

**Proposition** Pour tout nœud $`x`$, le sous-arbre de racine $`x`$ contient au moins $`2^{hn(x)} - 1`$ nœuds internes.


À démontrer par récurrence sur $`h(x)`$, la hauteur du sous-arbre de racine $`x`$.

---

**Proposition** La hauteur d'un ARN ayant $`n`$ nœuds internes est au plus $`2\log_2(n + 1)`$.

---

**Corollaire** Les opérations `rechercher()`, `minimum()`, `maximum()`, `successeur()` et `predecesseur()` s'exécutent en $`O(\log n)`$.

---

## Rotations

Pour ajouter ou supprimer des clés, on va se baser sur les opérations `ajouter()` et supprimer des ABR. Celles-ci ne préservent pas forcement les propriétés (1)-(5). Si certaines de ces propriétés sont violées, on va les réparer en faisant des recoloriages et des *rotations*.

```mermaid
sbalev's avatar
sbalev a validé
graph TD
sbalev's avatar
sbalev a validé
  subgraph T1
sbalev's avatar
sbalev a validé
  x1((x)) --- a1[/a\] & y1((y))
  y1 --- b1[/b\] & c1[/c\]
  end

sbalev's avatar
sbalev a validé
  subgraph T2
sbalev's avatar
sbalev a validé
  y2((y)) --- x2((x)) & c2[/c\]
  x2 --- a[/a\] & b[/b\]
  end
sbalev's avatar
sbalev a validé
```
sbalev's avatar
sbalev a validé

  * `rotationGauche(x)` : `T1 -> T2`
  * `rotationDroite(y)` : `T2 -> T1`
sbalev's avatar
sbalev a validé

---

**Proposition** Les rotations préservent la propriété des ABR.

---

**Complexité :** $`O(1)`$
sbalev's avatar
sbalev a validé


## Ajout

sbalev's avatar
sbalev a validé
On commence par l'ajout classique dans un ABR légèrement modifié :

```java
ajouter(Noeud z) {
  y = ;
  x = racine;
  while (x != ) {
    y = x;
    x = z.cle < x.cle ? x.gauche : x.droit;
  }
  z.pere = y;
  if (y == ) { // arbre vide
    racine = z;
  } else {
    if (z.cle < y.cle)
      y.gauche = z;
    else
      y.droit = z;
  }
  z.gauche = z.droit = ;
  z.couleur = R;
  ajouterCorrection(z);
}
```

Les modifications sont :
  * on remplace les `null` par `☒`
  * on colorie le nouveau nœud `z` en rouge
  * comme ce coloriage risque de violer certaines propriétés RN, on appelle une procédure qui les restaure.

Avant de donner le pseudo-code de `ajouterCorrection()` voyons quelles propriétés RN risquent d'être violées :
  1. OK
  2. Si l'arbre était vide `z` devient sa racine. C'est facile à réparer, il suffit de le colorier en noir
  3. OK
sbalev's avatar
sbalev a validé
  4. Si `y` (le père de `z`) est rouge, cette propriété est violée.
sbalev's avatar
sbalev a validé
  5. OK

Voici comment on répare :

```java
ajouterCorrection(Noeud z) {
  while (z.pere.couleur == R) {
sbalev's avatar
sbalev a validé
    // (*) La seule propriété RN violée est (4) : z et z.pere sont rouges
sbalev's avatar
sbalev a validé
    if (z.pere == z.pere.pere.gauche) {
      y = z.pere.pere.droit; // l'oncle de z
      if (y.couleur == R) {
        // cas 1
        z.pere.couleur = N;
        y.couleur = N;
        z.pere.pere.couleur = R;
        z = z.pere.pere;
      } else {
        if (z == z.pere.droit) {
          // cas 2
          z = z.pere;
          rotationGauche(z);
        }
        // cas 3
        z.pere.couleur = N;
        z.pere.pere.couleur = R;
        rotationDroite(z.pere.pere);
      }
    } else {
      // idem en miroir, gauche <-> droite
      // cas 1', 2', 3'
    }
  }
sbalev's avatar
sbalev a validé
  // (**) La seule propriété (potentiellement) violée est (2)
sbalev's avatar
sbalev a validé
  racine.couleur = N;
}
```

---

**Proposition**
sbalev's avatar
sbalev a validé
   * (*) Au début de chaque itération de la boucle `while` la seule propriété RN violée est (4) : `z` et `z.pere` sont tous les deux rouges.
   * (**) À la fin de la boucle, la seule propriété potentiellement violée est (2) et la dernière instruction la répare.
sbalev's avatar
sbalev a validé

---

Pour démontrer cette proposition, on va regarder ce qui se passe dans les cas 1, 2 et 3. Les cas 1', 2' et 3' sont symétriques.

**Cas 1** `y` (l'oncle de `z`) est rouge.

sbalev's avatar
sbalev a validé
Dans le schéma ci-dessous `z` est fils gauche. Le cas où `z` est fils droit est identique.
sbalev's avatar
sbalev a validé

sbalev's avatar
sbalev a validé
```mermaid
sbalev's avatar
sbalev a validé
graph TD
sbalev's avatar
sbalev a validé
  subgraph 0[Avant]
sbalev's avatar
sbalev a validé
    R1[" "] --- C1((C)) --- A1((A)) & D1((D))
sbalev's avatar
sbalev a validé
    A1 --- B1(("B")) & c1[/c\]
    B1 --- a1[/a\] & b1[/b\]
    D1 --- d1[/d\] & e1[/e\]
  end
  y1[y] -.-> D1
  z1[z] -.-> B1

  subgraph 1[Après]
    R2[" "] --- C2((C)) --- A2((A)) & D2((D))
    A2 --- B2((B)) & c2[/c\]
    D2 --- d2[/d\] & e2[/e\]
    B2 --- a2[/a\] & b2[/b\]
sbalev's avatar
sbalev a validé
  end
sbalev's avatar
sbalev a validé

  z2[z] -.-> C2

  classDef black fill:#000, stroke:#fff
  classDef red fill:#f00, stroke:#fff
  classDef invisible fill:#0000, stroke:#0000
  classDef pointer stroke-dasharray: 5 5, fill:#0000
sbalev's avatar
sbalev a validé
  class C1,A2,D2 black
  class A1,B1,D1,C2,B2 red
sbalev's avatar
sbalev a validé
  class R1,R2 invisible
  class z1,y1,z2 pointer
sbalev's avatar
sbalev a validé
```
sbalev's avatar
sbalev a validé

sbalev's avatar
sbalev a validé
   * Cette transformation préserve (1), (2), (3) et (5) (à vérifier)
sbalev's avatar
sbalev a validé
   * Si le père de C est rouge, on entame une nouvelle itération avec (*)
   * Si le père de C est noir, on sort de la boucle avec (**)

sbalev's avatar
sbalev a validé
**Cas 2** `y` (l'oncle de `z`) est noir et `z` est fils droit.
sbalev's avatar
sbalev a validé

```mermaid
graph TD
  subgraph 0[Avant]
    R1[" "] --- C1((C)) --- A1((A)) & D1((D))
    A1 --- a1[/a\] & B1(("B"))
    B1 --- b1[/b\] & c1[/c\]
    D1 --- d1[/d\] & e1[/e\]
  end
  y1[y] -.-> D1
  z1[z] -.-> B1

  subgraph 1[Après]
    R2[" "] --- C2((C)) --- B2((B)) & D2((D))
    B2 --- A2((A)) & c2[/c\]
    A2 --- a2[/a\] & b2[/b\]
    D2 --- d2[/d\] & e2[/e\]
  end

  y2[y] -.-> D2
  z2[z] -.-> A2

  classDef black fill:#000, stroke:#fff
  classDef red fill:#f00, stroke:#fff
  classDef invisible fill:#0000, stroke:#0000
  classDef pointer stroke-dasharray: 5 5, fill:#0000
  class C1,D1,C2,D2 black
  class A1,A2,B1,B2 red
  class R1,R2 invisible
sbalev's avatar
sbalev a validé
  class z1,y1,z2,y2 pointer
sbalev's avatar
sbalev a validé
```

sbalev's avatar
sbalev a validé
  * Cette transformation préserve (1), (2), (3) et (5) (à vérifier)
  * `z` devient fils gauche et on obtient le cas 3

**Cas 3** `y` (l'oncle de `z`) est noir et `z` est fils gauche.

```mermaid
graph TD
  subgraph 0[Avant]
    R1[" "] --- C1((C)) --- B1((B)) & D1((D))
    B1 --- A1((A)) & c1[/c\]
    A1 --- a1[/a\] & b1[/b\]
    D1 --- d1[/d\] & e1[/e\]
  end

  y1[y] -.-> D1
  z1[z] -.-> A1
sbalev's avatar
sbalev a validé

sbalev's avatar
sbalev a validé
  subgraph 1[Après]
    R2[" "] --- B2((B)) --- A2((A)) & C2((C))
    A2 --- a2[/a\] & b2[/b\]
    C2 --- c2[/c\] & D2((D))
    D2 --- d2[/d\] & e2[/e\]
  end

  z2[z] -.-> A2

  classDef black fill:#000, stroke:#fff
  classDef red fill:#f00, stroke:#fff
  classDef invisible fill:#0000, stroke:#0000
  classDef pointer stroke-dasharray: 5 5, fill:#0000
  class C1,D1,B2,D2 black
  class A1,B1,A2,C2 red
  class R1,R2 invisible
  class z1,y1,z2,y2 pointer
```
sbalev's avatar
sbalev a validé

sbalev's avatar
sbalev a validé
(1) - (5) sont restaurés (à vérifier) et on sort de la boucle.

À noter que les cas 1, 2, et 3 ne sont pas mutuellement exclusifs. Voici les différentes possibilités de déroulement d'une itération de la boucle :

```mermaid
graph TD
  start(("*")) ---> Cas1[Cas 1] & Cas2[Cas 2] & Cas3[Cas 3]
  Cas1 ---> start & stop(("**"))
  Cas2 ---> Cas3
  Cas3 ---> stop
```

**Complexité** Dans le cas 1 on remonte à deux niveaux dans l'arbre, dans les deux autres cas on s'arrête directement. Au pire des cas on fait $`O(h) = O(\log n)`$ itérations.


sbalev's avatar
sbalev a validé
## Suppression

TODO