Структуры, referenciando a si mismas en C

¡Hola هناك, futuros magos de la programación! Hoy vamos a emprender un viaje emocionante al mundo de las estructuras referenciadoras en C. No se preocupen si eso suena un poco intimidante, les prometo que lo desglosaremos en partes más pequeñas que incluso mi abuela podría entender (y créanme, ella aún piensa que un ratón es solo un pequeño animal peludo).

C - Self-Referential Structures

¿Qué son las Estructuras Referenciadoras?

Imagina que estás en una fiesta y quieres presentar a tu amigo a alguien. Podrías decir: "Este es mi amigo Alex, y Alex también es desarrollador de software". En esta presentación, te refieres a Alex dos veces, una vez por nombre y otra vez por profesión. ¡Esto es similar a cómo funcionan las estructuras referenciadoras en C!

Una estructura referenciadora es una estructura que contiene un puntero a una estructura del mismo tipo dentro de su definición. Es como una muñeca rusa, pero en lugar de muñecas más pequeñas adentro, son del mismo tipo de estructura en todo momento.

Definiendo una Estructura Referenciadora

Vamos a sumergirnos en cómo realmente definimos estas estructuras fascinantes. Aquí tienes un ejemplo simple:

struct Node {
int data;
struct Node* next;
};

En este ejemplo, hemos definido una estructura llamada Node. Contiene dos miembros:

  1. Un entero data para almacenar información.
  2. Un puntero next que apunta a otra estructura Node.

Esta es la esencia de una estructura referenciadora: se refiere a sí misma en su propia definición.

Ejemplos de Estructura Referenciadora

Ahora, veamos algunos ejemplos prácticos donde podríamos usar estructuras referenciadoras.

1. Nodo de Lista Enlazada

Ya hemos visto esto, pero vamos a desglosarlo más:

struct ListNode {
int data;
struct ListNode* next;
};

Esta estructura es perfecta para crear una lista enlazada. Cada nodo contiene algunos datos y un puntero al siguiente nodo en la lista.

2. Nodo de Árbol

Los árboles son otro caso de uso común para estructuras referenciadoras:

struct TreeNode {
int data;
struct TreeNode* left;
struct TreeNode* right;
};

Aquí, cada nodo en el árbol puede tener hasta dos hijos (izquierdo y derecho), lo que nos permite crear árboles binarios.

3. Nodo de Gráfico

Para los más aventureros, aquí está cómo podríamos representar un nodo en un gráfico:

struct GraphNode {
int data;
struct GraphNode** neighbors;
int neighborCount;
};

En este caso, tenemos un array de punteros a otras estructuras GraphNode, representando las conexiones en nuestro gráfico.

Creando una Lista Enlazada con Estructura Referenciadora

Ahora que entendemos lo básico, creemos una lista enlazada simple usando nuestra estructura referenciadora. No se preocupen si no entienden todo de inmediato, Roma no se construyó en un día, y neither is coding expertise!

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

struct Node {
int data;
struct Node* next;
};

// Función para crear un nuevo nodo
struct Node* createNode(int data) {
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
newNode->data = data;
newNode->next = NULL;
return newNode;
}

// Función para imprimir la lista enlazada
void printList(struct Node* head) {
struct Node* current = head;
while (current != NULL) {
printf("%d -> ", current->data);
current = current->next;
}
printf("NULL\n");
}

int main() {
struct Node* head = createNode(1);
head->next = createNode(2);
head->next->next = createNode(3);

printf("Nuestra lista enlazada: ");
printList(head);

return 0;
}

Vamos a desglosar esto:

  1. Definimos nuestra estructura Node como antes.
  2. Creamos una función createNode que asigna memoria para un nuevo nodo y inicializa sus datos.
  3. Tenemos una función printList que recorre la lista e imprime los datos de cada nodo.
  4. En main, creamos una lista enlazada con tres nodos e imprimimos.

Cuando ejecutes este programa, deberías ver:

Nuestra lista enlazada: 1 -> 2 -> 3 -> NULL

Creando una Lista Doblemente Enlazada con Estructura Referenciadora

