Download - LISTAS ENLAZADAS

Transcript

LISTAS ENLAZADAS* * * * * * * * * Definicin Operaciones bsicas sobre listas Listas ordenadas Listas reorganizables Cabecera ficticia y centinela Listas doblemente enlazadas Listas circulares Algoritmos de ordenacin de listas Conclusin y problemas

DefinicinUna lista es una estructura de datos secuencial. Una manera de clasificarlas es por la forma de acceder al siguiente elemento: - lista densa: la propia estructura determina cul es el siguiente elemento de la lista. Ejemplo: un array. - lista enlazada: la posicin del siguiente elemento de la estructura la determina el elemento actual. Es necesario almacenar al menos la posicin de memoria del primer elemento. Adems es dinmica, es decir, su tamao cambia durante la ejecucin del programa. Una lista enlazada se puede definir recursivamente de la siguiente manera: - una lista enlazada es una estructura vaca o - un elemento de informacin y un enlace hacia una lista (un nodo). Grficamente se suele representar as:

Como se ha dicho anteriormente, pueden cambiar de tamao, pero su ventaja fundamental es que son flexibles a la hora de reorganizar sus elementos; a cambio se ha de pagar una mayor lentitud a la hora de acceder a cualquier elemento. En la lista de la figura anterior se puede observar que hay dos elementos de informacin, x e y. Supongamos que queremos aadir un nuevo nodo, con la informacin p, al comienzo de la lista. Para hacerlo basta con crear ese nodo, introducir la informacin p, y hacer un enlace hacia el siguiente nodo, que en este caso contiene la informacin x. Qu ocurre si quisiramos hacer lo mismo sobre un array?. En ese caso sera necesario desplazar todos los elementos de informacin "hacia la derecha", para poder introducir el nuevo elemento, una operacin muy engorrosa.

Implementacin Para representar en lenguaje Pascal esta estructura de datos se utilizarn punteros, un tipo de datos que suministra el lenguaje. Se representar una lista vaca con la constante NIL. Se puede definir la lista enlazada de la siguiente manera: Lista = ^Rec; Rec = Record Clave : Integer; Sig : Lista; End; Como se puede observar, en este caso el elemento de informacin es simplemente un nmero entero. Adems se trata de una definicin autorreferencial. Pueden hacerse definiciones ms complejas. Ejemplo: cl = Record nombre : string[20]; edad : Integer; End; Lista = ^Rec; Rec = Record datos : cl; Clave : Integer; Sig : Lista; End; Cuando se crea una lista debe estar vaca. Por tanto para crearla se hace lo siguiente: Var L : Lista L := NIL;

Operaciones bsicas sobre listas- Insercin al comienzo de una lista: Es necesario utilizar una variable auxiliar, que se utiliza para crear el nuevo nodo mediante la reserva de memoria y asignacin de la clave. Posteriormente es necesario reorganizar los enlaces, es decir, el nuevo nodo debe apuntar al que era el primer elemento de la lista y a su vez debe pasar a ser el primer elemento. En el siguiente ejemplo se muestra un programa que crea una lista con cuatro nmeros. Notar que al introducir al comienzo de la lista, los elementos quedan ordenados en sentido inverso al de su llegada. Notar tambin que se ha utilizado un puntero auxiliar p para mantener correctamente los enlaces dentro de la lista. Var L, p : Lista; i : Integer; begin L := NIL; { Crea una lista vacia } for i := 4 downto 1 do begin { Reserva memoria para un nodo }

new(p); p^.clave := i; { Introduce la informacion } p^.sig := L; { reorganiza } L := p; { los enlaces } end - Recorrido de una lista. La idea es ir avanzando desde el primer elemento hasta encontrar la lista vaca. Antes de acceder a la estructura lista es fundamental saber si esa estructura existe, es decir, que no est vaca. En el caso de estarlo o de no estar inicializada es posible que el programa falle y sea difcil detectar donde, y en algunos casos puede abortarse inmediatamente la ejecucin del programa, lo cual suele ser de gran ayuda para la depuracin. Como se ha dicho antes, la lista enlazada es una estructura recursiva, y una posibilidad para su recorrido es hacerlo de forma recursiva. A continuacin se expone el cdigo de un programa que muestra el valor de la clave y almacena la suma de todos los valores en una variable pasada por referencia (un puntero a entero). Por el hecho de ser un proceso recursivo se utiliza un procedimiento para hacer el recorrido. Ntese como antes de hacer una operacin sobre el elemento se comprueba si existe. Procedure recorrer(L : Lista; var suma : Integer); begin if L NIL then begin write(L^.clave, ' '); suma := suma + L^.clave; recorrer(L^.sig, suma) end end; Var L : Lista; Suma : Integer; Begin { Crear la lista } ... Suma := 0; Recorrer(L, Suma); End.

