6. Árboles

#estructuraDeDatos #universidad

a. Definición

Un árbol es una estructura de datos jerárquica compuesta por nodos. Cada nodo puede tener cero o más nodos hijos, y no existen ciclos. El nodo superior se llama raíz, y los nodos sin hijos se llaman hojas.

Terminología:

b. Representación en Memoria de Árboles

Los árboles pueden ser representados en memoria mediante nodos que contienen referencias a sus hijos. Una representación común es utilizando arreglos o listas enlazadas.

Ejemplo de Representación con Punteros:

        A
       / \
      B   C
     / \
    D   E

Cada nodo contiene un dato y punteros a sus hijos.

c. Árboles Generales

Un árbol general es un árbol donde cada nodo puede tener cualquier número de hijos. No está restringido a una cantidad específica de hijos por nodo.

Representación de Árbol General en C:

#include <stdio.h>
#include <stdlib.h>

// Definición del nodo del árbol
typedef struct Nodo {
    char dato;
    struct Nodo **hijos;
    int numHijos;
} Nodo;

// Crear un nuevo nodo
Nodo* crearNodo(char valor, int numHijos) {
    Nodo *nuevo = (Nodo*)malloc(sizeof(Nodo));
    nuevo->dato = valor;
    nuevo->numHijos = numHijos;
    if (numHijos > 0) {
        nuevo->hijos = (Nodo**)malloc(numHijos * sizeof(Nodo*));
        for (int i = 0; i < numHijos; i++) {
            nuevo->hijos[i] = NULL;
        }
    } else {
        nuevo->hijos = NULL;
    }
    return nuevo;
}

// Ejemplo de uso
int main() {
    Nodo *raiz = crearNodo('A', 2);
    raiz->hijos[0] = crearNodo('B', 2);
    raiz->hijos[1] = crearNodo('C', 0);
    
    raiz->hijos[0]->hijos[0] = crearNodo('D', 0);
    raiz->hijos[0]->hijos[1] = crearNodo('E', 0);
    
    return 0;
}

d. Árboles Binarios

Un árbol binario es un tipo específico de árbol donde cada nodo tiene como máximo dos hijos, denominados hijo izquierdo y hijo derecho.

Características de un Árbol Binario:

e. Inserción de Elementos en Árbol Binario

Definición del Nodo del Árbol Binario:

typedef struct Nodo {
    int dato;
    struct Nodo *izquierdo;
    struct Nodo *derecho;
} Nodo;

Función para Crear un Nuevo Nodo:

Nodo* crearNodo(int valor) {
    Nodo *nuevo = (Nodo*)malloc(sizeof(Nodo));
    nuevo->dato = valor;
    nuevo->izquierdo = NULL;
    nuevo->derecho = NULL;
    return nuevo;
}

Inserción en un Árbol Binario de Búsqueda (BST):

En un Árbol Binario de Búsqueda (BST), para cada nodo, los valores de los nodos en el subárbol izquierdo son menores y en el derecho son mayores.

Nodo* insertarBST(Nodo *raiz, int valor) {
    if (raiz == NULL) {
        return crearNodo(valor);
    }
    if (valor < raiz->dato) {
        raiz->izquierdo = insertarBST(raiz->izquierdo, valor);
    } else if (valor > raiz->dato) {
        raiz->derecho = insertarBST(raiz->derecho, valor);
    }
    // Si el valor ya existe, no se inserta (evita duplicados)
    return raiz;
}

Ejemplo de Uso:

int main() {
    Nodo *raiz = NULL;
    raiz = insertarBST(raiz, 50);
    raiz = insertarBST(raiz, 30);
    raiz = insertarBST(raiz, 70);
    raiz = insertarBST(raiz, 20);
    raiz = insertarBST(raiz, 40);
    raiz = insertarBST(raiz, 60);
    raiz = insertarBST(raiz, 80);
    
    // Aquí podrías implementar funciones para recorrer el árbol, etc.
    
    return 0;
}

f. Recorridos de un Árbol Binario

Los recorridos son formas de visitar todos los nodos de un árbol. Los principales tipos de recorridos en árboles binarios son preorden, en orden y postorden.

1. Recorrido Preorden (Preorder Traversal):

Orden de Visita: Raíz, Subárbol Izquierdo, Subárbol Derecho.

void recorridoPreorden(Nodo *raiz) {
    if (raiz != NULL) {
        printf("%d ", raiz->dato);
        recorridoPreorden(raiz->izquierdo);
        recorridoPreorden(raiz->derecho);
    }
}

2. Recorrido en Orden (Inorder Traversal):

Orden de Visita: Subárbol Izquierdo, Raíz, Subárbol Derecho.

void recorridoEnOrden(Nodo *raiz) {
    if (raiz != NULL) {
        recorridoEnOrden(raiz->izquierdo);
        printf("%d ", raiz->dato);
        recorridoEnOrden(raiz->derecho);
    }
}

3. Recorrido Postorden (Postorder Traversal):

Orden de Visita: Subárbol Izquierdo, Subárbol Derecho, Raíz.

void recorridoPostorden(Nodo *raiz) {
    if (raiz != NULL) {
        recorridoPostorden(raiz->izquierdo);
        recorridoPostorden(raiz->derecho);
        printf("%d ", raiz->dato);
    }
}

Ejemplo de Uso de Recorridos:

int main() {
    Nodo *raiz = NULL;
    raiz = insertarBST(raiz, 50);
    raiz = insertarBST(raiz, 30);
    raiz = insertarBST(raiz, 70);
    raiz = insertarBST(raiz, 20);
    raiz = insertarBST(raiz, 40);
    raiz = insertarBST(raiz, 60);
    raiz = insertarBST(raiz, 80);
    
    printf("Recorrido Preorden: ");
    recorridoPreorden(raiz); // Imprime: 50 30 20 40 70 60 80
    printf("\n");
    
    printf("Recorrido en Orden: ");
    recorridoEnOrden(raiz); // Imprime: 20 30 40 50 60 70 80
    printf("\n");
    
    printf("Recorrido Postorden: ");
    recorridoPostorden(raiz); // Imprime: 20 40 30 60 80 70 50
    printf("\n");
    
    return 0;
}

g. Resumen de Árboles

Ejercicios

  1. Altura de un Árbol Binario:

    • Escribe una función en C que calcule la altura de un árbol binario.
  2. Número de Hojas:

    • Implementa una función en C que cuente el número de hojas en un árbol binario.
  3. Verificar si es un BST:

    • Escribe una función en C que verifique si un árbol binario dado es un Árbol Binario de Búsqueda (BST).
  4. Recorridos Iterativos:

    • Implementa los recorridos preorden, en orden y postorden de un árbol binario utilizando métodos iterativos (sin recursión).