1
votes

parcours de l'arborescence des niveaux sans utiliser de mémoire supplémentaire

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.


4 commentaires

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é?


3 Réponses :


0
votes

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.


1 commentaires

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é.




1
votes

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;
}

Version en C

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 >


4 commentaires

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 .