Sin embargo, a la hora de hacer un programa, es ms eficaz si el recorrido se hace de forma iterativa. En este caso se necesita una variable auxiliar que se desplace sobre la lista para no perder la referencia al primer elemento. Se expone un programa que hace la misma operacin que el anterior, pero sin recursin. Var L : Lista; Suma : Integer; Begin { Crear la lista L } ... suma := 0; p := L; while p NIL do begin write(p^.Clave, ' '); suma := suma + p^.Clave;

p := p^.Sig end end. Y si queremos insertar en una posicin arbitraria de la lista o queremos borrar un elemento? Como se trata de operaciones algo ms complicadas (tampoco mucho) se expone su desarrollo y sus variantes en los siguientes tipos de listas: las listas ordenadas y las listas reorganizables. Asimismo se estudiarn despus las listas que incorporan cabecera y centinela. Tambin se estudiarn las listas con doble enlace. Todas las implementaciones se harn de forma iterativa, y se deja propuesta por ser ms sencilla su implementacin recursiva, aunque es recomendable utilizar la versin iterativa.

Listas ordenadasLas listas ordenadas son aquellas en las que la posicin de cada elemento depende de su contenido. Por ejemplo, podemos tener una lista enlazada que contenga el nombre y apellidos de un alumno y queremos que los elementos -los alumnosestn en la lista en orden alfabtico. La creacin de una lista ordenada es igual que antes: var L : Lista; L := NIL; Cuando haya que insertar un nuevo elemento en la lista ordenada hay que hacerlo en el lugar que le corresponda, y esto depende del orden y de la clave escogidos. Este proceso se realiza en tres pasos: 1.- Localizar el lugar correspondiente al elemento a insertar. Se utilizan dos punteros: anterior y actual, que garanticen la correcta posicin de cada enlace. 2.- Reservar memoria para l (puede hacerse como primer paso). Se usa un puntero auxiliar (nuevo) para reservar memoria. 3.- Enlazarlo. Esta es la parte ms complicada, porque hay que considerar la diferencia de insertar al principio, no importa si la lista est vaca, o insertar en otra posicin. Se utilizan los tres punteros antes definidos para actualizar los enlaces. A continuacin se expone un programa que realiza la insercin de un elemento en una lista ordenada. Suponemos claves de tipo entero ordenadas ascendentemente. Procedure insertar(Var L : lista; elem : Integer); var actual, anterior, nuevo : Lista; begin { 1.- se busca su posicion } actual := L; anterior := L; while (actual NIL) And (actual^.clave < elem) do begin anterior := actual; actual := actual^.sig end; { 2.- se crea el nodo } new(nuevo); nuevo^.clave := elem;

{ 3.- Se enlaza } { inserta al principio } if (anterior = NIL) Or (anterior = actual) then begin nuevo^.sig := anterior; L := nuevo { importante: al insertar al principio actuliza la cabecera } end { inserta entre medias o al final } else begin nuevo^.sig := actual; anterior^.sig := nuevo; end end; { Programa de prueba } Var L : Lista; begin L := NIL; { Crea una lista vacia insertar(L, 0); insertar(L, 1); insertar(L, -1) end.

}

