Lenguaje de programación C

Árbol binario en C – Inserción y recorrido

Resumen: implementar la estructura de datos de árbol, árbol binario, binary tree o simplemente tree en C y mostrar operaciones para:

  • Insertar un nuevo elemento u hoja
  • Recorridos inorden, postorden y preorden

Vamos a usar recursividad para algunas operaciones. Al insertar elementos vamos a compararlos para insertarlos a la izquierda si son menores que el nodo padre, o a la derecha en caso contrario.

El árbol binario

Un árbol binario tiene un nodo raíz que tiene dos elementos a los que llamamos izquierda y derecha. Cada elemento es a su vez otro nodo que puede tener izquierda y derecha, infinitamente.

Creación de nodo

Árbol binario en C – struct nodo

Veamos cómo crear un nodo u hoja. Tenemos el struct del nodo que es así:

struct Nodo {
    int dato;
    struct Nodo *izquierda;
    struct Nodo *derecha;
};

Tiene un dato, que será el verdadero dato que tiene. Aquí puede haber una cadena, un flotante, etcétera. Además, tiene dos apuntadores a un struct del mismo tipo que es izquierda y derecha.

Nota: ya he implementado un árbol de tipo string en C, míralo aquí.

Para crear un nodo del árbol binario en C tenemos la siguiente función ayudante:

struct Nodo *nuevoNodo(int dato) {
    // Solicitar memoria
    size_t tamanioNodo = sizeof(struct Nodo);
    struct Nodo *nodo = (struct Nodo *) malloc(tamanioNodo);
    // Asignar el dato e iniciar hojas
    nodo->dato = dato;
    nodo->izquierda = nodo->derecha = NULL;
    return nodo;
}

Lo que hace es alojar memoria (con malloc memory allocation) para el nuevo nodo y regresar el apuntador. Estamos casteando el apuntador a un Nodo en la línea 4. Después, en la línea 6 y 7 iniciamos los datos del nodo.

Con esto ya tenemos todo lo necesario, es hora de ver las operaciones del árbol binario.

Insertar dato

Como lo dije, vamos a insertar los elementos de tal forma que los menores queden a la izquierda de la raíz y los mayores a la derecha.

En caso de que el nodo en donde vayamos a insertar ya esté ocupado, intentamos insertarlo en un nodo hijo. Y si ese hijo está ocupado, entonces vamos a más profundidad. Esto se logra con recursión / recursividad.

void insertar(struct Nodo *nodo, int dato) {
    // ¿Izquierda o derecha?
    // Si es mayor va a la derecha
    if (dato > nodo->dato) {
        // Tienes espacio a la derecha?
        if (nodo->derecha == NULL) {
            nodo->derecha = nuevoNodo(dato);
        } else {
            // Si la derecha ya está ocupada, recursividad ;)
            insertar(nodo->derecha, dato);
        }
    } else {
        // Si no, a la izquierda
        if (nodo->izquierda == NULL) {
            nodo->izquierda = nuevoNodo(dato);
        } else {
            // Si la izquierda ya está ocupada, recursividad ;)
            insertar(nodo->izquierda, dato);
        }
    }
}

