14
14
LISTAS ENLAZADAS
Estructuras estáticas frente a dinámicas.................................................................................................................1
Listas enlazadas........................................................................................................................................................3
Clasificación de las listas enlazadas.................................................................................................................... 4
Declaración y creación de un nodo......................................................................................................................... 4
Puntero de entrada a la lista.................................................................................................................................... 6
Operaciones con listas enlazadas............................................................................................................................ 7
Creación de una lista enlazada............................................................................................................................. 8
Recorrido de una lista........................................................................................................................................... 9
Insertar un nodo como primero de la lista......................................................................................................... 10
Insertar un nodo como último de la lista............................................................................................................ 10
Insertar un nodo en su posición en una lista ordenada ..................................................................................... 11
Eliminar primer nodo de la lista......................................................................................................................... 12
Eliminar último nodo de la lista......................................................................................................................... 12
Eliminar un nodo intermedio de la lista............................................................................................................. 13
Eliminar todos los nodos de una lista................................................................................................................. 13
Ejercicios resueltos................................................................................................................................................ 14
Ejercicios propuestos............................................................................................................................................. 21
Puesto que normalmente tendremos menos datos que dicha dimensión máxima, en los
programas se maneja en una variable la longitud del "subarray" en el que hemos colocado los
valores (diml: dimensión lógica o efectiva) y únicamente a esa parte del array es a la que se
accede. Esta dimensión lógica puede variar durante la ejecución del programa, y por supuesto
en distintas ejecuciones del mismo (desde 1 a la dimensión máxima del array), pero sin
embargo la propia dimensión máxima del array no puede variar a no ser que volvamos a
compilar el programa.
Hay otra técnica para representar una lista: las listas enlazadas. En esta técnica los elementos
de la lista (también llamados NODOS), son variables dinámicas, que se crean durante la
ejecución del propio programa sólo cuando de necesiten, y se destruirán igualmente cuando
ya no se necesiten. Estos nodos, en vez de ir “físicamente” uno detrás de otro como en un
array, van “lógicamente” uno detrás de otro. Cada elemento contiene, además de la
información propia que se desea guardar en cada uno de ellos (como en un elemento de un
array), información sobre la posición del siguiente nodo. ¿Cómo?. Almacenándose en cada
nodo un puntero al siguiente nodo de la lista enlazada.
Una estructura de datos dinámica de construye con nodos. Cada nodo está formado de
Listas enlazadas
Se llaman así ya que cada nodo contiene un puntero que lo enlaza con el siguiente nodo. Las
lista enlazadas son estructura de datos dinámicas, que se van construyendo dentro de la
ejecución del programa. El acceso a una lista enlazada es como el juego de los niños “la caza
del tesoro”, en el que a cada niño se le da una pista del lugar oculto de la siguiente pista y la
cadena de pistas conduce finalmente al tesoro.
Para crear una lista enlazada, comenzamos creando el primer nodo1 y guardando su dirección
en el puntero de entrada a la lista. Crearemos entonces un segundo nodo y almacenamos su
dirección en el campo de enlace del primer nodo. Continuamos este proceso, creando un
1
Recordar que los nodos son variables dinámicas que se crearán con la función que convenga: calloc ó malloc
nuevo nodo y almacenando su dirección en el campo enlace del nodo anterior, hasta que
alcanzamos el final de la lista.
En el puntero de enlace del último nodo de la lista almacenaremos la constante NULL, para
marcar que ese nodo no tiene “siguiente”.
Se ha declarado el tipo NODO con un componente genérico, para poder extender los
algoritmos que veremos a continuación. En cada implementación concreta tipo_componente
será un tipo de dato válido en C. Por ejemplo para crear nodos que almacenen un dato de tipo
long, podrá sustituirse el literal tipo_componente por el tipo de dato long o bien anteponer la
siguiente definición a la definición del tipo NODO:
typedef tipo_componente long;
Los nodos pueden tener varios componentes de datos, definiendo varios miembros en la
estructura. Por ejemplo la siguiente definición de NODO contienen 4 componentes de datos:
typedef struct Nodo_pers {
char[80] nombre;
long num_matricula;
int edad;
float peso;
struct Nodo_pers *sig;
} NODO;
Siempre la definición del tipo NODO debe de incluir un último miembro de enlace a un nodo
del mismo tipo, por tanto definido como puntero a la propia estructura definida para el nodo.
En el último ejemplo es el puntero sig a struct Nodo_pers.
Para crear un nodo puede utilizarse cualquiera de las funciones de reserva de memoria
dinámica. Siempre deberemos disponer de un puntero al tipo NODO con el que “sustentar” el
nodo.
En este ejemplo ese puntero es nuevo:
NODO *nuevo;
...
nuevo = (NODO *) malloc (sizeof (NODO));
/* Se carga el dato y el puntero del nuevo NODO
con los valores que corresponda */
nuevo -> dato = ...
nuevo -> sig = ...
Este puntero de entrada deberá ser declarado como una variable puntero al tipo nodo de la
lista:
NODO *lista;
Si la lista estuviera vacía, este puntero cabecera tendrá valor NULL, como por ejemplo, al
principio del programa, cuando todavía no se ha creado ningún nodo de la lista
Para pasar una lista a una función bastará pasar el puntero de entrada a la misma. Según la
necesidad de que la función modifique o no el contenido del puntero de entrada a la lista,
éste se pasará por valor o por referencia. Por referencia se deberá pasar un puntero a
puntero.
Este algoritmo es válido incluso para listas vacías. En ese caso simplemente se sale del bucle
sin hacer nada.
Este algoritmo es válido incluso para listas vacías. En ese caso el puntero lista estará a NULL,
que será el valor que se cargue en el puntero sig del nodo creado.
indice = lista
Si (indice == NULL)
/* Lista vacía. El nodo es el primero */
lista = nuevo
sino
/* Recorremos la lista hasta llegar a su final */
Mientras (indice -> sig != NULL) hacer
indice = indice -> sig
Fin Mientras
Fin Si
Este algoritmo es válido incluso para listas vacías, en cuyo caso el nodo a añadir será el
primero.
indice = lista
anterior = NULL
Si (indice == NULL)
/* Lista vacía. El nodo es el primero */
lista = nuevo
sino
/* Recorremos la lista hasta encontrar un nodo con el contenido
de dato mayor que el del nodo a insertar, o bien hasta llegar
al final de la lista */
Mientras (indice -> dato < nuevo -> dato)
AND (indice -> sig != NULL) hacer
anterior = indice
indice = indice -> sig
Fin Mientras
Si (indice -> sig == NULL) AND (indice -> dato < nuevo -> dato)
/* El nodo se inserta al final de la lista */
nuevo -> sig = NULL
indice -> sig = nuevo
sino
Si (anterior == NULL)
/* El nodo se inserta como primero */
nuevo -> sig = lista
lista = nuevo
sino
/* El nodo se inserta en medio de la lista */
nuevo -> sig = indice
anterior -> sig = nuevo
Fin Si
Fin Si
Fin Si
El algoritmo, lo primero que comprobamos es que la lista no esté vacía. De ser así, no se hace
nada. Si la lista no tuviera más que un nodo, se eliminaría dicho nodo y el puntero lista
quedaría a valor NULL (valor del puntero sig del primer nodo), es decir lista vacía.
Para liberar la memoria del nodo que eliminamos de la lista utilizaremos en C la función
free.
Si (anterior == NULL)
/* Sólo existe un nodo. Eliminamos primer nodo */
lista = NULL
liberar memoria del NODO apuntado por el puntero indice
sino
anterior -> sig = NULL
liberar memoria del NODO apuntado por el puntero indice
Fin Si
Fin Si
El algoritmo, lo primero que comprueba es que la lista no esté vacía. De ser así, no se hace
nada. Este algoritmo es válido incluso para listas con un único nodo. En ese caso libera el
primer y único nodo de la lista, quedando la misma vacía (puntero lista a valor NULL).
El algoritmo, lo primero que comprueba es que la lista no esté vacía. De ser así, no se hace
nada. Este algoritmo es válido incluso para listas con un único nodo. En ese caso libera el
primer y único nodo de la lista, quedando la misma vacía (puntero lista a valor NULL).
Este algoritmo es válido incluso para listas vacías. En ese caso no hace nada.
Al finalizar el algoritmo la lista queda vacía, es decir el puntero lista a valor NULL y liberada
la memoria de todos los nodos.
Ejercicios resueltos
EJERCICIO 1
Escribir una función que devuelva “cierto” si una lista que se la pasa como parámetro está
vacía, y “falso” en otro caso.
Codificación
Suponiendo las siguientes declaraciones:
typedef int Item;
typedef struct Nodo {
Item dato;
struct Nodo *sig;
} NODO;
Comentario
Suponiendo que en el módulo llamador (función main, por ejemplo) el puntero de entrada a
la lista es
NODO *lista;
Un ejemplo de llamada a esta función será
if (EstaVacia (lista))
.....
else
.....
En este caso la función EstaVacia recibe en su parámetro formal lst (definido como
NODO *) el valor del parámetro real en la llamada lista (puntero de entrada a la lista).
Dentro de la función tenemos acceso a la lista a través del puntero lst, pero no podemos
cambiar su primer nodo por otro, pues no podemos modificar dentro de la función el
contenido del puntero lista, que es el parámetro real en la llamada.
Figura 7. Paso de una lista a una función. Paso por valor de su puntero de entrada
En la figura se representa:
A) Situación en el módulo llamador, antes de llamar a la función. La lista es sustentada por el
puntero lista de entrada a la misma.
B) Situación vista “desde dentro” de la función llamada, cuando ésta se está ejecutando.
Tenemos acceso a la lista porque en el puntero lst se ha recibido el valor parámetro real en
la llamada lista, pero no tenemos acceso al propio parámetro real lista, por lo que no
podremos modificar su contenido. Si modificamos dentro de la función la dirección del
primer nodo al que apunta, esto no tendrá efecto en lista al terminar la función.
Dentro de la función lst será un puntero que contendrá la dirección del primer nodo
EJERCICIO 2
Escribir una función que devuelva el número de nodos de una lista enlazada.
Codificación
Suponiendo las declaraciones del ejercicio anterior, la función NumeroDeNodos puede ser:
int NumeroDeNodos (NODO *lst) {
int k=0;
NODO *p;
p = lst;
while (p != NULL) {
k++;
p = p -> sig;
}
return (k);
}
EJERCICIO 3
Escribir una función que elimine el nodo i de una lista enlazada.
Análisis
Deberemos recorrer la lista contando el número de nodos, terminando cuando encontremos el
nodo i a eliminar. Se hace un tratamiento diferente si debe eliminarse el primer nodo u otro de
la lista.
Si la lista tuviera menos de i nodos, o el valor de i recibido fuera cero o negativo, la función
terminará sin hacer nada.
Codificación
Suponiendo las declaraciones del ejercicio anterior, la función EliminaPosicion puede ser:
void EliminaPosicion (NODO **lst, int i) {
int k;
NODO *indice, *anterior;
if (i <= 0) return;
if (k==i)
{ if (anterior == NULL)
free (indice);
}
}
Comentario
Suponiendo que en el módulo llamador (función main, por ejemplo) el puntero de entrada a
la lista es
NODO *lista;
Un ejemplo de llamada a esta función será
EliminaPosicion (&lista, 5)
En este caso la función EliminaPosicion recibe en su parámetro formal lst (definido como
NODO **) la dirección del parámetro real en la llamada lista (puntero de entrada a la lista).
Dentro de la función tenemos acceso al propio parámetro real lista (a través de *lst), por lo
que podemos modificar dentro de la función el contenido del puntero lista (podemos
cambiar su primer nodo por otro).
Figura 8. Paso de una lista a una función. Paso por referencia de su puntero de entrada
En la figura se representa:
En el caso de funciones que pueden necesitar cambiar el primero nodo de una lista por otro,
la forma de pasar la lista a la función deber ser a través de un puntero a puntero (NODO
**).
EJERCICIO 4
Escribir una función que reciba como parámetro una lista enlazada y un valor del tipo “dato”
de los nodos de la lista. Deberá crear un nuevo nodo e insertarlo como primero de la lista,
almacenando en el mismo el valor del tipo “dato” recibido.
Codificación
Suponiendo las declaraciones del ejercicio anterior, la función InsertaPrimero puede ser:
void InsertaPrimero (NODO **lst, Item info) {
NODO *nuevo;
*lst = nuevo;
}
EJERCICIO 5
Escribir una función que reciba como parámetro una lista enlazada y un valor a buscar en los
nodos de la lista. Nos devolverá como valor de retorno de la función un puntero al nodo que
contenga la primera aparición del valor a buscar, y en un parámetro entero el número de dicho
nodo. En caso de no encontrar el valor buscado el puntero lo devolverá con valor NULL y el
entero con valor 0.
Codificación
Suponiendo las declaraciones del ejercicio anterior, la función BuscarLista puede ser:
NODO *BuscarLista (NODO *lst, Item info, int *num) {
NODO *indice;
*num = 0;
return (NULL);
}
EJERCICIO 6
Escribir un programa que genere una lista enlazada de números enteros aleatorios de tal forma
que se almacenen en la lista en el orden en que se generan. Posteriormente deberá indicar el
número de nodos de la lista y presentarse la lista completa en pantalla. Antes de finalizar el
programa deberá eliminarse la lista enlazada.
Programa codificado en C
Ver fuente LISTRAND.C
EJERCICIO 7
Un conjunto es una secuencia de elementos del mismo tipo, sin duplicados.
Escribir un programa para representar conjuntos de números enteros mediante una lista
enlazada. El programa generará un conjunto de números enteros aleatorios. La dimensión del
conjunto será también un valor aleatorio entre 0 y 50.
Programa codificado en C
Ver fuente CONJUNTO.C
Ejercicios propuestos
EJERCICIO 8
Escribir un programa que genere una lista enlazada de números enteros positivos y aleatorios,
de tal forma que se almacenen en la lista en el orden en que se generan. La lista podrá
contener elementos repetidos.
Posteriormente entrará en un bucle de forma que solicitará por pantalla un número y lo
localizará en la lista, eliminando todos los nodos de la lista que contengan dicho valor,
solicitando posteriormente otro número y actuando de la misma forma.
Deberá siempre presentar el número de nodos al comienzo y al final de cada iteración del
bucle, de forma que comprobemos cuantos nodos se eliminan en cada momento.
El bucle terminará cuando se teclee un número negativo.
EJERCICIO 9
Escribir un programa que genere una lista enlazada de números enteros y aleatorios, de tal
forma que se almacenen en la lista de forma ordenada. La lista podrá contener elementos
repetidos.
Más tarde solicitará un número entero por teclado y eliminará todos los nodos de la lista que
cumplan la condición de que su contenido es estrictamente mayor que el valor tecleado.
EJERCICIO 10
Escribir un programa que genere una lista enlazada de números enteros positivos y aleatorios,
de tal forma que se almacenen en la lista en el orden en que se generan. La lista podrá
contener elementos repetidos.
Más tarde generará una segunda lista en la que se almacenarán los nodos de la primera pero
ordenados descendentemente, y ya sin elementos repetidos.
Por último se indicará el número de nodos de esta segunda lista y se presentará completa en
pantalla.
EJERCICIO 11
Con la representación de conjuntos realizada en un ejercicio anterior, añadir las siguientes
operaciones básicas:
UnionConjuntos, para realizar la unión del conjunto c1 con el c2 en el conjunto c3. Por
tanto c3 contendrá todos los elementos de c1 y c2 no repetidos.
DiferenciaConjuntos:, para realizar la diferencia del conjunto c1 con el c2 dejando el
resultado en c3. Por tanto c3 contendrá todos los elementos de c1 que no estén en c2.
IncluidoEnConjunto, para indicar si c1 está incluido en c2. Para que esto ocurra todos
los elementos de c1 deben estar en c2.
InterseccionConjuntos, para realizar en c3 la intersección de los conjuntos c1 y c2. Por
tanto c3 contendrá todos los elementos de c1 que también están en c2.
EJERCICIO 12
Supongamos un fichero que tiene en cada línea un nombre de persona, y que nos piden
presentarlos ordenados alfabéticamente en pantalla.
Solución 1:
Crear un array de cadenas de caracteres en memoria, cargar el fichero en dicho array, ordenar
el array y presentarlo en pantalla.
Recordemos que el array es una estructura estática cuyo tamaño queda fijado en tiempo de
compilación, y por lo tanto permanece inalterable en memoria mientras se está ejecutando el
programa.
Como normalmente el número de elementos a utilizar variará en cada ejecución (el fichero de
una ejecución a otra puede tener distinto número de elementos), estaremos obligados a
trabajar con una dimensión física del array (MAXDIM, el máximo número de elementos que
se prevé tratar en cualquier ejecución del programa) que se fijará en tiempo de compilación, y
otra efectiva o lógica, de forma que a través de una variable manejemos la longitud del
subarray en la que hemos colocado valores en esa ejecución, y únicamente a esa parte del
array es a la que accedamos y tratemos.
Esta dimensión efectiva puede variar en distintas ejecuciones del programa, pero sin embargo
la dimensión máxima del array no es posible modificarla, a no ser que modifiquemos el fuente
y volvamos a compilar el programa.
Problemas:
En tiempo de programación debemos plantearnos y escoger la dimensión física del array.
Cualquier futura modificación de ella requiere la intervención del programador, y volver a
compilar el programa.
Siempre existirá ese fatal día en que tengamos que tratar más elementos que los máximos
esperados.
El array no puede definirse todo lo grande que se desee, pues estamos limitados por el
tamaño del segmento de datos.
Solución 2:
Utilizar una lista enlazada que se irá creando en tiempo de ejecución y así siempre se
acomodará al número de elementos a tratar, ni uno más ni uno menos.
Se leerá el fichero de entrada y por cada registro leído se creará un nodo en la lista de forma
ordenada. Posteriormente se presenta la lista en pantalla.
No es necesario saber de antemano cómo será la lista de larga (no es necesario que definamos
una dimensión máxima), puesto que tal lista puede expandirse conforme se ejecute el
programa, creándose los nodos necesarios, por ser variables dinámicas.
Problemas:
Dificultad de tratamiento.
Ventajas:
El tamaño de la lista se acomoda exactamente al número de datos a almacenar en cada
ejecución. No es necesario que en tiempo de programación debemos plantearnos la
dimensión física a dar a la lista.
Los nodos de la lista enlazada se crean en el montículo, y éste es mucho mayor que el
segmento de datos, por lo que las listas enlazadas podrán ser mucho más grandes que las
listas estáticas o arrays.
Realizar un programa que utilice la solución 2 para, a partir de un fichero de texto que
contiene varios registros con una cadena de caracteres en cada registro, generar un segundo
fichero en disco ordenado.
EJERCICIO 13
Considérense la siguiente estructura
typedef struct Nodo {
int PR_id;
long PR_time;
float PR_pc;
struct Nodo *sig;
} PROCESO;
que sirven como base para la construcción de una lista enlazada de nodos PROCESO.