Libera un albero binario


13

Quindi, prima di leggere alcuni concetti di base di informatica.

  1. Un albero binario è una struttura allocata in modo dinamico (solitamente utilizzata per l'archiviazione ordinata).
  2. A causa della sua natura, l'attraversamento di alberi binari è generalmente ricorsivo;
    Questo perché l'attraversamento lineare (tramite un loop) non è naturale quando ci sono due strade di loop.
    • Ricorsivo: significa una funzione che si chiama da sola.
  3. Nelle vecchie lingue, la gestione della memoria richiede una gestione manuale della memoria.
    • Manuale: significa che devi farlo da solo.
  4. Quando si esegue la gestione manuale della memoria, è necessario chiedere al sistema sottostante di liberare ciascun membro dell'albero.
    • Gratuito: recupera la memoria nella poos globale in modo che possa essere riutilizzata e non esaurire la memoria.
    • Liberazione: questo viene fatto chiamando la funzione free()e passandole il puntatore che si desidera ripristinare.
    • Puntatore: è come un bastone virtuale. Alla fine è la memoria. Quando chiedi memoria ti viene dato un puntatore (stick virtuale) che ha memoria. Al termine restituisci il puntatore (levetta virtuale).

La soluzione ricorsiva:

freeTree(Node* node)
{
    freeTree(node->left);  
    freeTree(node->right);
    free(node);
}

Il problema quindi è che la ricorsione significa che stai ripetutamente chiamando la stessa funzione. Questo fa crescere lo stack. La crescita dello stack utilizza più memoria. Il motivo per cui stai liberando l'albero è che vuoi che la memoria venga ripristinata usando più memoria è controproducente (anche se ottieni entrambi i bit di memoria).

Alla fine la domanda:

Quindi il problema è incentrato sulla conversione della versione ricorsiva sopra in una soluzione lineare (in modo da non dover usare la memoria).

Indica il tipo di nodo

typedef struct Node Node;
struct Node
{
    Node* left;
    Node* right;
};

Scrivi una funzione per liberare un albero di questi nodi.

restrizioni:

  • Impossibile utilizzare la ricorsione (nemmeno indirettamente)
  • Impossibile allocare spazio dinamico per il tracciamento.

  • Nota che esiste una soluzione O (n)

Vincitore:

  1. Migliore complessità.
  2. Tie Break 1: primo inviato
  3. Tie Break 2: Minima quantità di personaggi.

Risposte:


7

Mi sembra molto vicino a O (n):

Questo fa una passeggiata in profondità sull'albero e utilizza il ->leftpuntatore dei nodi attraversati per tenere traccia del genitore.

struct Node * node = root;
struct Node * up = NULL;

while (node != NULL) {
    if (node->left != NULL) {
        struct Node * left = node->left;
        node->left = up;
        up = node;
        node = left;
    } else if (node->right != NULL) {
        struct Node * right = node->right;
        node->left = up;
        node->right = NULL;
        up = node;
        node = right;
    } else {
        if (up == NULL) {
            free(node);
            node = NULL;
        }
        while (up != NULL) {
            free(node);
            if (up->right != NULL) {
                node = up->right;
                up->right = NULL;
                break;
            } else {
                node = up;
                up = up->left;
            }
        }
    }
}

+1 Aggiungi il segno di spunta per l'unica risposta. È un po 'più complesso della soluzione che presento di seguito ma molto buona.
Martin York,

4

C99, 94, O (n)

Modifica: tutti sembrano riferirsi struct Nodeproprio Nodecome se lo typedefavessero fatto, così ho fatto anche io.

questo è in realtà il mio primo golf C. molti segfault.

comunque, questo richiede C99 perché usa una dichiarazione all'interno della prima istruzione di un ciclo for.

void f(Node*n){for(Node*q;n;n=q)(q=n->left)?n->left=q->right,q->right=n:(q=n->right,free(n));}

nemmeno usando #define!

questo algoritmo funziona trasformando l'albero in modo che il nodo superiore non abbia alcun figlio sinistro, quindi eliminandolo e passando al figlio giusto.

per esempio, se iniziamo con l'albero

 1
/ \
2 3
 \
 4

l'algoritmo muterà i puntatori in modo che l'albero sia

2
 \
 1
/ \
4 3

ora possiamo eliminare facilmente il nodo più in alto.


Non ho usato typedef perché il mio era in C ++ (dimentichi queste piccole differenze tra le lingue). Ho aggiornato la domanda in modo che funzioni allo stesso modo in C e C ++.
Martin York,

@LokiAstari In realtà non conosco c ++ e ho appena iniziato a studiare C di recente. Ma sapevo abbastanza per rispondere a questo :-)
orgoglioso haskeller il