Ahora, subamos de nivel y creemos una lista doblemente enlazada. Esto es como una lista enlazada regular, pero cada nodo también tiene un puntero al nodo anterior. Es como una carretera de dos vías en lugar de una de una vía.

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

struct DoubleNode {
int data;
struct DoubleNode* next;
struct DoubleNode* prev;
};

// Función para crear un nuevo nodo
struct DoubleNode* createDoubleNode(int data) {
struct DoubleNode* newNode = (struct DoubleNode*)malloc(sizeof(struct DoubleNode));
newNode->data = data;
newNode->next = NULL;
newNode->prev = NULL;
return newNode;
}

// Función para imprimir la lista doblemente enlazada hacia adelante
void printForward(struct DoubleNode* head) {
struct DoubleNode* current = head;
printf("Hacia adelante: ");
while (current != NULL) {
printf("%d <-> ", current->data);
current = current->next;
}
printf("NULL\n");
}

// Función para imprimir la lista doblemente enlazada hacia atrás
void printBackward(struct DoubleNode* tail) {
struct DoubleNode* current = tail;
printf("Hacia atrás: ");
while (current != NULL) {
printf("%d <-> ", current->data);
current = current->prev;
}
printf("NULL\n");
}

int main() {
struct DoubleNode* head = createDoubleNode(1);
head->next = createDoubleNode(2);
head->next->prev = head;
head->next->next = createDoubleNode(3);
head->next->next->prev = head->next;

struct DoubleNode* tail = head->next->next;

printForward(head);
printBackward(tail);

return 0;
}

En este ejemplo:

  1. Nuestra estructura DoubleNode ahora tiene tanto los punteros next como prev.
  2. Creamos funciones para imprimir la lista tanto hacia adelante como hacia atrás.
  3. En main, creamos una lista doblemente enlazada y establecemos tanto los punteros next como prev.

Ejecutar este programa debería darte:

Hacia adelante: 1 <-> 2 <-> 3 <-> NULL
Hacia atrás: 3 <-> 2 <-> 1 <-> NULL

Creando un Árbol con Estructura Referenciadora

Por último, pero no menos importante, creemos un árbol binario simple usando nuestra estructura referenciadora. Piensa en esto como un árbol familiar, donde cada persona puede tener hasta dos hijos.

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

struct TreeNode {
int data;
struct TreeNode* left;
struct TreeNode* right;
};

// Función para crear un nuevo nodo
struct TreeNode* createTreeNode(int data) {
struct TreeNode* newNode = (struct TreeNode*)malloc(sizeof(struct TreeNode));
newNode->data = data;
newNode->left = NULL;
newNode->right = NULL;
return newNode;
}

// Función para realizar una traversión in-order
void inOrderTraversal(struct TreeNode* root) {
if (root != NULL) {
inOrderTraversal(root->left);
printf("%d ", root->data);
inOrderTraversal(root->right);
}
}

int main() {
struct TreeNode* root = createTreeNode(1);
root->left = createTreeNode(2);
root->right = createTreeNode(3);
root->left->left = createTreeNode(4);
root->left->right = createTreeNode(5);

printf("Traversión in-order del árbol: ");
inOrderTraversal(root);
printf("\n");

return 0;
}

En este último ejemplo:

  1. Nuestra estructura TreeNode tiene punteros left y right para los nodos hijos.
  2. Creamos una función para realizar una traversión in-order, que visitas el subárbol izquierdo, luego la raíz, y luego el subárbol derecho.
  3. En main, creamos un árbol binario simple y realizamos una traversión in-order.

Ejecutar este programa debería output:

Traversión in-order del árbol: 4 2 5 1 3

Y ahí lo tienes, amigos. Hemos recorrido el país de las estructuras referenciadoras, desde listas enlazadas simples hasta listas doblemente enlazadas y hasta árboles binarios. Recuerda, la práctica hace al maestro, así que no tengas miedo de experimentar con estas estructuras y crear tus propias variaciones. ¡Quién sabe? Podrías ser la próxima persona en inventar una estructura de datos revolucionaria!

¡Feliz programación, y que las estructuras referenciadoras estén contigo!

Credits: Image by storyset