Estructuras de datos

Listas ligadas

Una lista ligada es una estructura de datos lineal en la que cada elemento (llamado nodo) se conecta a otro(s) mediante el uso de apuntadores

 

A diferencia de un arreglo, los nodos de la lista se encuentran en direcciones aleatorias de memoria

Listas ligadas

Podemos tener distintos tipos de listas, entre las que destacan:

  • Listas ligadas
  • Listas doblemente ligadas
  • Listas circulares

Listas ligadas

Cada nodo se conforma de dos partes:

  1. dato: El valor que guarda
    • Y ojo que no necesariamente es un valor primitivo

Nodos

dato

Cada nodo se conforma de dos partes:

  1. dato: El valor que guarda
    • Y ojo que no necesariamente es un valor primitivo
  2. apuntador: Un apuntador al siguiente nodo de la secuencia
    • Dependiendo del tipo de lista que usemos, un nodo puede tener más de un apuntador

Nodos

dato

El primer nodo en una lista recibe también el nombre de cabecera

 

Cuando la lista se encuentra vacía, la cabecera apunta a NULL

Nodo cabecera

En este ejemplo ocurren varias cosas:

  1. Primero definimos la estructura de los nodos de la lista
  2. Noten que el apuntador es del mismo tipo del nodo
    • ¡Hola, autoreferencia!
  3. Lo siguente que necesitamos es una función para crear un nuevo nodo con base en el dato que queremos almacenar
  4. Noten el uso de la flecha para almacenar valores
  5. La función regresa un apuntador de tipo Nodo

Nodos

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

//Estructura de un nodo para una lista ligada
struct Nodo
{
  int dato;
  struct Nodo *siguiente;
};

//Función para crear un nodo nuevo
struct Nodo* nuevoNodo(int dato) {
    struct Nodo* n =  (struct Nodo*)malloc(sizeof(struct Nodo));
    n->dato = dato;
    n->siguiente = NULL;
    return n;
}

int main() {
}

En este ejemplo ocurren varias cosas:

  1. Iniciamos dos apuntadores, uno para mantener la referencia de la lista y uno de uso general
  2. Creamos un nodo nuevo con el dato 5 y lo asignamos a cabecera
    • La función nuevoNodo regresa un apuntador
  3. Creamos un nodo nuevo con el dato 4 y lo asignamos a n
    • Asignamos cabecera al apuntador siguiente del nuevo nodo
    • Asignamos el valor del apuntador n a cabecera
  4. Se repite lo mismo para otros números (3 al 1)
  5. Asignamos el valor de cabecera a n
  6. Ciclamos la lista a través de los apuntadores siguiente hasta que el apuntador n sea NULL

Una lista de ejemplo

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

//Estructura de un nodo para una lista ligada
struct Nodo
{
  int dato;
  struct Nodo *siguiente;
};

//Función para crear un nodo nuevo
struct Nodo* nuevoNodo(int dato) {
    struct Nodo* n =  (struct Nodo*)malloc(sizeof(struct Nodo));
    n->dato = dato;
    n->siguiente = NULL;
    return n;
}

int main() {
	struct Nodo *cabecera = NULL;
    struct Nodo *n = NULL;
    
    cabecera = nuevoNodo(5);
    
    n = nuevoNodo(4);
    n->siguiente = cabecera;
    cabecera = n;

    n = nuevoNodo(3);
    n->siguiente = cabecera;
    cabecera = n;

    n = nuevoNodo(2);
    n->siguiente = cabecera;
    cabecera = n;
   
    n = nuevoNodo(1);
    n->siguiente = cabecera;
    cabecera = n;

	n = cabecera;
    while (n != NULL) {
        printf("%d ", n->dato);
        n = n->siguiente;
      }
   
}

Limpiando un poquito el código anterior nos puede quedar algo así:

Una lista de ejemplo

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

//Estructura de un nodo para una lista ligada
struct Nodo
{
  int dato;
  struct Nodo *siguiente;
};

//Función para crear un nodo nuevo
struct Nodo* nuevoNodo(int dato) {
    struct Nodo* n =  (struct Nodo*)malloc(sizeof(struct Nodo));
    n->dato = dato;
    n->siguiente = NULL;
    return n;
}