1
Per ora farò un +1. Ma non ho ancora capito come funziona, quindi tornerò dopo la Turchia. :-)
Martin York il

@LokiAstari fondamentalmente usa il fatto che C mescola espressioni e dichiarazioni insieme per fare le cose usando solo espressioni
orgoglioso haskeller il

1

C / C ++ / Objective-C 126 caratteri (include newline finale richiesto)

#define b(t)(t->left||t->right)
void f(Node*r){while(r&&b(r)){Node**p=&r,*c=b(r);while(c)p=&c,c=b(c);free(*p);*p=0;}free(r);}

Non funziona in O (n) tempo. Ma l'OP non lo richiede, quindi ecco la mia soluzione O (n 2 ).

Algoritmo: cammina verso una foglia dalla radice. Rilascialo. Ripeti fino a quando non ci sono foglie. Rilascia il root.

Ungolfed:

void freeTree (Node * root) {
    while (root && (root->left || root->right)) {
        Node ** prev = &root;
        Node * curr = root->left || root->right;
        while (curr != 0) {
            prev = &curr;
            curr = curr->left || curr->right;
        }
        free(*prev);
        *prev = 0;
    }
    free(root);
}

Purtroppo non funzionerà. non impostare il puntatore sulla foglia su NULL prima di liberarlo. In questo modo continuerai all'infinito a liberare lo stesso nodo foglia e non arriverai mai al punto in cui liberi l'albero.
Martin York,

@LokiAstari: grazie per aver notato il bug. Dovrebbe essere corretto ora (anche se non ho testato il codice).
Thomas Eding

1

c ++ 99 O (n)

La cosa qui i loop sono grandi per concatenare un elenco ma non per andare su e giù nelle gerarchie. user300 l'ha gestito (sono impressionato) ma il codice è difficile da leggere.

La soluzione è convertire l'albero in un elenco.
Il trucco è farlo contemporaneamente alla cancellazione dei nodi.

void freeNode(Node* t)
{
    if (t == NULL)
    {   return;
    }

    // Points at the bottom left node.
    // Any right nodes are added to the bottom left as we go down
    // this progressively flattens the tree into a list as we go.    
    Node* bottomLeft    = findBottomLeft(t);


    while(t != NULL)
    {
        // Technically we don't need the if (it works fine without)
        // But it makes the code easier to reason about with it here.
        if (t->right != NULL)
        {
            bottomLeft->left = t->right;
            bottomLeft = findBottomLeft(bottomLeft);
        }
        // Now just free the curent node
        Node*   old = t;
        t = t->left;
        free(old);
    }
}

Node* findBottomLeft(Node* t)
{
    while(t->left != NULL)
    {
        t = t->left;
    }
    return t;
}

Versione da golf

void f(Node*t){Node*o,*l=t;for(;t;free(o)){for(;l->left;l=l->left);l->left=t->right;o=t;t=t->left;}}

Golf espanso

void f(Node* t)
{
        Node*o,*l    = t;

        for(;t;free(o))
        {
            for(;l->left;l = l->left);
            l->left = t->right;
            o = t;
            t = t->left;
        }
}

0

C, 150 byte

f(T*r,int s){T*t[s];t[0]=r;T**f=&t[0],**e=&t[0];while(f<=e){if(*f){if((*f)->left){*++e=(*f)->left;}if((*f)->right){*++e=(*f)->right;}}free(*f);f++;}}

Provalo su JDoodle

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.