Parcours d’arbre : Depth First & Breadth First 🌳
Parcours d’arbre : Depth First & Breadth First
Le parcours d’arbre est un grand classique lors d’entretiens algorithmiques et bien que cet exercice peut sembler intimidant au premier abord, nous allons dĂ©couvrir qu’il y a, en rĂ©alitĂ©, plus de peur que de mal !
Ainsi, dans cet article, nous nous intéressons aux deux méthodes les plus courantes afin de parcourir un arbre que sont :
- le parcours en profondeur (Depth First), oĂą le choix du prochain noeud se porte sur le premier enfant du noeud courant :
Parcours en profondeur
- le parcours en largeur (Breadth First), oĂą le prochain noeud est celui adjacent au noeud courant, c’est-Ă -dire celui situĂ© au mĂŞme niveau de profondeur :
Parcours en largeur
Parcours en profondeur - Depth first
Au niveau algorithmique, l’utilisation de la rĂ©cursivitĂ© reste le choix le plus pertinent. Ainsi, nous utiliserons la rĂ©cursivitĂ© pour notre parcours mais nous y ajouterons toutefois une Ă©tape supplĂ©mentaire pour les besoins de l’article, en stockant les noeuds dans une structure de donnĂ©es.
En effet, dès l’entrĂ©e dans un noeud et après le traitement de sa valeur, nous stockerons ses enfants dans une structure de donnĂ©es (contenant une mĂ©thode saveNode), rĂ©cupĂ©rerons ensuite le prochain noeud (mĂ©thode getNextNode) et passerons Ă la prochaine Ă©tape de notre parcours. En pseudo-code, nous obtenons ainsi :
// Structure de données encore inconnue
class Structure {
saveNode(node) {}
getNextNode() {}
}
// Fonction de traitement d'un noeud
var nodeStructure = new Structure()
function computeNode(node) {
if(!node) return;
computeNodeValue(node.value)
if(node.left) nodeStructure.saveNode(node.left)
if(node.right) nodeStructure.saveNode(node.right)
computeNode(nodeStructure.getNextNode())
}
// Création de l'arbre et début du parcours
var tree = new Tree(...)
computeNode(tree.root)
Maintenant, si nous dĂ©roulons un exemple plus concret avec l’arbre prĂ©sentĂ© en dĂ©but d’article et le pseudo-code ci-dessus :
1ère étape :
- Nous traitons la valeur du noeud : 1, nous sommes Ă la racine
- Nous ajoutons ses deux enfants dans la structure de données :
[2,5]
- Nous récupérons le prochain élément :
[2,5]
=> 2,[5]
2ème étape :
- Nous traitons la valeur du noeud : 2
- Nous ajoutons ses deux enfants dans la structure de données :
[3, 4, 5]
- Nous récupérons le prochain élément :
[3, 4, 5]
=> 3,[4,5]
3ème étape :
- Nous traitons la valeur du noeud : 3
- Pas d’enfant, la structure est inchangĂ©e :
[4, 5]
- Nous récupérons le prochain élément :
[4, 5]
=> 4,[5]
4ème étape :
- Nous traitons la valeur du noeud : 4
- Pas d’enfant, la structure est inchangĂ©e :
[5]
- Nous récupérons le prochain élément :
[5]
=> 5,[]
5ème étape :
- Nous traitons la valeur du noeud : 5
- Nous ajoutons ses deux enfants dans la structure de données :
[6,7]
- Nous récupérons le prochain élément :
[6,7]
=> 6,[7]
6ème étape :
- Nous traitons la valeur du noeud : 6
- Pas d’enfant, la structure est inchangĂ©e :
[7]
- Nous récupérons le prochain élément :
[7]
=> 7,[]
7ème étape :
- Nous traitons la valeur du noeud : 7
- Pas d’enfant, la structure est inchangĂ©e :
[]
- Nous récupérons le prochain élément :
[]
=> x,[]
8ème étape :
- Pas de valeur et la structure est vide : le parcours est terminé
Après ce long dĂ©roulĂ©, nous constatons que l’ordre d’ajout dans la liste Ă son importance : nous ne prenons que le noeud insĂ©rĂ© le plus rĂ©cemment. En algorithmique, ce concept est appelĂ© Last In First Out, abrĂ©gĂ© LIFO, et notre structure de donnĂ©es doit respecter cette règle pour devenir compatible avec le parcours en profondeur; comme par exemple une stack.
Un ajustement s’avère nĂ©anmoins nĂ©cessaire vis-Ă -vis du pseudo-algorithme prĂ©cĂ©dent : le noeud gauche y est insĂ©rĂ© avant le noeud de droite. On parcourt ainsi l’arbre par la droite (1 -5 - 7 - 6 - 2 - 4 - 3) et on doit ainsi permuter les deux lignes saveNode :
// Noeud droite avant le gauche
if (node.right) nodeStructure.saveNode(node.right);
if (node.left) nodeStructure.saveNode(node.left);
Nous en avons ainsi fini avec le parcours en profondeur. L’ajout d’une structure de donnĂ©es peut sembler au premier abord superflu mais, rassurez-vous, ce socle nous sera très utile pour le parcours en largeur.
Parcours en largeur - Breadth First
Pour le parcours en largeur, nous utiliserons une base identique à celle du parcours en profondeur, à savoir le pseudo-algorithme associé à une structure de données (encore inconnue à ce stade).
Si, comme précédent, nous effectuons un déroulé des différentes étapes du parcours, nous obtenons :
1ère étape :
- Nous traitons la valeur du noeud : 1, nous sommes Ă la racine
- Nous ajoutons ses deux enfants dans la structure de données :
[2,5]
- Nous récupérons le prochain élément :
[2,5]
=> 2,[5]
2ème étape :
- Nous traitons la valeur du noeud : 2
- Nous ajoutons ses deux enfants dans la structure de données :
[3, 4, 5]
- Nous récupérons le prochain élément :
[3, 4, 5]
=> 5,[3, 4]
3ème étape :
- Nous traitons la valeur du noeud : 5
- Nous ajoutons ses deux enfants dans la structure de données :
[3, 4, 6, 7]
- Nous récupérons le prochain élément :
[3, 4, 6, 7]
=> 3,[4, 6, 7]
4ème étape :
- Nous traitons la valeur du noeud : 3
- Pas d’enfant, la structure est inchangĂ©e :
[4, 6, 7]
- Nous récupérons le prochain élément :
[4, 6, 7]
=> 4,[6, 7]
5ème étape :
- Nous traitons la valeur du noeud : 4
- Pas d’enfant, la structure est inchangĂ©e :
[6,7]
- Nous récupérons le prochain élément :
[6,7]
=> 6,[7]
6ème étape :
- Nous traitons la valeur du noeud : 6
- Pas d’enfant, la structure est inchangĂ©e :
[7]
- Nous récupérons le prochain élément :
[7]
=> 7,[]
7ème étape :
- Nous traitons la valeur du noeud : 7
- Pas d’enfant, la structure est inchangĂ©e :
[]
- Nous récupérons le prochain élément :
[]
=> x,[]
8ème étape :
- Pas de valeur et la structure est vide : le parcours est terminé
Ici, et contrairement au parcours en profondeur, ce n’est pas le dernier noeud insĂ©rĂ© qui nous intĂ©resse mais le plus ancien. Ce concept algorithmique, appelĂ© First In First Out et abrĂ©gĂ© FIFO, se doit donc d’ĂŞtre respectĂ© par notre structure de donnĂ©es et comme exemple, nous pouvons citer une queue, cette dernière Ă©tant similaire Ă une file d’attente.
Conclusion
Plusieurs domaines de l’informatique exploitent la puissance des arbres : index de base de donnĂ©es, arbres de dĂ©cision, analyse de code source (Abstract syntax tree)… et cela explique ainsi leur prĂ©sence en entretien technique.
Toutefois comme nous venons de le voir, le parcours d’arbre en profondeur ou en largeur n’est pas si complexe et peut se rĂ©sumer Ă une simple association avec une structure de donnĂ©es :
- une structure respectant la logique LIFO pour un parcours en profondeur
- et une structure respectant la logique FIFO pour un parcours en largeur
Deux concepts finalement faciles et rapides Ă retenir et qui, j’espère, ne vous fera plus craindre de futurs entretiens avec des parcours d’arbres !