Libere un árbol binario

13

Entonces, antes de leer algunos conceptos básicos de informática.

  1. Un árbol binario es una estructura asignada dinámicamente (generalmente utilizada para el almacenamiento ordenado).
  2. Debido a su naturaleza, el recorrido de los árboles binarios suele ser recursivo;
    Esto se debe a que el recorrido lineal (a través de un bucle) no es natural cuando hay dos vías de bucle.
    • Recursivo: Esto significa una función que se llama a sí misma.
  3. En lenguajes pasados ​​de moda, la administración de memoria requiere administración manual de memoria.
    • Manual: significa que tienes que hacerlo tú mismo.
  4. Cuando realiza la gestión manual de la memoria, debe solicitar al sistema subyacente que libere a cada miembro del árbol.
    • Gratis: recupere la memoria en los poos globales para que pueda reutilizarse y no se quede sin memoria.
    • Liberación: esto se hace llamando a la función free() y pasándole el puntero que desea recuperar.
    • Puntero: es como un palo virtual. Al final es la memoria. Cuando solicita memoria, se le proporciona un puntero (dispositivo virtual) que tiene memoria. Cuando haya terminado, devuelve el puntero (dispositivo virtual).

La solución recursiva:

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

El problema es que la recursión significa que repetidamente estás llamando a la misma función. Esto crece la pila. El crecimiento de la pila usa más memoria. La razón por la que está liberando el árbol es porque desea recuperar la memoria usando más memoria, es contraproducente (incluso si recupera ambos bits de memoria).

Por fin la pregunta:

Entonces, el problema se centra en convertir la versión recursiva anterior en una solución lineal (para que no tenga que usar memoria).

Dar el tipo de nodo

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

Escriba una función para liberar un árbol de estos nodos.

Restricciones:

  • No se puede usar la recursividad (ni siquiera indirectamente)
  • No se puede asignar ningún espacio dinámico para el seguimiento.

  • Tenga en cuenta que hay una solución O (n)

Ganador:

  1. La mejor complejidad.
  2. Tie Break 1: primer envío
  3. Tie Break 2: menor cantidad de personajes.
Martin York
fuente

Respuestas:

7

Me parece muy cercano a O (n):

Esto hace una caminata profunda en el árbol, y usa el ->leftpuntero de los nodos recorridos para hacer un seguimiento del padre.

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;
            }
        }
    }
}
Arnaud Le Blanc
fuente
+1 Agrega la marca para la única respuesta. Es un poco más complejo que la solución que presento a continuación, pero muy buena.
Martin York
4

C99, 94, O (n)

Editar: todo el mundo parece referirse al struct Nodeigual que Nodecomo si el typedefque la DE, así que lo hice también.

Este es en realidad mi primer golf C. Muchos segfaults.

de todos modos, esto requiere C99 porque usa una declaración dentro de la primera declaración del bucle 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));}

ni siquiera usando #define!

Este algoritmo funciona transformando el árbol de modo que el nodo superior no tenga un hijo izquierdo, y luego lo elimina y pasa a su hijo derecho.

por ejemplo, si comenzamos con el árbol

 1
/ \
2 3
 \
 4

el algoritmo mutará los punteros para que el árbol sea

2
 \
 1
/ \
4 3

ahora podemos eliminar el nodo superior fácilmente.

orgulloso Haskeller
fuente
No utilicé typedef porque el mío estaba en C ++ (olvida estas pequeñas diferencias entre los lenguajes). He actualizado la pregunta para que funcione igual en C y C ++.
Martin York
@LokiAstari No conozco realmente C ++, y recientemente comencé a aprender C recientemente. Pero sabía lo suficiente como para responder esto :-)
orgulloso Haskeller
1
Voy a hacer un +1 por ahora. Pero todavía no he resuelto cómo funciona, así que volveré después de Turquía. :-)
Martin York
@LokiAstari básicamente utiliza el hecho de que C mezcla expresiones y declaraciones para hacer cosas usando solo expresiones
orgulloso Haskeller
1

C / C ++ / Objective-C 126 caracteres (incluye la nueva línea final requerida)

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

No se ejecuta en tiempo O (n). Pero el OP no lo requiere, así que aquí está mi solución O (n 2 ).

Algoritmo: caminar hacia una hoja desde la raíz. Liberarlo. Repita hasta que no haya hojas. Suelta la raíz.

Sin golf:

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);
}
Thomas Eding
fuente
Lamentablemente eso no funcionará. no establece el puntero a la hoja en NULL antes de liberarlo. Por lo tanto, continuará liberando sin cesar el mismo nodo de hoja y nunca llegará al punto donde libera el árbol.
Martin York
@LokiAstari: Gracias por notar el error. Debería arreglarse ahora (aunque no he probado el código).
Thomas Eding
1

c ++ 99 O (n)

Los bucles aquí son excelentes para encadenar a lo largo de una lista pero no para subir y bajar jerarquías. user300 lo logró (estoy impresionado) pero el código es difícil de leer.

La solución es convertir el árbol en una lista.
El truco es hacerlo al mismo tiempo que elimina los nodos.

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

Versión de 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 expandido

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;
        }
}
Martin York
fuente
0

C, 150 bytes

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

Pruébalo en JDoodle

T. Salim
fuente