[BOJ-9934] Arbre binaire complet (Golang)

[BOJ-9934] Arbre binaire complet (Golang)

21 mai 2025

Aperçu

En préparant un changement de carrière, j’ai revisité les algorithmes d’arbre et j’ai décidé de rédiger ce post.

Le problème d’arbre binaire complet consiste à déterminer la forme d’un arbre binaire complet à partir d’un tableau représentant l’ordre d’un parcours infixe. Il est donc essentiel de comprendre le concept de parcours infixe.

Parcours infixe

Les notions de parcours préfixe, infixe et postfixe se basent sur la position du nœud.

Nœud -> sous-arbre gauche -> sous-arbre droit
Dans cet ordre, le nœud est en premier, donc c’est un parcours préfixe.

Si l’on considère l’ordre sous-arbre gauche -> nœud -> sous-arbre droit, alors le nœud est au milieu, donc c’est un parcours infixe.

Et enfin, si l’on suit l’ordre sous-arbre gauche -> sous-arbre droit -> nœud, alors le nœud est le dernier, ce qui donne un parcours postfixe.

Le mode que nous allons examiner est le parcours infixe, dont un exemple est présent dans l’image suivante.

image

Arbre binaire complet

Un arbre binaire complet se définit par le fait que le nombre total de nœuds satisfait la condition 2^h-1 pour une hauteur d’arbre h donnée.

Un arbre binaire est un arbre dans lequel chaque nœud a au maximum 2 enfants.

En d’autres termes, il représente un arbre où tous les nœuds sont remplis jusqu’à la hauteur spécifiée.

Par exemple, pour un arbre de hauteur 1, il n’y a qu’un seul nœud comme illustré ci-dessous.

Nombre de nœuds = 2^1-1 = 1

image

Pour une hauteur de 2, cela donne un arbre de 3 nœuds comme suit.

image

Résolution du problème

Pour commencer, observons le résultat du parcours infixe.

L’arbre donné dans le problème est le suivant,

image

Un exemple d’entrée pour le résultat de ce parcours infixe est [1, 6, 4, 3, 5, 2, 7].

En regardant le tableau, on peut voir que le tableau lui-même ressemble à un arbre.

Prenons un tableau comme celui-ci,

image

En modifiant légèrement la hauteur ?

image

On commence à voir quelque chose.

image

Nous pouvons constater qu’il reflète exactement la forme de l’arbre que nous cherchions à créer.

Transformons cela en code.

Le but de ce problème est d’imprimer les nœuds par hauteur, donc nous devons d’abord trouver le nœud racine.

1. Trouver la nœud racine

Il est très simple de trouver le nœud racine d’un arbre binaire complet. Comme mentionné précédemment, il a toujours une longueur de 2^h-1, et comme indiqué précédemment, la forme du arbre à partir du parcours infixe est comprimée, donc le nœud racine est toujours situé au centre.

Dans le tableau [1, 6, 4, 3, 5, 2, 7], l’élément central est 3, ce qui indique qu’il s’agit du nœud racine, comme nous l’avons vérifié dans l’image précédente.

En code, cela se traduit par :

root := arr[len(arr/2)]

2. Trouver les nœuds enfants

Les nœuds enfants peuvent être divisés en gauche et droite selon l’indice du nœud racine.

Examinons à nouveau le tableau de parcours infixe.

Les enfants du nœud racine 3 sont 6 et 2 respectivement.

Nous pouvons constater que chaque nœud a un écart de 2 indices par rapport au nœud racine 3.

image

Examiner les enfants des enfants, 6 et 2 ont pour enfants respectivement 1, 4 et 5, 7.

Nous pouvons constater qu’ils sont chacun séparés de 1 indice.

image

Ainsi, en s’éloignant du parent, on constate que l’écart d’indices diminue. Cependant, il est encore difficile de déterminer selon quelle règle cela s’éloigne, donc ajoutons d’autres nœuds pour examiner la situation.

Pour simplifier la compréhension, supposons que le résultat du parcours infixe contienne des nombres consécutifs.

image

Le nœud racine est le centrale 8, et ses enfants sont respectivement 4 et 12.

On peut constater que l’écart d’indices est 4.

image

Investigons les enfants de 4 et 12, 4 a 2 et 6, tandis que 12 a 10 et 14.

L’écart d’indices ici est 2.

image

En continuant, 2 et 6 ont respectivement 1 et 3, ainsi que 5 et 7, tandis que 10 et 14 ont respectivement 9 et 11, et 13 et 15. L’écart d’indices est 1 dans tous les cas.

Nous pouvons voir que l’écart d’indices change selon la hauteur, où pour la racine de hauteur 1, l’écart est 4, pour la hauteur 2, l’écart est 2, et pour la hauteur 3, l’écart est 1.

Nous pouvons exprimer cela arithmétiquement, pour une hauteur totale d’arbre K, l’écart d’indices d’une hauteur h donnée est:

interval = 2^(K-h)

Design

Récapitulons les points (a), (b) et (c).

Tout d’abord, (a) trouvons le nœud racine, puis (b) trouvons les enfants séparés par interval à partir de la racine, puis (c) pour chaque nœud enfant, utilisons-le pour retrouver les nœuds enfants des enfants respectivement.

Solution

Il y a beaucoup de parties interconnectées, donc je vais les gérer via des commentaires dans le code.

package main

import (
    "bufio"
    "fmt"
    "log"
    "os"
)

func main() {
    writer := bufio.NewWriter(os.Stdout)
    reader := bufio.NewReader(os.Stdin)
    defer writer.Flush()

    // Obtention de la hauteur de l'arbre
    var K int
    if _, err := fmt.Fscanln(reader, &K); err != nil {
        log.Fatal(err)
    }

    // Calculer la longueur de l'arbre en fonction de sa hauteur et obtenir le tableau de parcours infixe
    length := 1<<K - 1
    inOrder := make([]int, length)
    for i := 0; i < length; i++ {
        if _, err := fmt.Fscan(reader, &inOrder[i]); err != nil {
            log.Fatal(err)
        }
    }

    // Indice initial du nœud racine : longueur totale / 2
    idxs := []int{length / 2}

    // Processus pour obtenir l'indice des nœuds enfants en fonction de la hauteur h
    for h := 1; h < K; h++ {
        children := make([]int, 0, 1<<(h))
        interval := 1 << (K - h - 1)
        for _, idx := range idxs {
            _, _ = fmt.Fprint(writer, inOrder[idx], " ") // Impression des nœuds enfants pour la hauteur h
            children = append(children, idx-interval, idx+interval) // Enregistrement des indices des nœuds enfants
        }
        idxs = children
        _, _ = fmt.Fprintln(writer)
    }

    // Impression des nœuds enfants de la dernière hauteur
    for _, idx := range idxs {
        _, _ = fmt.Fprint(writer, inOrder[idx], " ")
    }
}

Résultat

image

Conclusion

Nous avons réussi à résoudre le problème d’arbre binaire complet. Cette méthode de résolution a été inspirée par la solution d’une autre personne. Au début, j’avais en fait l’intention de créer un arbre d’indices fictifs et d’exécuter un parcours infixe, puis d’utiliser ces indices pour déduire l’arbre d’origine.

Cependant, j’ai trouvé que cette méthode était plus concise et plus propre, donc j’ai adopté cette approche.

Vous pouvez consulter le code de la solution de problème que j’ai rédigée en Go sur YangTaeyoung/algorithm.

Références