struct Nodo* agregaDato(struct Nodo *l, int dato) {
    struct Nodo *n = NULL;
    n = nuevoNodo(dato);
    n->siguiente = l;
    l = n;
    n = NULL;
    return l;
}

void imprimeLista(struct Nodo *l) {
    struct Nodo *n = NULL;
	n = l;
    while (n != NULL) {
        printf("%d ", n->dato);
        n = n->siguiente;
      }
}

int main() {
	struct Nodo *cabecera = NULL;
    struct Nodo *n = NULL;
    
    cabecera=agregaDato(cabecera, 5);
    cabecera=agregaDato(cabecera, 4);
    cabecera=agregaDato(cabecera, 3);
    cabecera=agregaDato(cabecera, 2);
    cabecera=agregaDato(cabecera, 1);

    imprimeLista(cabecera);
}

Y si de verdad quieren que les duela la cabeza podrían intentar algo así:

  • ¡Hola, indirección!

Una lista de ejemplo

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

//Estructura de un nodo para una lista ligada
struct Nodo
{
  int dato;
  struct Nodo *siguiente;
};

//Función para crear un nodo nuevo
struct Nodo* nuevoNodo(int dato) {
    struct Nodo* n =  (struct Nodo*)malloc(sizeof(struct Nodo));
    n->dato = dato;
    n->siguiente = NULL;
    return n;
}

void agregaDato(struct Nodo **l, int dato) {
    struct Nodo *n = NULL;
    n = nuevoNodo(dato);
    n->siguiente = *l;
    *l = n;
    n = NULL;
}

void imprimeLista(struct Nodo *l) {
    struct Nodo *n = NULL;
	n = l;
    while (n != NULL) {
        printf("%d ", n->dato);
        n = n->siguiente;
      }
}

int main() {
	struct Nodo *cabecera = NULL;
    struct Nodo *n = NULL;

	agregaDato(&cabecera, 5);
    agregaDato(&cabecera, 4);
    agregaDato(&cabecera, 3);
    agregaDato(&cabecera, 2);
    agregaDato(&cabecera, 1);

    imprimeLista(cabecera);
}

En lo general, la memoria no es un problema en sistemas modernos, pero es buena práctica revisar que:

  1. la función de asignación de memoria esté funcionando y no nos regrese un valor nulo
  2. cualquier función que dependa de una función de asignación de memoria (como nuevoNodo, por ejemplo) revise si obtiene un valor nulo o no

Una lista de ejemplo

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

//Estructura de un nodo para una lista ligada
struct Nodo
{
  int dato;
  struct Nodo *siguiente;
};

//Función para crear un nodo nuevo
struct Nodo* nuevoNodo(int dato) {
    struct Nodo* n =  (struct Nodo*)malloc(sizeof(struct Nodo));
    if (n != NULL) {
    	n->dato = dato;
        n->siguiente = NULL;   
    }
    return n;
}

struct Nodo* agregaDato(struct Nodo *l, int dato) {
    struct Nodo *n = NULL;
    n = nuevoNodo(dato);
    if (n!=NULL) {
        n->siguiente = l;
        l = n;
        n = NULL;
    }
    return l;
}

void imprimeLista(struct Nodo *l) {
    struct Nodo *n = NULL;
	n = l;
    while (n != NULL) {
        printf("%d ", n->dato);
        n = n->siguiente;
      }
}

int main() {
	struct Nodo *cabecera = NULL;

    cabecera = agregaDato(cabecera, 5);
    cabecera = agregaDato(cabecera, 4);
    cabecera = agregaDato(cabecera, 3);
    cabecera = agregaDato(cabecera, 2);
    cabecera = agregaDato(cabecera, 1);
    imprimeLista(cabecera);
}

Las operaciones básicas que podemos realizar en una lista son:

  • Inserción al inicio de una lista
  • Inserción al final de una lista
  • Inserción en una posición específica de una lista
  • Eliminación al inicio de una lista
  • Eliminación al final de una lista
  • Eliminación en una posición específica de una lista
  • Recorrer una lista

Operaciones en una lista

¿Cómo creen que sería el código para hacer cada una de esas operaciones?

Estructuras de datos: Listas ligadas

By Gilberto 🦁

Estructuras de datos: Listas ligadas

Listas ligadas

  • 152