Se puede apreciar que se pasa la lista L con el parmetro Var L . La razn para hacer esto es que cuando se inserta al comienzo de la lista (porque est vaca o es donde corresponde) se cambia la cabecera. Un ejemplo de prueba: suponer que se tiene esta lista enlazada: 1 -> 3 -> 5 -> NIL Queremos insertar un 4. Al hacer la bsqueda el puntero actual apunta al 5. El puntero anterior apunta al 3. Y nuevo contiene el valor 4. Como no se inserta al principio se hace que el enlace siguiente a nuevo sea actual, es decir, el 5, y el enlace siguiente a anterior ser nuevo, es decir, el 4. La mejor manera de entender el funcionamiento es haciendo una serie de seguimientos a mano o con la ayuda del depurador. A continuacin se explica el borrado de un elemento. El procedimiento consiste en localizarlo y borrarlo si existe. Aqu tambin se distingue el caso de borrar al principio o borrar en cualquier otra posicin. Se puede observar que el algoritmo no tiene ningn problema si el elemento no existe o la lista est vaca. Procedure borrar(Var L : lista; elem : Integer); Var actual, anterior : Lista; Begin { 1.- busca su posicion. Es casi igual que en la insercion, ojo al ( 2 -> 1 -> 6 -> 9 -> 0 -> 7 -> 4 -> 3 -> 8 se fusiona el primer elemento con el segundo, el tercero con el cuarto, etctera: [(3) -> (2)] -> [(1) -> (6)] -> [(9) -> (0)] -> [(7) -> (4)] -> [(3) -> (8)] queda: 2 -> 3 -> 1 -> 6 -> 0 -> 9 -> 4 -> 7 -> 3 -> 8 se fusionan los dos primeros (primera sublista) con los dos siguientes (segunda sublista), la tercera y cuarta sublista, etctera. Observar que la quinta sublista se fusiona con una lista vaca, lo cual no supone ningn inconveniente para el algoritmo de fusin. [(2 -> 3) -> (1 -> 6)] -> [(0 -> 9) -> (4 -> 7)] -> [(3 -> 8)] queda: 1 -> 2 -> 3 -> 6 -> 0 -> 4 -> 7 -> 9 -> 3 -> 8 se fusionan los cuatro primeros con los cuatro siguientes, y aparte quedan los dos ltimos:

[(1 -> 2 -> 3 -> 6) -> (0 -> 4 -> 7 -> 9)] -> [(3 -> 8)] queda: 0 -> 1 -> 2 -> 3 -> 4 -> 6 -> 7 -> 9 -> 3 -> 8 se fusionan los ocho primeros con los dos ltimos, y el resultado final es una lista totalmente ordenada: 0 -> 1 -> 2 -> 3 -> 3 -> 4 -> 6 -> 7 -> 8 -> 9

Para una lista de N elementos, ordena en el mejor y en el peor caso en un tiempo proporcional a: NlogN. Observar que para ordenar una lista de 2 elementos requiere un paso de ordenacin, una lista de 4 elementos requiere dos pasos de ordenacin, una lista de 8 elementos requiere tres pasos de ordenacin, una lista de 16 requiere cuatro pasos, etctera. Es decir: log 2 = 1 log 4 = 2 log 8 = 3 log 16 = 4 log 32 = 5 De ah el logaritmo en base 2. N aparece porque en cada paso se requiere recorrer toda la lista, luego el tiempo es proporcional a NlogN. Se pide: codificar el algoritmo de ordenacin por fusin ascendente.

Lista Doblemente EnlazadaLas listas de enlace simple restringen el movimiento por lo nodos a una sla direccin: no puede atravesar una lista de enlace simple en direccin opuesta a menos que primero utilice el algoritmo de inversin para invertir los enlaces de los nodos, lo que lleva tiempo. Despus de atraversarlos en direccin opuesta, problamente necesitar repetir la inversin para restaurar el orden original, lo que lleva an ms tiempo. Un segundo problema implica el borrado de nodos: no puede borrar un nodo arbitrario sin acceder al predecesor del nodo. Estos problemas desaperecen cuando se utiliza una lista doblemente enlazada. Una lista doblemente enlazada es una lista enlazada de nodos, donde cada nodo tiene un par de campos de enlace. Un campo de enlace permite atravesar la lista hacia adelante, mientras que el otro permite atravesar la lista haca atrs. Para la direccin hacia adelante, una variable de referencia contiene una referencia al primer nodo. Cada nodo se enlaza con el siguiente mediante el campo de enlace next, excepto el ltimo nodo, cuyo campo de enlace nextcontiene null para indicar el final de la lista (en direccion hacia adelante). De forma similar, para la direccin contraria, una variable de referencia contiene una referencia al ltimo nodo de la direccin normal (hacia adelante), lo que se interpreta como el primer nodo. Cada nodo se enlaza con el anterior mediante el campo de enlace previous, y el primer nodo de la direccion hacia adelante, contiene null en su campo previous para indicar el fin de la lista. La siguiente figura representa una lista doblemente enlazada de tres nodos, donde topForward referencia el primer nodo en la direccion hacia adelante, y topBackward referencia el primero nodo la direccin inversa.

Truco: Piense en una lista doblemente enlazada como una pareja de listas de enlace simple que interconectan los mismos nodos. La insercin y borrado de nodos en una lista doblemente enlazada son operaciones comunes. Estas operaciones se realizan mediante algoritmos que se basan en los algoritmos de insercin y borrado de las listas de enlace simple (porque las listas doblemente enlazadas slo son una pareja de listas de enlace simple que interconectan los mismos nodos). El siguiente listado muestra la insercin de nodos para crear la lista de la figura anterior, el borrado de nodos ya que elimina el nodo B de la lista, y el movimiento por la lista en ambas direcciones: // DLLDemo.java class DLLDemo { static class Node {

String name; Node next; Node prev; } public static void main (String [] args) { // Build a doubly linked list Node topForward = new Node (); topForward.name = "A"; Node temp = new Node (); temp.name = "B"; Node topBackward = new Node (); topBackward.name = "C"; topForward.next = temp; temp.next = topBackward; topBackward.next = null; topBackward.prev = temp; temp.prev = topForward; topForward.prev = null; // Dump forward singly linked list System.out.print ("Forward singly-linked list: "); temp = topForward; while (temp != null){ System.out.print (temp.name); temp = temp.next; } System.out.println (); // Dump backward singly linked list System.out.print ("Backward singly-linked list: "); temp = topBackward; while (temp != null){ System.out.print (temp.name); temp = temp.prev; } System.out.println (); // Reference node B temp = topForward.next; // Delete node B temp.prev.next = temp.next; temp.next.prev = temp.prev; // Dump forward singly linked list System.out.print ("Forward singly-linked list (after deletion): "); temp = topForward; while (temp != null){ System.out.print (temp.name); temp = temp.next; }

System.out.println (); // Dump backward singly linked list System.out.print ("Backward singly-linked list (after deletion): "); temp = topBackward; while (temp != null){ System.out.print (temp.name); temp = temp.prev; } System.out.println (); } } Cuando se ejecuta, DLLDemo produce la siguiente salida: Forward singly-linked list: ABC Backward singly-linked list: CBA Forward singly-linked list (after deletion): AC Backward singly-linked list (after deletion): CA Algoritmo de Insercin-Ordenada Algunas veces querr crear una lista doblemente enlazada que organice el orden de sus nodos basndose en un campo no de enlace. Atravesar la lista doblemente enlazada en una direccin presenta esos nodos en orden ascendente, y atravsarla en en direccin contraria los presenta ordenados descedentemente. El algoritmo de ordenacin de burbuja es inapropiado en este caso porque requiere ndices de array. Por el contrario, insercin-ordenada construye una lista de enlace simple o una lista doblemente enlzada ordenadas por un campo no de enlace para identificar el punto de insercin de cada nuevo nodo. El siguiente litado demuestra el algoritmo de insercin-ordenada: // InsSortDemo.java class InsSortDemo { // Note: To keep Employee simple, I've omitted various constructor and // nonconstructor methods. In practice, such methods would be present. static class Employee { int empno; String name; Employee next; Employee prev; } public static void main (String [] args) { // Data for a doubly linked list of Employee objects. The lengths of // the empnos and names arrays must agree. int [] empnos = { 687, 325, 567, 100, 987, 654, 234 }; String [] names = { "April", "Joan", "Jack", "George", "Brian", "Sam", "Alice" }; Employee topForward = null; Employee topBackward = null; // Prime the doubly linked list by creating the first node. topForward = new Employee (); topForward.empno = empnos [0]; topForward.name = names [0]; topForward.next = null; topForward.prev = null; topBackward = topForward; // Insert remaining Employee nodes (in ascending order -- via empno) // into the doubly linked list. for (int i = 1; i < empnos.length; i++) {

// Create and initialize a new Employee node. Employee e = new Employee (); e.empno = empnos [i]; e.name = names [i]; e.next = null; e.prev = null; // Locate the first Employee node whose empno is greater than // the empno of the Employee node to be inserted. Employee temp = topForward; while (temp != null && temp.empno