Estructuras dinámicas lineales (ii)
Introducción
Continúa la exposición de los distintos algoritmos existentes para manipular listas simplemente enlazadas; sin el lección anterior se presentaban algoritmos iterativos, en ésta se verán en su versión recursiva.
Implementación de listas simplemente enlazadas en la notación algorítmica (ii)
Operaciones sobre listas (simplemente enlazadas) implementadas recursivamente
Recorrido de una lista simplemente enlazada (de forma recursiva)
acción recorrer (cabezaLista ∈ puntero a nodo) inicio
si cabezaLista ≠ NIL entonces escribir cabezaLista↑.numero
llamar recorrer (cabezaLista↑.siguiente) fin si
fin acción
En el algoritmo que se presenta el caso base podría decirse que es elíptico1 puesto que ninguna acción se debe realizar para recorrer una lista vacía; así, sólo aparece el caso recursivo que se limita a imprimir el contenido de la cabeza de la lista recibida y procede a recorrer el resto de la lista (una lista cuya cabeza es el elemento que sigue al actual).
A continuación se muestra de forma gráfica el recorrido recursivo de una lista con los números 1, 2 y 3:
llamar recorrer (lista)
2
3
lista 1
NIL
inicio
si cabezaLista ≠≠ NIL entonces escribir cabezaLista↑↑.numero
llamar recorrer (cabezaLista↑.siguiente) fin si
fin acción
La primera invocación de la acción imprime el contenido de la cabeza de la lista (1) y invoca de nuevo la acción para el resto de la lista.
3
lista 2
NIL
inicio
si cabezaLista ≠≠ NIL entonces escribir cabezaLista↑↑.numero
llamar recorrer (cabezaLista↑.siguiente) fin si
fin acción
La segunda invocación imprime el contenido de la cabeza de la lista, en este caso el resto de la lista anterior, con lo cual aparece por pantalla el número 2 y se invoca nuevamente la acción para el resto de la lista.
lista 3 NIL
inicio
si cabezaLista ≠≠ NIL entonces escribir cabezaLista↑↑.numero
llamar recorrer (cabezaLista↑.siguiente) fin si
fin acción
La tercera invocación imprime el contenido de la cabeza de la lista, en este caso el resto del resto de la lista anterior, mostrándose el número 3 e invocando la acción para el resto de la lista.
lista NIL
inicio
si cabezaLista ≠ NIL entonces escribir cabezaLista↑.numero
llamar recorrer (cabezaLista↑.siguiente)
fin si fin acción
Esta cuarta invocación recibe una lista vacía, por tanto, no hace nada y retorna a la tercera invocación que finaliza y retorna a la segunda que, a su vez, finaliza y retorna a la primera invocación que también finaliza y devuelve el control al programa principal.
Búsqueda de un elemento en una lista simplemente enlazada (de forma recursiva)
La operación de búsqueda se basa en la de recorrido; básicamente se trata de recorrer la lista hasta que se encuentre el elemento o se llegue al final, retornando un puntero al elemento encontrado (o NIL en caso contrario).
puntero a nodo función buscar (cabezaLista ∈ puntero a nodo, elemento ∈ entero) inicio
si cabezaLista ≠ NIL entonces
si cabezaLista↑.numero = elemento entonces buscar ß cabezaLista
si no
llamar buscar (cabezaLista↑.siguiente,elemento) fin si
si no
buscar ß NIL fin si
fin acción
Inserción de un elemento en una lista simplemente enlazada
Al igual que en la lección anterior, se procederá a implementar las operaciones de inserción en una cola y en una lista ordenada ascendentemente; la inserción en una pila no se puede implementar de forma recursiva puesto que es un problema que no consiste en una repetición de pasos.
Inserción en una cola (de forma recursiva)
Como ya se ha dicho, la inserción en una cola precisa buscar el último elemento de la lista e insertar el nuevo elemento detrás del mismo.
acción insertarCola (cabezaLista ∈ puntero a nodo, dato_nuevo ∈ entero) inicio
si cabezaLista ≠ NIL entonces
llamar insertarCola (cabezaLista↑.siguiente,dato_nuevo) si no
crear (cabezaLista)
cabezaLista↑.siguiente ß NIL cabezaLista↑.numero ß dato fin si
fin acción
A continuación se muestra de forma gráfica la inserción del número 3 en una lista que ya contiene los enteros 1 y 2: insertarCola (lista, 3) 2 lista 1 NIL inicio
si cabezaLista ≠≠ NIL entonces
llamar insertarCola (cabezaLista↑↑.siguiente,dato_nuevo)
si no
crear (cabezaLista)
cabezaLista↑.siguiente ß NIL cabezaLista↑.numero ß dato fin si
fin acción
En la primera invocación se comprueba que la cola no está vacía por lo que se procede a insertar, recursivamente, el elemento en el resto de la cola (2).
lista 2 NIL
inicio
si cabezaLista ≠≠ NIL entonces
llamar insertarCola (cabezaLista↑↑.siguiente,dato_nuevo)
si no
crear (cabezaLista)
cabezaLista↑.siguiente ß NIL cabezaLista↑.numero ß dato fin si
fin acción
En la segunda invocación se comprueba que la cola no está vacía por lo que se invoca recursivamente la acción para insertar el elemetno.
lista NIL
inicio
si cabezaLista ≠ NIL entonces
llamar insertarCola (cabezaLista↑.siguiente,dato_nuevo)
si no
crear (cabezaLista)
cabezaLista↑.siguiente ß NIL cabezaLista↑.numero ß dato fin si
fin acción
En la tercera invocación la cola está vacía, por tanto, es posible crear un nuevo elemento.
lista
inicio
si cabezaLista ≠ NIL entonces
llamar insertarCola (cabezaLista↑.siguiente,dato_nuevo) si no
crear (cabezaLista)
cabezaLista↑.siguiente ß NIL cabezaLista↑.numero ß dato fin si
fin acción
El nuevo elemento es creado.
lista NIL
inicio
si cabezaLista ≠ NIL entonces
llamar insertarCola (cabezaLista↑.siguiente,dato_nuevo) si no
crear (cabezaLista)
cabezaLista↑↑.siguiente ßß NIL
cabezaLista↑.numero ß dato fin si
fin acción
El nuevo elemento es el primero y el último de su cola.
lista 3 NIL
inicio
si cabezaLista ≠ NIL entonces
llamar insertarCola (cabezaLista↑.siguiente,dato_nuevo) si no
crear (cabezaLista)
cabezaLista↑.siguiente ß NIL
cabezaLista↑↑.numero ßß dato fin si
fin acción
Se asigna un valor al nuevo elemento y se retorna desde la tercera invocación a la segunda.
3
lista 2
NIL
inicio
si cabezaLista ≠ NIL entonces
llamar insertarCola (cabezaLista↑.siguiente,dato_nuevo) si no
crear (cabezaLista)
cabezaLista↑.siguiente ß NIL cabezaLista↑.numero ß dato
fin si fin acción
La segunda invocación ya ha insertado, recursivamente, el nuevo elemento así que puede retornar a la primera.
2
lista 1
inicio
si cabezaLista ≠ NIL entonces
llamar insertarCola (cabezaLista↑.siguiente,dato_nuevo) si no
crear (cabezaLista)
cabezaLista↑.siguiente ß NIL cabezaLista↑.numero ß dato
acción insertarOrdenado (cabezaLista ∈ puntero a nodo, dato ∈ entero) variables
nuevo ∈ puntero a nodo inicio
si cabezaLista ≠ NIL entonces
si cabezaLista↑.numero ≥ dato entonces crear (nuevo)
nuevo↑.siguiente ß cabezaLista↑.siguiente cabezaLista↑.siguiente ß nuevo
nuevo↑.numero ß cabezaLista↑.numero cabezaLista↑.numero ß dato
si no
llamar insertarOrdenado (cabezaLista↑.siguiente,dato) fin si
si no
crear (cabezaLista)
cabezaLista↑.siguiente ß NIL cabezaLista↑.numero ß dato fin si
fin acción
Eliminar un elemento de una lista simplemente enlazada (de forma recursiva)
El algoritmo básico de eliminación simplemente debe recorrer la lista hasta encontrar el dato a eliminar, enlazar la lista de forma adecuada y destruir el nodo sobrante.
acción eliminarElemento (cabezaLista ∈ puntero a nodo, dato ∈ entero) variables
cursor ∈ puntero a nodo inicio
si cabezaLista ≠ NIL entonces
si cabezaLista↑.numero = dato entonces si cabezaLista↑.siguiente ≠ NIL entonces cursor ß cabezaLista↑.siguiente cabezaLista↑.numero ß cursor↑.numero cabezaLista↑.siguiente ß cursor↑.siguiente destruir (cursor)
si no
destruir (cabezaLista) fin si
si no
si cabezaLista↑.siguiente ≠ NIL entonces
si cabezaLista↑.siguiente↑.numero = dato entonces cursor ß cabezaLista↑.siguiente
cabezaLista↑.siguiente ß cursor↑.siguiente destruir (cursor)
si no
llamar eliminarElemento (cabezaLista↑.siguiente,dato) fin si
fin si fin si fin si fin acción
Vaciado de una lista simplemente enlazada (de forma recursiva)
acción vaciarLista (cabezaLista ∈ puntero a nodo) inicio
si cabezaLista↑.siguiente ≠ NIL entonces llamar vaciarLista (cabezaLista↑.siguiente) fin si
destruir(cabezaLista) fin acción
Para vaciar una lista simplemente enlazada de forma recursiva basta con vaciar el resto de la lista, si es que existe, y después eliminar el nodo que se encuentra en la cabeza.
A continuación se muestra de forma gráfica el vaciado recursivo de la lista que contiene los números 1, 2 y 3.
2
3
lista 1
NIL
inicio
si cabezaLista↑↑.siguiente ≠≠ NIL entonces llamar vaciarLista (cabezaLista↑↑.siguiente)
fin si
destruir(cabezaLista) fin acción
En la primera invocación se debe vaciar el resto de la lista (2 y 3).
3
lista 2
NIL
inicio
si cabezaLista↑↑.siguiente ≠≠ NIL entonces llamar vaciarLista (cabezaLista↑↑.siguiente)
fin si
destruir(cabezaLista) fin acción
En la segunda invocación se debe vaciar el resto de la lista (3).
lista 3 NIL
inicio
si cabezaLista↑.siguiente ≠ NIL entonces llamar vaciarLista (cabezaLista↑.siguiente) fin si
destruir(cabezaLista)
fin acción
En la tercera invocación no hay resto de lista con lo cual se destruye la cabeza (3).
lista
inicio
si cabezaLista↑.siguiente ≠ NIL entonces llamar vaciarLista (cabezaLista↑.siguiente) fin si
destruir(cabezaLista)
fin acción
Una vez destruida la cabeza de la lista en la tercera invocación se retorna a la segunda.
lista 2
inicio
si cabezaLista↑.siguiente ≠ NIL entonces llamar vaciarLista (cabezaLista↑.siguiente) fin si
destruir(cabezaLista)
fin acción
En la segunda invocación ya se ha vaciado el resto de la lista, por lo que se puede destruir la cabeza (2).
lista
inicio
si cabezaLista↑.siguiente ≠ NIL entonces llamar vaciarLista (cabezaLista↑.siguiente) fin si
destruir(cabezaLista)
fin acción
Destruida la cabeza de la lista de la segunda invocación se puede retornar a la primera.
lista 1
inicio
si cabezaLista↑.siguiente ≠ NIL entonces llamar vaciarLista (cabezaLista↑.siguiente) fin si
destruir(cabezaLista)
fin acción
Al haberse vaciado el resto de la lista en la primera invocación se elimina la cabeza.
inicio
si cabezaLista↑.siguiente ≠ NIL entonces llamar vaciarLista (cabezaLista↑.siguiente) fin si