Lección 11 – Estructuras dinámicas lineales (ii) 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) lista inicio si cabezaLista ≠ NIL entonces escribir cabezaLista↑ ↑ .numero llamar recorrer (cabezaLista↑.siguiente) fin si fin acción 1 2 3 lista 2 3 lista lista NIL 3 NIL NIL NIL 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. 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. 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. 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. 1 Elipsis: figura de construcción que consiste en omitir en la oración palabras que no son indispensables para la claridad del sentido. 1 Lección 11 – Estructuras dinámicas lineales (ii) 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) lista 1 2 lista 2 NIL 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). 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. 2 Lección 11 – Estructuras dinámicas lineales (ii) 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 NIL En la tercera invocación la cola está vacía, por tanto, es posible crear un nuevo elemento. 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 lista lista lista lista NIL 3 2 3 lista NIL NIL El nuevo elemento es el primero y el último de su cola. 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. 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. 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 1 2 3 El nuevo elemento es creado. 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 NIL La primera invocación ha logrado insertar, recursivamente, el nuevo elemento por lo que puede devolver el control al programa principal. 3 Lección 11 – Estructuras dinámicas lineales (ii) Inserción en una lista ordenada ascendentemente (de forma recursiva) 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) Para vaciar una lista simplemente enlazada de inicio forma recursiva basta con vaciar el resto de la lista, si es si cabezaLista↑.siguiente ≠ NIL entonces que existe, y después eliminar el nodo que se encuentra llamar vaciarLista (cabezaLista↑.siguiente) en la cabeza. fin si destruir(cabezaLista) fin acción A continuación se muestra de forma gráfica el vaciado recursivo de la lista que contiene los números 1, 2 y 3. llamar vaciarLista (lista) 4 Lección 11 – Estructuras dinámicas lineales (ii) lista inicio si cabezaLista↑ ↑ .siguiente ≠ NIL entonces llamar vaciarLista (cabezaLista↑ ↑ .siguiente) fin si destruir(cabezaLista) fin acción 1 2 En la primera invocación se debe vaciar el resto de la lista (2 y 3). 3 lista 3 2 lista NIL En la segunda invocación se debe vaciar el resto de la lista (3). 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. 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). inicio si cabezaLista↑.siguiente ≠ NIL entonces llamar vaciarLista (cabezaLista↑.siguiente) fin si destruir(cabezaLista) fin acción lista lista NIL En la tercera invocación no hay resto de lista con lo cual se destruye la cabeza (3). inicio si cabezaLista↑.siguiente ≠ NIL entonces llamar vaciarLista (cabezaLista↑.siguiente) fin si destruir(cabezaLista) fin acción lista lista inicio si cabezaLista↑ ↑ .siguiente ≠ NIL entonces llamar vaciarLista (cabezaLista↑ ↑ .siguiente) fin si destruir(cabezaLista) fin acción 2 3 lista NIL 1 Destruida la cabeza de la lista de la segunda invocación se puede retornar a la primera. 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 destruir(cabezaLista) fin acción La primera invocación ha destruido todos los elementos de la cola a partir del segundo y, después, la cabeza. Una vez hecho esto retorna al programa principal. 5