El primer if es el que indica si lo insertamos a la izquierda o derecha. Después comprobamos si está disponible (línea 6 y 14, en caso de que sí, alojamos un nuevo nodo.

Si no, invocamos a la función dentro de sí misma (recursión) pero le pasamos el nodo a la derecha o izquierda, eso es en la línea 10 y 18.

Como puedes notar, entre mayor sea el árbol, la inserción será un poco más lenta, pero es un sacrificio que se hace para poder buscar y recorrer en menor tiempo.

Recorrer árbol binario en C

Los 3 recorridos de un árbol binario en C

Ahora vamos a ver las tres formas de recorrer un árbol, en donde simplemente imprimimos el dato e invocamos a la función con recursividad.

Como puedes ver en el código, la condición de salida es que encontremos un nodo nulo, es decir, que lleguemos al final de una rama.

El único argumento que reciben las funciones de recorrido son el nodo raíz o padre.

Recorrido preorden

En este caso se recorre la raíz, luego la izquierda y finalmente la derecha. Queda así:

void preorden(struct Nodo *nodo) {
    if (nodo != NULL) {
        printf("%d,", nodo->dato);
        preorden(nodo->izquierda);
        preorden(nodo->derecha);
    }
}

De este modo el primer elemento será la raíz y después todas las izquierdas. Finalmente todas las derechas.

Recorrido inorden

Este recorrido es muy interesante, pues tenemos los datos ordenados de forma ascendente al recorrerlo de esta forma. Esto es debido a que primero recorremos la izquierda, luego la raíz y finalmente la derecha.

Esta es una de las ventajas de los árboles binarios, pues los datos ya están acomodados al insertarlos, así que ya no tenemos que usar un algoritmo de ordenamiento más tarde.

void inorden(struct Nodo *nodo) {
    if (nodo != NULL) {
        inorden(nodo->izquierda);
        printf("%d,", nodo->dato);
        inorden(nodo->derecha);
    }
}

Recorrido postorden

Veamos el último caso, queda así:

void postorden(struct Nodo *nodo) {
    if (nodo != NULL) {
        postorden(nodo->izquierda);
        postorden(nodo->derecha);
        printf("%d,", nodo->dato);
    }
}

Como puedes ver, primero visitamos la izquierda, después la derecha, y finalmente la raíz.

Modo de uso del árbol binario en C

Para usarlo solo tienes que crear un nodo que será la raíz y después invocar al método insertar. No te preocupes, la función se encargará de acomodarlo.

int main(void) {
    struct Nodo *raiz = nuevoNodo(28);
    insertar(raiz, 11);
    insertar(raiz, 96);
    insertar(raiz, 21);
    insertar(raiz, 6);
    insertar(raiz, 97);
    insertar(raiz, 1);
    insertar(raiz, 30);
    insertar(raiz, 10);
    insertar(raiz, 2);
    printf("\nImprimiendo preorden\n");
    preorden(raiz);
    printf("\nImprimiendo inorden\n");
    inorden(raiz);
    printf("\nImprimiendo postorden\n");
    postorden(raiz);
}

Como puedes ver, en el código estoy insertando a varios elementos y después imprimiendo.

Poniendo todo junto

Todo el código queda así:

/**
 * Árboles binarios en C
 * Operaciones de:
 * Inserción
 * Recorrido inorden, postorden y preorden
 * Uso de malloc
 *
 * @author parzibyte
 * @see https://parzibyte.me/blog
 * */#include <stdio.h>
#include <stdlib.h>

struct Nodo {
    int dato;
    struct Nodo *izquierda;
    struct Nodo *derecha;
};


struct Nodo *nuevoNodo(int dato) {
    // Solicitar memoria
    size_t tamanioNodo = sizeof(struct Nodo);
    struct Nodo *nodo = (struct Nodo *) malloc(tamanioNodo);
    // Asignar el dato e iniciar hojas
    nodo->dato = dato;
    nodo->izquierda = nodo->derecha = NULL;
    return nodo;
}

void insertar(struct Nodo *nodo, int dato) {
    // ¿Izquierda o derecha?
    // Si es mayor va a la derecha
    if (dato > nodo->dato) {
        // Tienes espacio a la derecha?
        if (nodo->derecha == NULL) {
            nodo->derecha = nuevoNodo(dato);
        } else {
            // Si la derecha ya está ocupada, recursividad ;)
            insertar(nodo->derecha, dato);
        }
    } else {
        // Si no, a la izquierda
        if (nodo->izquierda == NULL) {
            nodo->izquierda = nuevoNodo(dato);
        } else {
            // Si la izquierda ya está ocupada, recursividad ;)
            insertar(nodo->izquierda, dato);
        }
    }
}


void preorden(struct Nodo *nodo) {
    if (nodo != NULL) {
        printf("%d,", nodo->dato);
        preorden(nodo->izquierda);
        preorden(nodo->derecha);
    }
}

void inorden(struct Nodo *nodo) {
    if (nodo != NULL) {
        inorden(nodo->izquierda);
        printf("%d,", nodo->dato);
        inorden(nodo->derecha);
    }
}

void postorden(struct Nodo *nodo) {
    if (nodo != NULL) {
        postorden(nodo->izquierda);
        postorden(nodo->derecha);
        printf("%d,", nodo->dato);
    }
}

int main(void) {
    struct Nodo *raiz = nuevoNodo(28);
    insertar(raiz, 11);
    insertar(raiz, 96);
    insertar(raiz, 21);
    insertar(raiz, 6);
    insertar(raiz, 97);
    insertar(raiz, 1);
    insertar(raiz, 30);
    insertar(raiz, 10);
    insertar(raiz, 2);
    printf("\nImprimiendo preorden\n");
    preorden(raiz);
    printf("\nImprimiendo inorden\n");
    inorden(raiz);
    printf("\nImprimiendo postorden\n");
    postorden(raiz);
}

Conclusión

Así funciona un árbol binario en C. Si quieres más sobre estructuras de datos puedes ver una pila dinámica en C.

En el futuro escribiré más sobre C, te invito a seguirme. Mientras tanto puedes ver otros posts de este tema.

Aquí no he tocado el tema de búsqueda en el árbol, pero sí lo he hecho en mi otro post.

Estoy aquí para ayudarte 🤝💻


Estoy aquí para ayudarte en todo lo que necesites. Si requieres alguna modificación en lo presentado en este post, deseas asistencia con tu tarea, proyecto o precisas desarrollar un software a medida, no dudes en contactarme. Estoy comprometido a brindarte el apoyo necesario para que logres tus objetivos. Mi correo es parzibyte(arroba)gmail.com, estoy como@parzibyte en Telegram o en mi página de contacto

No te pierdas ninguno de mis posts 🚀🔔

Suscríbete a mi canal de Telegram para recibir una notificación cuando escriba un nuevo tutorial de programación.
parzibyte

Programador freelancer listo para trabajar contigo. Aplicaciones web, móviles y de escritorio. PHP, Java, Go, Python, JavaScript, Kotlin y más :) https://parzibyte.me/blog/software-creado-por-parzibyte/

Ver comentarios

Entradas recientes

Creador de credenciales web – Aplicación gratuita

Hoy te voy a presentar un creador de credenciales que acabo de programar y que…

1 semana hace

Desplegar PWA creada con Vue 3, Vite y SQLite3 en Apache

Ya te enseñé cómo convertir una aplicación web de Vue 3 en una PWA. Al…

2 semanas hace

Arquitectura para wasm con Go, Vue 3, Pinia y Vite

En este artículo voy a documentar la arquitectura que yo utilizo al trabajar con WebAssembly…

2 semanas hace

Vue 3 y Vite: crear PWA (Progressive Web App)

En un artículo anterior te enseñé a crear un PWA. Al final, cualquier aplicación que…

2 semanas hace

Errores de Comlink y algunas soluciones

Al usar Comlink para trabajar con los workers usando JavaScript me han aparecido algunos errores…

2 semanas hace

Esperar promesa para inicializar Store de Pinia con Vue 3

En este artículo te voy a enseñar cómo usar un "top level await" esperando a…

2 semanas hace

Esta web usa cookies.