Je connais l'algorithme pour la traversée par ordre de niveau d'un arbre. (Je pense que tout le monde le sait) Cet algorithme utilise la file d'attente pour stocker les nœuds de l'arbre. Existe-t-il un algorithme qui n'utilise pas de mémoire supplémentaire? Cet algorithme ne doit pas utiliser la récursivité (de cette façon, nous utilisons stack). Notez que cet arbre est donné dans la représentation gauche-enfant droit-frère. Aucun pointeur supplémentaire n'est autorisé.
Les structures en C, pour l'arbre sont:
struct node { int data; struct node *left-child; struct node *right-sibling; }
L'arbre est représenté avec un pointeur vers le nœud racine. Bien sûr, root ne peut pas avoir de frère droit.
3 Réponses :
Si vous pouvez stocker un pointeur suivant supplémentaire dans chaque nœud de l'arborescence qui pointe vers le nœud suivant dans l'ordre des niveaux pour chaque niveau, alors vous pouvez effectuer la traversée de l'ordre des niveaux dans l'espace constant.
Je n'ai pas mentionné que l'arbre est donné dans la représentation gauche-enfant droit-frère, aucun pointeur supplémentaire n'est autorisé.
Vous pouvez appliquer la traversée par ordre de niveau de Morris si vous souhaitez parcourir votre arbre dans un espace constant. Vous pouvez vous référer à ici et ici a>.
C'est utile, mais j'ai besoin de quelques lignes directrices. Je ne sais pas comment adapter cet algorithme (qui est pour les arbres binaires) aux arbres ordinaires. Chaque arbre (ordinaire) peut être représenté avec un arbre binaire, mais l'ordre des niveaux de l'arbre binaire correspondant n'est pas le même que l'ordre des niveaux de l'arbre (ordinaire).
Une façon pourrait être d'utiliser les pointeurs frères de droite
qui sont nuls, pour rendre tous les nœuds frères les uns des autres (temporairement).
Vous pouvez utiliser un pointeur lent et rapide. Le rapide serait toujours au dernier frère (qui a un pointeur nul comme frère droit
). Le pointeur left-child
du nœud lent serait alors copié dans ce right-frère
, après quoi le pointeur rapide se poursuivrait à nouveau vers la fin. Le pointeur lent va d'un pas vers la droite et répète les mêmes. Lorsque le pointeur lent atteint également la fin, tous les nœuds seront frères et sœurs. Le pointeur lent ou rapide peut être utilisé pour afficher les valeurs dans l'ordre des niveaux. Cela fera le travail, mais l'arbre aura été détruit en conséquence.
Pour restaurer l'arbre, je suggérerais que pendant le processus ci-dessus, la direction de tous les bords frères soit inversée. Cela signifie que vous devez avoir un autre pointeur qui se trouve derrière le pointeur lent. Cela permettra d'effectuer l'inversion entre ces deux. C'est un peu obscur, car le frère droit
pointera alors en fait vers quelque chose qui est principalement un frère gauche .
Après le processus ci-dessus , les pointeurs seront à la fin de la liste des nœuds, mais comme nous avons inversé les bords frères, nous pouvons également revenir en arrière et inverser à nouveau les bords. Une difficulté est de savoir quels pointeurs frères devraient redevenir nuls (quand un nœud était à l'origine un enfant le plus à droite). Cela peut être fait en ayant à nouveau un pointeur rapide se déplaçant vers l'avant (dans la direction gauche) pour trouver les nœuds qui ont un enfant. Si le pointeur qui est en retard sur le pointeur lent atteint un tel enfant, nous savons que le nœud du pointeur lent devrait avoir un pointeur nul comme frère droit
. Lorsque ce correctif est appliqué, le pointeur rapide devrait à nouveau courir pour trouver un autre nœud parent, ... etc.
Notez que les pointeurs left-child
ne sont pas modifiés par cet algorithme.
Donc, au total, cette solution utilise trois pointeurs et la structure de l'arbre lui-même.
Voici un exemple d'arbre que j'ai utilisé dans une implémentation ci-dessous: p >
void printTree(Node node, int indent) { if (!node) return; for (int i = 0; i < indent; i++) printf(" "); printf("%d\n", node->data); printTree(node->leftChild, indent+1); printTree(node->rightSibling, indent); }
Implémentation en JavaScript - extrait exécutable:
#include <stdio.h> #include <stdlib.h> // define Node as a pointer to a node struct typedef struct node { int data; struct node *leftChild; struct node *rightSibling; } * Node; // Some helper functions to ease the creation of a tree: Node sibling(Node leftSibling, Node rightSibling) { leftSibling->rightSibling = rightSibling; return leftSibling; } Node parent(Node parent, Node child) { parent->leftChild = child; return parent; } Node create(int data) { Node node = malloc(sizeof(struct node)); node->data = data; return node; } // end - helper functions void traverse(Node node) { Node lead = node; // ...walks somewhere ahead of node Node lag = NULL; // ... always follows one step behind node while (node) { printf("%d\n", node->data); // output lead->rightSibling = node->leftChild; while (lead->rightSibling) lead = lead->rightSibling; // rotate: point node to next right-sibling, and reverse direction of sibling edge Node temp = node->rightSibling; node->rightSibling = lag; lag = node; node = temp; } // Restore tree lead = node = lag->rightSibling; // backwards lag->rightSibling = NULL; while (lead != NULL && lead->leftChild == NULL) lead = lead->rightSibling; // actually going left! while (node != NULL) { if (lead != NULL && lead->leftChild == lag) { // When lag is the leftChild of some node (lead), then lag should not be the target of a rightSibling lag = node; node = node->rightSibling; lag->rightSibling = NULL; // Find previous parent lead = lead->rightSibling; while (lead != NULL && lead->leftChild == NULL) lead = lead->rightSibling; // actually going left! } else { // walk back and restore sibling pointers Node temp = node->rightSibling; node->rightSibling = lag; lag = node; node = temp; } } } int main(void) { // Create the example tree Node root = parent(create(1), sibling(parent(create(2), sibling(create(5), sibling(parent(create(6), sibling(create(14), sibling(create(15), create(16))) ), create(7))) ), sibling(parent(create(3), sibling(create(8), create(9)) ), parent(create(4), sibling(create(10), sibling(parent(create(11), sibling(create(17), sibling(create(18), create(19))) ), sibling(create(12), create(13)))) ))) ); traverse(root); return 0; }
Je ne parle pas couramment le C, mais je pense que cela devrait le faire:
function * traverse(node) { let lead = node; // ...walks somewhere ahead of node let lag = null; // ... always follows one step behind node while (node) { yield node.data; // output lead.rightSibling = node.leftChild; while (lead.rightSibling) lead = lead.rightSibling; // rotate: point node to next right-sibling, and reverse direction of sibling edge [node.rightSibling, lag, node] = [lag, node, node.rightSibling] } // Restore tree lead = node = lag.rightSibling; // backwards lag.rightSibling = null; while (lead !== null && lead.leftChild === null) lead = lead.rightSibling; // actually going left! while (node) { if (lead !== null && lead.leftChild === lag) { // When lag is the leftChild of some node (lead), then lag should not be the target of a rightSibling [node.rightSibling, lag, node] = [null, node, node.rightSibling]; // Find previous parent lead = lead.rightSibling; while (lead !== null && lead.leftChild === null) lead = lead.rightSibling; // actually going left! } else { // walk back and restore sibling pointers [node.rightSibling, lag, node] = [lag, node, node.rightSibling]; } } } // Create node, given its data and child nodes function Node(data, ...children) { // Link the children as siblings if (children.length > 1) children.reduceRight((a, b) => (b.rightSibling = a, b)) // Create the node itself. For now, without any siblings return { data, leftChild: children.length ? children[0] : null, rightSibling: null }; } // Example tree let tree = Node(1, Node(2, Node(5), Node(6, Node(14), Node(15), Node(16) ), Node(7) ), Node(3, Node(8), Node(9) ), Node(4, Node(10), Node(11, Node(17), Node(18), Node(19) ), Node(12), Node(13) ) ); // Apply the algorithm and output the yielded values console.log(...traverse(tree));
Pour imprimer l'arborescence dans un format très basique, vous pouvez utiliser cette fonction:
1 / 2 ------------ 3 ---------4 / / / 5 -- 6 -- 7 8 -- 9 10 -- 11 -- 12 -- 13 / / 14 -- 15 -- 16 17 -- 18 -- 19
Cela aidera à vérifier qu'en effet l'arbre est le même avant et après le parcours. p >
C'est de quoi j'ai besoin. J'ai une question. Le pointeur lent est-il un niveau au-dessus du pointeur rapide? Pouvez-vous réécrire l'algorithme en C, je ne suis pas si bon en JavaScript. Merci.
Le terme «niveau» devient un peu ambigu car les modifications apportées aux arêtes frères rendent le graphe temporairement cyclique (l'enfant d'un nœud devient également son frère). Mais à tous les stades, les nœuds lents et rapides sont également) frères. En fait, le but est de rendre tous les nœuds frères (sans détruire les relations enfants existantes). Je vais passer un peu de temps à le convertir en C.
Merci. J'ai compris la première partie de l'algorithme, où l'arbre est "aplati" et imprimé, mais je ne comprends pas la deuxième partie où l'arbre est restauré. Je devrai le lire demain, quand je serai frais. Quoi qu'il en soit, merci encore.
Pour comprendre la deuxième partie, assurez-vous de réaliser que rightSibling
est à ce moment pointé vers la gauche . Deuxièmement, les liens leftChild
pointeront toujours vers des nœuds qui sont temporairement liés en tant que frères et sœurs. Pour restaurer les liens rightSibling
, nous devons (1) les inverser à nouveau, et (2) certains doivent être définis sur NULL. La règle doit être qu'un nœud ne peut jamais être à la fois un leftChild
et un rightSibling
en même temps. Ainsi, lorsque nous détectons un tel nœud, le pointeur rightSibling
vers ce nœud doit être mis à NULL
.
Eh bien, si l'arbre était un arbre complet représenté dans un tableau, il n'y aurait pratiquement rien à faire: veuillez mentionner comment l'arbre est représenté.
(Excellent, un arbre non binaire pour un changement.) Voir aussi: Morris inorder tree traversal expliqué
@greybeard Voir le commentaire que j'ai écrit dans la réponse de Rudresh Dixit
Est-il permis de modifier les pointeurs d'arbre pendant le parcours? Si tel est le cas, l'arborescence doit-elle être restaurée à son état d'origine une fois le parcours terminé?