TEMA 3. Listas. CONSIDERACIONES GENERALES. A la hora de abordar la resolución de un ejercicio sobre listas se recomienda tener en cuenta las siguientes consideraciones generales: Identificar si se trata de un caso de solo consulta, es decir, el algoritmo a desarrollar no implica modificar la información inicialmente contenida en la lista o modificación (inserción, borrado). Tener en cuenta si el tratamiento permite finalización anticipada. Es decir, si es necesario recorrer toda la estructura (la finalización se produce cuando lista == null) o acaba anticipadamente con el cumplimiento de una condición (por ejemplo encontrar el valor de una clave), en este caso no se deberán realizar nuevas llamadas recursivas o, en el caso de tratamiento iterativo, deberá salirse del bucle. En particular en el caso de listas ordenadas no se considerará correcto el acceder a una clave mediante exploración exhaustiva la lista, sin parar al llegar a la posición donde debería encontrarse el elemento. Plantearse la técnica (iterativa o recursiva) más adecuada a utilizar (salvo indicación expresa en el enunciado). La técnica recursiva se considera recomendable para los ejercicios de listas enlazadas básicas, pero no siempre es así en otro tipo de implementaciones, por lo que se tendrá que analizar cada situación concreta. ListasMaximaDistancia. Enunciado. Dada la siguiente declaración de lista enlazada: public class NodoLista { int dato; NodoLista sig; public NodoLista (int x, NodoLista n) { clave=x; sig=n; } } public class Lista { NodoLista inicio; Lista () { inicio = null; } } SE PIDE: Desarrollar un algoritmo recursivo en Java tal que dada una lista perteneciente al tipo anterior y una clave entera, x, determine la máxima distancia existente entre dos elementos de la lista que contengan la clave x. OBSERVACIONES: No se permitirá la utilización de ninguna estructura de datos auxiliar. Solo se permitirá la realización de un único recorrido en la lista. Se supone que la clave x siempre va a existir en la lista. En caso de que solo se encontrase un único elemento que contiene la clave x, se deberá devolver como distancia máxima el valor cero. EJEMPLO: Dada la lista de la figura 1 y una clave x = 4, la máxima distancia entre dos elementos que contienen la clave 4, es 3. 9 4 6 8 D=3 4 5 D=2 4 4 D=1 Orientación. Se trata de desarrollar un método recursivo que devuelva un valor entero correspondiente a la máxima distancia, lo que será el resultado del método. El proceso tiene lugar durante la fase de “ida” y no existe posibilidad de terminación anticipada. Consiste en actualizar maximaDistancia como consecuencia de cada par de apariciones sucesivas de n. Para ello se utiliza un argumento entero (distancia), con valor inicial 0. Dicho argumento toma el valor 1 cuando aparece el primer n y se incementa con cada clave sucesiva distinta de n. Cuando vuelva a aparecer otro valor n (siendo distancia != 0) se procede a actualizar, en su caso, maximaDistancia y se vuelve a inicializar n (a 0). No es necesario verificar la condición excepcional de recibir la lista vacía, dado que el enunciado dice explícitamente que “Se supone que la clave x siempre va a existir en la lista”. Código. static int buscarMaximaDistancia (NodoLista nodoLista, int n, int distancia, int maximaDistancia) { int resul; if (nodoLista != null) { if (distancia == 0) { if (nodoLista.clave == n) distancia=1; } else if (nodoLista.clave != n) distancia++; else { if (distancia > maximaDistancia) maximaDistancia=distancia; distancia=1; } resul = buscarMaximaDistancia(nodoLista.sig,n,distancia,maximaDistancia); } else resul = maximaDistancia; return resul; } static int buscarDistancia (Lista lista, int n) { return buscarMaximaDistancia (lista.inicio, n, 0, 0); } ListasComprobarDatoUltimoNodo. Enunciado. Dada la siguiente declaración de lista enlazada: public class NodoLista { int dato; NodoLista sig; public NodoLista (int x, NodoLista n) { clave=x; sig=n; } } public class Lista { NodoLista inicio; Lista () { inicio = null; } } SE PIDE: Codificar un método en Java que, recibiendo como argumento una lista del tipo anterior, devuelva como resultado un valor de entre el siguiente subconjunto de números enteros: o 0, si el valor del último nodo coincide con el número de nodos de la lista. o o -1, si el valor del último nodo es menor que el número de nodos de la lista. +1, si el valor del último nodo es mayor que el número de nodos de la lista. OBSERVACIONES: Si la lista se recibe vacía el método devolverá el valor 0. No se permite utilizar ninguna estructura de datos auxiliar. Sólo se permite la realización de un único recorrido en la lista. Al final de la ejecución del método, la lista deberá permanecer con la estructura y el contenido iniciales. EJEMPLOS: El valor del último nodo de lista1 (5) coincide con número de nodos de la lista. El método devuelve 0. El valor del último nodo de lista2 (1) es menor que el número de nodos de la lista. El método devuelve -1. El valor del último nodo de lista3 (5) es mayor que el número de nodos de la lista. El método devuelve +1. lista1 1 2 4 5 5 1 lista2 1 3 1 lista3 7 5 3 null 5 null null Orientación. El ejercicio puede resolverse utilizando indistintamente las técnicas iterativa o recursiva dado que no es necesario hacer nada a la vuelta. La terminación esperada es “casi” pesimista habida cuenta de que el proceso termina cuando se recibe el último nodo (diferente de null). La condición de parada pesimista, en este caso, solo contempla la situación excepcional de recibir la lista inicialmente vacía, en cuyo caso el valor devuelto por el método será 0. El tratamiento (fase de ida) consiste, simplemente, en contar los nodos, para lo que hará falta un argumento entero (num). En la fase de transición (terminación anticipada) se obtiene el resultado que devolverá en la fase de vuelta. Código. static int comprobar (NodoLista nodoLista, int num) { int resul; if (nodoLista != null) { num++; if (nodoLista.sig != null) resul = comprobar (nodoLista.sig, num); else if (num == nodoLista.clave) resul = 0; else if (nodoLista.clave < num) resul = -1; else resul = 1; } else resul = 0; return resul; } static int comprobarUltimoNodo (Lista lista) { return comprobar (lista.inicio, 0); } ListasInsertarCeroDespuesDeSuma. Enunciado. Dada la siguiente declaración de lista enlazada: public class NodoLista { int dato; NodoLista sig; public NodoLista (int x, NodoLista n) { clave=x; sig=n; } } public class Lista { NodoLista inicio; Lista () { inicio = null; } } SE PIDE: Codificar un método en Java que, recibiendo como parámetro una lista perteneciente al tipo anterior, inserte en dicha lista un nuevo elemento con clave igual a cero, después de todo aquel elemento de la lista cuya clave coincida con la suma de todas las claves contenidas en la lista. Observaciones: No se permitirá la utilización de ninguna estructura de datos auxiliar. Sólo se permitirá la realización de un único recorrido en la lista. Se supone que la suma de todas las claves contenidas en la lista nunca va a ser cero. EJEMPLO: En la lista mostrada en la parte superior de la figura, como puede apreciarse, la suma de todas sus claves es 1 + 2 + 3 + 2 + (-6) = 2. Por tanto el método deberá devolver la mencionada lista en la situación mostrada en la parte inferior de la figura. 1 1 2 2 0 3 3 2 2 -6 0 6 Orientación. El método, recursivo, realiza el resultado pedido en dos fases. A lo largo de la fase de “ida”, con condición de terminación nodoLista != null y sin posibilidad de terminación anticipada, se suman las claves de la lista y se recoge el resultado en un argumento entero (suma), que devolveremos como resultado del método al llegar al final de la lista. El tratamiento de “vuelta” consiste, simplemente, en verificar si el resultado del método tiene un valor igual al de la clave actual, en cuyo caso se inserta un 0. Código. static int insertarCero (NodoLista lista, int suma) { int resul; if (lista != null) { resul = insertarCero (lista.sig, suma+lista.clave); if (lista.clave == resul) lista.sig = new NodoLista (0, lista.sig); } else resul = suma; return resul; } static void insertarCeroDespuesDeSuma (Lista lista) { insertarCero (lista.inicio, 0); } ListasBorrarPosicionesParesOImpares. Enunciado. Dada la siguiente declaración de lista enlazada: public class NodoLista { int dato; NodoLista sig; public NodoLista (int x, NodoLista n) { clave=x; sig=n; } } public class Lista { NodoLista inicio; Lista () { inicio = null; } } Codificar un método en Java, que recibiendo una lista perteneciente al tipo anterior, borre de la misma todos aquellos elementos que ocupen posiciones pares (caso de que la lista tenga un número par de elementos), o que borre todos aquellos elementos que ocupen posiciones impares (caso de que la lista tenga un número impar de elementos). Observaciones: - No se permite la utilización de ninguna estructura de datos auxiliar. - Sólo se podrá realizar un único recorrido en la lista. Orientación. El algoritmo (recursivo) requiere llegar hasta el final de lista sin hacer nada y en la “vuelta” proceder alternativamente a eliminar o no el nodo actual. No es necesario realizar ninguna disquisición sobre la paridad del número de elementos de la lista. El último nodo siempre se elimina. Ejemplos: Número par de elementos (4): 10, 20, 30, 40. El resultado será: 10, 30 Número impar de elementos (5): 10, 20 30, 40, 50 El resultado será 20, 40 Lo que sí es necesario es disponer de una información booleana que actúe en la fase de vuelta como conmutador para conocer si hay que eliminar o no. Dado que en Java no es posible pasar argumentos por referencia se ha optado por diseñar el propio método de forma que, además de realizar la tarea solicitada (eliminar unos nodos sí y otros no), devuelva dicha información boolena. Se inicializa (a true) en la transición (lista.inicio = null). Se utilizan dos métodos: borrarPosicionesParesOImpares recibe la lista, y llama al método recursivo (borrarParesOImpares) pasando como argumento lista.inicio (de tipo NodoLista). Dado que las referencias se pasan por valor, cuando sea necesario borrar un nodo lo borraremos desde el anterior (cuando el resultado de la anterior llamada recursiva haya sido false). En caso de que sea necesario borrar el primer nodo (cuando la lista tenga un número impar de elementos), lo haremos desde el método de llamada (haciendo lista.inicio = lista.inicio.sig). Código. static boolean borrarParesOImpares (NodoLista nodo) { boolean resul; NodoLista aux; if (nodo != null) { aux = nodo; nodo = nodo.sig; resul = borrarParesOImpares (nodo); if (!resul && (nodo != null)) aux.sig = nodo.sig; resul = !resul; } else resul = true; return resul; } static void borrarPosicionesParesOImpares (Lista lista){ boolean resul = borrarParesOImpares (lista.inicio); if (!resul) lista.inicio = lista.inicio.sig; } ListasBorrarPosicionesParesOImparesSuma. Enunciado. Dada la siguiente declaración de lista enlazada: public class NodoLista { int dato; NodoLista sig; } public class Lista { NodoLista inicio; } SE PIDE: Codificar un algoritmo recursivo en Java que, recibiendo como parámetro una lista perteneciente al tipo anterior, determine si la suma de las claves que se encuentran contenidas en los elementos que ocupan posiciones impares, es igual a la suma de las claves que se encuentran contenidas en los elementos que ocupan posiciones pares. En caso de que la suma de las claves que se encuentran contenidas en los elementos que ocupan posiciones impares, sea igual a la suma de las claves que se encuentran contenidas en los elementos que ocupan posiciones pares, el algoritmo deberá borrar de la lista todos los elementos que ocupan posiciones pares. En caso contrario, el algoritmo deberá borrar de la lista todos los elementos que ocupen posiciones impares. OBSERVACIONES: No se permitirá la utilización de ninguna estructura de datos auxiliar. Sólo se permitirá la realización de un único recorrido en la lista. Si la lista se encuentra vacía, se considerará que la suma de las claves contenidas en elementos que ocupan posiciones pares, es igual a la suma de las claves contenidas en elementos que ocupan posiciones impares. En este caso, al coincidir ambas sumas, teóricamente se debería proceder al borrado de aquellos elementos que ocupasen posiciones pares. Pero, como la lista se encuentra vacía, obviamente no se borrará ningún elemento. Si la lista contiene un único elemento, se considerará que la suma de las claves contenidas en elementos que ocupan posiciones pares es cero. Por su parte, la suma de las claves contenidas en elementos que ocupan posiciones impares, coincidirá con el valor de la única clave existente. No se dará por buena ninguna solución que no sea recursiva. EJEMPLOS: o En la figura a) la suma de las claves que se encuentran contenidas en los elementos que ocupan posiciones impares es 10 (5 + 3 + 2), y la suma de las claves que se encuentran contenidas en los elementos que ocupan posiciones pares es 5 4 3 6 2 también 10 (4+6). Por tanto, al coincidir ambas sumas, se deberán borrar los elementos que Figura a) ocupan posiciones pares, es decir, el 4 y el 6. o Análogamente, en la figura b), la suma de las claves que se encuentran contenidas en los elementos que ocupan posiciones impares es 10 (4 + 6), y la suma de las claves que se encuentran contenidas en los elementos 4 7 6 3 que ocupan posiciones pares es también 10 (7 + 3). Por tanto, al coincidir ambas sumas, se deberán borrar los Figura b) elementos que ocupan posiciones pares, esto es el 7 y el 3. o Por el contrario, en la lista mostrada en la figura 1c), la suma de las claves que se encuentran contenidas en los elementos que ocupan posiciones impares es 15 (5 + 3 + 7), mientras que la suma de las claves que se encuentran contenidas en los elementos que ocupan posiciones pares es 10 (4 + 5 4 3 6 7 6). Por tanto, al no concidir ambas sumas, se deberán borrar los elementos que ocupan posiciones Figura c) impares, es decir, el 5, el 3 y el 7. Orientación. La lógica del método es la siguiente: o En la fase de “ida” se contabiliza la suma neta de los elementos de la lista (se suman los que ocupan posición par y se resta los que ocupan posición impar). o En la fase de “vuelta” se aplican los criterios expresados en el enunciado. Es decir: o Si la suma es 0 (suma de elementos en posiciones pares igual a suma de elementos en posiciones impares) se eliminan los elementos que ocupan posiciones pares. o En caso contrario (suma != 0) se eliminan los elementos que ocupan posiciones impares. Se requieren los siguientes argumentos: o par, de tipo booleano, con valor inicial false. Conmuta en cada llamada durante la fase de “ida”. Se consulta su valor durante la fase de “vuelta”. o suma, de tipo entero e inicializado a 0, con la finalidad expresada anteriormente. La condición de finalización en el tratamiento recursivo es la completa exploración de la lista (lista == null) sin posibilidad de terminación anticipada. El método recursivo será booleano, devolviendo true si la suma es 0 (y por tanto hay que borrar los elementos pares), y false en caso contrario (cuando habrá que borrar los elementos impares). Dado que las referencias se pasan por valor, cuando sea necesario borrar un nodo lo borraremos desde el anterior (cuando el resultado haya sido false y estemos en una posición par, o bien el resultado sea true y estemos en una posición impar). En caso de que sea necesario borrar el primer nodo (cuando la lista tenga un número impar de elementos y el resultado del método recursivo haya sido false), lo haremos desde el método de llamada (haciendo lista.inicio = lista.inicio.sig). Código. static boolean borrarParesOImpares (NodoLista nodo, int suma, boolean par) { boolean resul; NodoLista aux; if (nodo != null) { aux = nodo; nodo = nodo.sig; if (par) resul = borrarParesOImpares (nodo, aux.clave + suma, !par); else resul = borrarParesOImpares (nodo, suma - aux.clave, !par) ; if (!resul && par && (nodo != null)) aux.sig = nodo.sig; else if (resul && !par && (nodo != null)) aux.sig = nodo.sig; } else resul = (suma == 0); return resul; } public static void borrarPosicionesParesOImpares (Lista lista){ boolean resul = borrarParesOImpares (lista.inicio, 0, false); if (!resul) lista.inicio = lista.inicio.sig; } ListasInvertirContenido. Enunciado. Dada la siguiente declaración de lista enlazada: public class NodoLista { int dato; NodoLista sig; } public class Lista { NodoLista inicio; } SE PIDE: Codificar un método en Java que, recibiendo como parámetro una lista perteneciente al tipo anterior, invierta su contenido. OBSERVACIONES: Solamente se permite la realización de un único recorrido de la lista No se permite la creación de nuevos nodos de la clase NodoLista. En caso de que la lista esté vacía o posea un solo elemento, la ejecución del método no deberá surtir ningún efecto Además de la lista, la cabecera del método podrá contener otros parámetros EJEMPLO: Dada la lista mostrada en la parte superior de la figura, el método deberá devolver dicha lista en la situación mostrada en la parte inferior. lista 1 3 5 4 2 lista 2 4 5 3 1 Orientación. El ejercicio básicamente, supone desarrollar un método recursivo sin terminación anticipada (seguiremos hasta que la lista esté vacía) que, durante la fase de “ida”, recorra la lista almacenado localmente la dirección del nodo desde el que se realiza la llamada (anterior). Dicha referencia (anterior) se utilizará, en la fase de “vuelta”, para sustituir los sucesivos campos lista.sig consiguiendo así que cado nodo pase a apuntar al anterior. El primer elemento, que como consecuencia de la ejecución del proceso pasará a ser el último, deberá tratarse de forma excepcional: Su campo sig deberá tomar el valor null. El valor inicial (y final ) de lista deberá ser el correspondiente al nodo que ocupa inicialmente la última posición. Para ello se requiere que el método recursivo devuelva al módulo principal la referencia del último nodo (el valor de anterior en la fase de “transición): será el resultado del método recursivo. Código. static NodoLista invertir (NodoLista lista, NodoLista anterior) { NodoLista resul; if (lista != null) { resul = invertir (lista.sig, lista); lista.sig=anterior; } else resul = anterior; return resul; } static void invertirLista (Lista lista) { NodoLista ultimo; if (lista != null) { ultimo = invertir (lista.inicio.sig, lista.inicio); lista.inicio.sig = null; lista.inicio = ultimo; } } ListasEstaContenida. Enunciado. Se dispone de dos listas calificadas ordenadas con la siguiente declaración: class NodoLista { int clave; NodoLista sig; } SE PIDE: Codificar un método booleano en Java que, recibiendo dos listas como parámetro, compruebe si cada uno de los elementos de la segunda lista está contenido en la primera. OBSERVACIONES: No se permitirá la utilización de estructuras de datos auxiliares. No se permitirá la realización de más de un recorrido en ninguna de las dos listas. Se prestará especial atención al número de elementos visitados en la solución. Lista 1 1 2 3 4 5 1 Lista 2 1 3 4 Lista 3 1 2 6 null 7 null Como se puede apreciar en el ejemplo, todos los elementos de la Lista2 están en la Lista1, mientras que no todos los elementos de la Lista3 están contenidos en la Lista1 (por ejemplo el 6). Orientación. La condición de finalización más restrictiva es haber procesado con éxito lista2. En tal caso el resultado será true. Se producirá finalización anticipada (no se realizarán más llamadas recursivas) proporcionando un resultado false cuando: o Finalice lista1. En tal caso aún quedan pendientes elementos de lista2 y, en consecuencia, no pueden estar todos sus elementos incluidos en lista1. o Se encuentre un elemento de lista2 no existente en lista1. Ya no es necesario seguir procesando pues no se cumplirá la condición de que todos los elementos de lista2 estén en lista1. El resto de situaciones que pueden producirse serán: o Se enfrentan dos claves iguales. Se realizará una llamada recursiva a partir de los siguientes elementos de ambas listas. o Se enfrenta una clave de lista1 inferior a la de lista2. Se realiza una llamada recursiva desde la siguiente posición de lista1 sin modificar la de lista2. null Código. static boolean estaContenida (NodoLista nodoLista1, NodoLista nodoLista2) { boolean resul; if (nodoLista2 != null) if (nodoLista1 == null) resul = false; else if (nodoLista1.clave > nodoLista2.clave) resul = false; else if (nodoLista1.clave == nodoLista2.clave) resul = estaContenida (nodoLista1.sig, nodoLista2.sig); else resul = estaContenida (nodoLista1.sig, nodoLista2); else resul = true; return resul; } ListasGenerarDosListasSiNodoComun Enunciado Dada la siguiente declaración de lista calificada de números enteros: public class NodoLista { int dato; NodoLista sig; NodoLista (int x) { dato = x; sig = null; } } public class Lista { NodoLista inicio; } SE PIDE: Implementar, en Java, un método recursivo tal que, dadas dos listas (lista1 y lista2) del tipo anterior, ordenadas ascendentemente y que pueden tener un nodo común, genere dos listas independientes. OBSERVACIONES: Si no existe ningún nodo común el método no deberá surtir ningún efecto. En cada una de las listas no puede haber claves repetidas. No se permite la utilización de ninguna estructura de datos auxiliar. Sólo se permite la realización de un único recorrido en cada lista. EJEMPLO: Situación de partida: lista1 1 5 3 5 7 12 16 25 * lista2 Situación final: lista1 1 5 7 12 16 3 5 12 16 25 25 * lista2 * Orientación Se trata de un ejercicio de enfrentamiento de listas en modalidad AND, es decir: el proceso continúa si hay elementos en ambas listas: ((nodoLista1 != null) && nodoLista2 != null)). Durante la fase de “ida” se recorren ambas listas teniendo en cuenta su ordenación ascendente. El cumplimento de la terminación “pesimista” significa que se ha alcanzado el final de alguna de las listas sin haber encontrado un nodo común. El método no surte efecto alguno. La terminación anticipada tiene lugar como consecuencia del encuentro de un nodo común (nodoLista1 == nodoLista2) que requiere como condición necesaria, pero no suficiente, que las dos claves sean iguales. La “transición” implica la llamada desde el punto actual a un método recursivo auxiliar (replicar). Los únicos argumentos necesarios son nodoLista1 y nodoLista2. El método auxiliar (replicar) recibe un argumento de tipo NodoLista (nodoListaO), y devuelve un resultado de tipo NodoLista. Se trata de devolver la lista apuntada por uno de los punteros (nodoListaO) y una réplica de la misma (el resultado del método). Código. static NodoLista replicar (NodoLista nodoListaO) { NodoLista aux = null; if (nodoListaO != null) { aux = replicar (nodoListaO.sig); aux = new NodoLista (nodoListaO.clave, aux); } return aux; } static NodoLista generarDosListasSiNodoComun (NodoLista nodoLista1, NodoLista nodoLista2) { NodoLista aux; if ((nodoLista1 != null) && (nodoLista2 != null)) if (nodoLista1.clave < nodoLista2.clave) aux = generarDosListasSiNodoComun (nodoLista1.sig,nodoLista2); else if (nodoLista1.clave > nodoLista2.clave) aux = generarDosListasSiNodoComun (nodoLista1,nodoLista2.sig); else if (nodoLista1 != nodoLista2) { aux = generarDosListasSiNodoComun (nodoLista1.sig, nodoLista2.sig); if (nodoLista2 != aux) { nodoLista2.sig = aux; aux = nodoLista2; } } else aux = replicar (nodoLista1); else aux = nodoLista2; return aux; } static void llamadaGenerar (Lista lista1, Lista lista2) { generarDosListasSiNodoComun (lista1.inicio, lista2.inicio); } ListasBusquedaReorganizableEnListaCabeceraCentinela. Enunciado. Dada la siguiente declaración de lista reorganizable con cabecera y centinela ficticios de números enteros: class NodoLista { int clave; NodoLista sig; } public class Lista { NodoLista cab, cent; } SE PIDE: Codificar, en Java un método booleano que, recibiendo como argumentos una lista del tipo anterior y un dato entero, devuelva el valor true si el dato se encuentra en la lista a la vez que mantiene la naturaleza de la lista reorganizable o false, en caso contrario y la lista no se modifica. EJEMPLO: dados la lista de la figura 1 y el dato 13 el método devolverá true y la lista quedará según se indica en la figura 2. lista cabecera 10 9 13 centinela 14 15 21 * 0 Figura 1. lista cabecera 13 9 centinela 10 14 15 0 Figura 2. OBSERVACIONES: La lista no contiene elementos repetidos. No se permite la utilización de ninguna estructura de datos auxiliar. Sólo se permite la realización de un único recorrido en la lista.. 21 * Orientación. Como siempre que trabajamos con listas con cabecera y centinela, se plantea una solución iterativa. El algoritmo contempla dos funcionalidades: Devolver el valor booleano correspondiente a la existencia (o no) de la clave solicitada. Para reubicar, en su caso, el nodo que contiene la clave buscada se utiliza la propia variable referencia (actual) empleada para recorrer la lista (lista.cab.sig = actual). También es necesario “arrastrar” una referencia al nodo anterior (ant) que permita enlazarlo con el siguiente al que se quiere reubicar (actual.sig = lista.cab.sig y anterior.sig = actual.sig)1. Código. static boolean busquedaReorganizable (Lista lista, int dato) { NodoLista anterior, actual; boolean resul = false; anterior = lista.cab; actual = anterior.sig; lista.cent.clave = dato; while (actual.clave != dato) { anterior = actual; actual = actual.sig; } if (actual != lista.cent){ resul = true; anterior.sig = actual.sig; actual.sig = lista.cab.sig; lista.cab.sig = actual; } return resul; } 1 Deberá prestar especial atención al orden en que ejecute dichas asignaciones. ListasPilasInsertarNSiListaEnPila. Enunciado. Dados el Tad Pila de Enteros con las operaciones static boolean pilaVacia (); static void apilar (int elem); static int desapilar (); y la siguiente declaración de lista enlazada: class NodoLista { int clave; NodoLista sig; } public class Lista { NodoLista inicio; String nombre; } SE PIDE: Codificar un método recursivo en Java que, recibiendo como parámetros una pila perteneciente al Tad Pila anterior, una lista enlazada perteneciente al tipo Lista y un número entero n, inserte en la pila el número n justo debajo de la secuencia de números que representa la lista, si es que dicha secuencia se encuentra contenida en la pila. OBSERVACIONES: No se permite la utilización de ninguna estructura de datos auxiliar. Sólo se permite la realización de un único recorrido tanto en la pila como en la lista. Se supone que ni la lista ni la pila se encuentran vacías. Se supone que ni en la lista ni en la pila existen elementos repetidos. EJEMPLOS: Dadas la pila, la lista y el número n mostrados en la figura a), la ejecución del método debera dejar la pila en la situación mostrada en la figura b. Sin embargo, dadas la pila, la lista y el número n mostrados en la figura c, dado que la secuencia de números que constituye la lista no se encuentra contenida dentro de la pila, la ejecución del método no deberá surtir ningún efecto. 1 1 5 1 3 2 4 n=6 3 2 5 1 3 2 6 4 5 1 3 4 2 n=6 3 2 Figura a Figura b Figura c Orientación. El método continuará siempre que la pila tenga elementos (!pila.pilaVacia()) y que no se haya alcanzado el final de la lista (&& lista != null). Pueden darse las siguientes circunstancias: o Se encuentran (desapilan) elementos de la pila distintos al primero de la lista. Se lanzará una nueva llamada recursiva desde el estado actual de la pila y el mismo punto de la lista. No olvidar, en la fase de vuelta, volver a apilar el elemento desapilado. o Se produce, en su caso, la primera coincidencia entre un elemento de la pila y de la lista. Esta situación se identifica mediante el argumento engan, (de tipo boolean inicializado a false en el módulo de llamada). En este momento su valor cambiará a true y se realiza una llamada recursiva pasando como argumento el nodo siguiente de la lista. (No olvidar apilar en la fase de vuelta). o A partir de esta situación se espera encontrar siempre coincidencias entre los elementos de la pila y de la lista y ejecutar nuevas llamadas recursivas apilando a la vuelta). Si es así se alcanzaría la condición general (compuesta) de terminación. o Si dicha condición se cumple como consecuencia de haber recorrido la lista completamente (lista == null) se entiende que se satisfacen las condiciones del enunciado y puede apilarse el argumento n. o En caso de vaciado de la pila (pila.pilaVacia()) sin haber recorrido la lista completamente (lista != null), se entiende no se cumplen las condiciones especificadas (la secuencia de la lista no está en la pila) y, por tanto, no procede la inserción de n. o En caso de que se interrumpa la secuencia de coincidencias entre los elementos de la pila y de la lista se produce terminación anticipada (no hay más llamadas recursivas) y el proceso finaliza después de apilar el último elemento desapilado de la pila. Por supuesto, no procede la inserción de n. Código. static void insertarN (Pila pila, NodoLista lista, int n, boolean engan) throws PilaVacia { int elem; if (!pila.pilaVacia() && (lista != null)) { elem = pila.desapilar (); if (elem != lista.clave) if (!engan) { insertarN (pila,lista, n,engan); pila.apilar (elem); } else pila.apilar (elem); else { if (!engan) engan = true; insertarN(pila,lista.sig, n,engan); pila.apilar (elem); } } else if (lista == null) pila.apilar (n); } static void insertarNSiListaEnPila (Pila pila, Lista lista, int n) throws PilaVacia { if (lista.inicio != null) insertarN (pila, lista.inicio, n, false); } Ejercicios de Estructuras de Datos I Listas 133 ListasInsertarUnoListaSobreMatriz. Enunciado. Considérese la siguiente declaración de Lista Enlazada Ordenada de Números Enteros Positivos: class NodoLista { int clave, sig; NodoLista () { clave = 0; sig = 0; } public class Lista { final int NULL = 0, N = 9; NodoLista [] matriz; int i; Lista () { matriz = new NodoLista [N]; for (i = 0; i < N-1; i++) { matriz [i] = new NodoLista (); matriz [i].sig = i + 1; } matriz [i] = new NodoLista (); } } } Nodo inicial (índice 0): El campo “clave” es un puntero explícito al primer nodo de la lista en tanto que el campo “sig” es otro puntero al primer hueco. Nodos con información: El campo clave es la clave y el campo sig es un puntero al siguiente nodo de la lista. El valor 0 (NULL) se debe interpretar como final de la lista. Nodos libres (“huecos”): El campo clave no tiene ningún significado. El campo sig es un puntero al siguiente hueco (el valor 0 se interpreta como final de la lista de huecos). 0 1 10 12 13 21 Figura 1 2 3 4 5 6 7 8 1 10 12 21 13 2 3 4 7 6 0 8 5 0 Figura 2 SE PIDE: Codificar un método en Java que, recibiendo como parámetro una lista del tipo anterior, inserte un 1 entre aquellos dos elementos consecutivos cuya diferencia sea la mínima existente. OBSERVACIONES: Solamente se permite la realización de un único recorrido, tanto en la lista de números como en la lista de huecos. No se permite la utilización de estructuras de datos auxiliares. Se supone que en la lista no existen números repetidos. Si existen varios pares de elementos consecutivos cuya diferencia mínima sea la misma, el 1 se insertará entre los dos más pequeños. Si la lista de números posee menos de dos elementos, la ejecución del método no surtirá ningún efecto. Si no existe ningún hueco libre en el vector, la ejecución del método no surtirá ningún efecto. EJEMPLO: Dada la lista mostrada en las figuras 1 y 2, dado que los números consecutivos de difencia mínima son el 12 y el 13, la ejecución del método deberá devolver la lista en la situación mostrada en las figuras 3 y 4. Al insertar un nuevo elemento en la lista de números, éste se ubicará en la posición del primer hueco de la lista de huecos, debiendo desconectarse dicho hueco de la mencionada lista de huecos. 10 12 1 Figura 3 13 21 0 1 2 3 4 5 6 7 8 1 10 1 12 21 13 4 3 7 2 6 0 8 5 0 Figura 4 Orientación. Básicamente la solución del problema consiste en aplicar un tratamiento recursivo que: En la fase de ida recorra la lista encontrando la menor diferencia (difMin). El tratamiento del primer elemento (no tiene anterior) debería ser diferente al del resto, salvo que se admita utilizar la constante MAX_VALUE. No existe posibilidad de terminación anticipada. En la fase de transición se “fija” el valor de alguna información necesaría para identificar (en la fase de vuelta) el lugar donde ha de procederse a la inserción del “1”. Podría ser la posición de un nodo o también la clave correspondiente al sustraendo que participa en la primera menor diferencia encontrada (no hay claves repetitdas). Se ha optado por esta última lo que obliga a utilizar (en la fase de ida) dicha información (menor) que no se puede pasar como argumento (por valor), dado que en la fase de vuelta se necesita su valor final. Se ha optado por declararlo como variable global de una clase interna Inserta1. En la fase de vuelta se consultan las claves correspondientes hasta encontrar la que coincide con el valor de menor. En ese momento se procede a la inserción del nodo con clave “1” (método insertar1). Dada la naturlaeza de la lista (implementada mediante una matriz), la creación de nuevo nodo implica codificar expresamente las funcionalidades de localizar el primer nodo disponible (si hay) y actualizar, en su caso, la lista de huecos2. Para ello se emplean los correspondientes algoritmos básicos explicados en teoría. 2 En el caso de utilizar estructuras de datos dinámicas estas funcionalidades se ejecutan de forma transparente para el programador cuando se hace uso del contructor (new). Ejercicios de Estructuras de Datos I Código. static class Inserta1 { static final int NULL = 0, N = 9; static int menor, difMin; static void insertar1 (NodoLista [ ] lista, int i) { int aux; aux = lista[0].sig; lista[0].sig = lista[aux].sig; lista[aux].clave = 1; lista[aux].sig = lista[i].sig; lista[i].sig = aux; } static void insertarUno (NodoLista [ ] lista, int i, int ant) { if (i != NULL) { if (lista[i].clave - ant < difMin) { difMin = lista[i].clave-ant; menor = ant; } ant = lista[i].clave; insertarUno (lista, lista[i].sig, ant); if (lista[i].clave == menor) insertar1 (lista,i); } } static void insertarUnoMenorDiferencia (Lista lista) { int ant, i; NodoLista [] auxLista = lista.matriz; if ((auxLista [0].sig != NULL) && (auxLista [0].clave != NULL)) { i = auxLista[0].clave; menor = Integer.MAX_VALUE; ant = auxLista[i].clave; difMin = menor; insertarUno (auxLista, auxLista[i].sig, ant); if (auxLista[i].clave== menor) insertar1(auxLista, i); } } } Listas 135 ListasInsertarUnoTADLista. Enunciado. Dado el TAD Lista de números enteros positivos con las siguientes operaciones: void crearNodo (); /*Crea un nuevo nodo al principio del tadLista*/ int devolverClave (); /*Devuelve la clave contenida en el nodo del tadLista*/ NodoLista devolverReferencia (); /*Devuelve una referencia al primer nodo del tadLista*/ NodoLista devolverSiguiente (); /*Devuelve una referencia al siguiente del tadLista*/ void asignarClave (int dato); /*Asigna el dato al primer nodo del tadLista*/ void asignarReferencia (NodoLista referencia); /*Hace que el tadLista apunte al mismo sitio que referencia*/ void asignarReferenciaSiguiente (NodoLista referenciaNueva); /*Hace que el siguiente del nodo actual apunte ahora al mismo sitio que referenciaNueva*/ void asignarNulo (); /*Hace que el tadLista tome el valor null*/ boolean esNulo (); /*Devuelve true si el tadLista tiene valor null; false en caso contrario*/ boolean esIgual (NodoLista referencia); /*Devuelve true si referencia apunta al mismo sitio que el tadLista, false en caso contrario*/ SE PIDE: Codificar un método en Java que, recibiendo como parámetro una lista ordenada ascendentemente perteneciente al TadLista anterior, inserte un 1 entre aquellos dos elementos consecutivos cuya diferencia sea la mínima existente. OBSERVACIONES: Solamente se permite la realización de un único recorrido en la lista. No se permite la utilización de estructuras de datos auxiliares. Se supone que en la lista no existen números repetidos. Si existen varios pares de elementos consecutivos cuya diferencia mínima sea la misma, el 1 se insertará entre aquellos dos que resulten ser los más pequeños. Si la lista de números posee menos de dos elementos, la ejecución del método no surtirá ningún efecto. EJEMPLO: Dada la lista mostrada en la figura 1, puesto que los números consecutivos de diferencia mínima son el 12 y el 13, la ejecución del método deberá devolver la lista en la situación mostrada en la figura 2. 10 12 13 Figura 1 21 10 12 1 Figura 2 13 21 Ejercicios de Estructuras de Datos I Listas 137 Orientación. Dado que se trata de un TAD no está permitido presuponer ningún tipo de implementación física. No obstante, por comodidad, se puede desarrollar una solución previa basada en algún tipo de implementación conocida (por ejemplo, mediante estructuras dinámicas) y adaptarla posteriormente a las especificaciones del TAD. Se realiza un tratamiento recursivo sin posibilidad de terminación anticipada3. En la fase de “ida” se identifica tanto el valor de la (primera) menor diferencia y alguno de sus componentes, por ejemplo la clave del sustraendo. En consecuencia se requiere el uso de dos argumentos (dif y menor), pasados por referencia pues deberá conocerse su valor final en la fase de “vuelta”. Además es necesario pasar el valor de la clave anterior (anterior). Dado que en la solución propuesta se utiliza en la fase de “vuelta” para identificar el punto de inserción (anterior=menor) se pasará por valor. Para poder implementar la lógica anterior a partir de las especificaciones del TAD se respetarán las equivalentes correspondencias. Adicionalmente, las llamadas con el argumento lista.sig deberán adaptarse en el sentido de utilizar una variable local (punt) a la que previamente se le asigna (mediante la operación devolverSiguiente) el valor adecuado. Código. static class Inserta1 { static int menor, minDif; static void insertar1 (Lista lista, int anterior) { TadLista aux = new TadLista (); if (!lista.esNulo()) { if ((lista.devolverClave() - anterior) < minDif) { minDif = lista.devolverClave() - anterior; menor = anterior; } aux.asignarReferencia(lista.devolverSiguiente ()); insertar1 (aux, lista.devolverClave()); lista.asignarReferenciaSiguiente (aux.devolverReferencia()); if (anterior == menor) { lista.crearNodo(); lista.asignarClave(1); } } } static void insertarUno (Lista lista) { TadLista punt = new TadLista (); if (!lista.esNulo()) { minDif = Integer.MAX_VALUE; menor = lista.devolverClave (); punt.asignarReferencia(lista.devolverSiguiente ()); insertar1(punt, lista.devolverClave()); lista.asignarReferenciaSiguiente (punt.devolverReferencia()); } } } 3 Podría plantearse una situación excepcional y específica para este ejercicio concreto, en el caso de aparecer una pareja que ofrezca diferencia de 1 unidad (la lista está ordenada y no hay elementos repetidos). La inserción del nuevo nodo tendría lugar entre ambos elementos dado que aunque aparezcan nuevas diferencias de valor 1, el enunciado establece que la inserción se realice entre los de menor valor. ListasTADMatrizDinamicaBidimensional. Enunciado. Dadas las siguientes declaraciones: class ListaColumna { int contenido; ListaColumna sig; ListaColumna (int dato) { contenido = dato; sig = null; } } class Lista { ListaColumna columna; Lista sig; Lista () { columna = null; sig = null; } } public class MatrizDinamica { Lista matriz; int numFilas, numColumnas; MatrizDinamica () { matriz = null; numFilas = 0; numColumnas = 0; } } SE PIDE: Implementar el TAD Matriz Dinámica Bidimensional de Números Enteros Positivos con las siguientes operaciones: public int recuperarContenido (int fila, int columna) /*Devuelve el contenido de la posición dada por fila y columna. Si la fila y/o la columna referenciadas no existiesen en la matriz, el método devolverá el valor cero*/ public void escribirContenido (int fila, int columna, int valor) /*Escribe en la posición dada por fila y columna el valor especificado. Si la fila y/o la columna no existiesen en la matriz, se creará una nueva posición dada por esa fila y esa columna, en la que se almacenará el mencionado valor y además deberán crearse tantas nuevas posiciones como sean necesarias para alcanzar las mencionadas fila y columna. Todas esas nuevas posiciones deberán inicializarse con el valor cero.*/ Por ejemplo, supóngase que se dispone de una matriz de 2x3 y se deseara escribir en la posición (3,4) el valor 8, la matriz deberá experimentar los cambios mostrados en la figura: 0 0 1 1 2 2 4 9 3 8 7 0 1 2 3 0 1 2 3 4 2 3 0 0 4 8 0 0 9 7 0 0 0 0 0 0 0 0 0 8 OBSERVACIONES: No se permite la utilización de estructuras de datos auxiliares. Tanto los números de filas y columnas como los valores serán siempre positivos. Ejercicios de Estructuras de Datos I Listas 139 Orientación. La implementación física de la estructura se ilustra en la figura siguiente: Lista de punteros a las sucesivas columnas matriz col col col * 2 4 9 3 8 7 * * * Columnas con el contenido de la matriz 3 nodos. El diseño propuesto implica accesos múltiples a los diferentes Método escribirContenido: Utiliza tres métodos auxiliares. o aumentarColumnas. Se ejecuta solo en caso de que alguno de los argumentos fila o columna sea superior a la variable numFilas o numColumnas, respectivamente. En ese caso habrá que proceder a “ampliar” la matriz. Este método de cabecera: static ListaColumna aumentarColumnas (Lista lista, int columnas, int filas) Se ejecuta recursivamente tantas veces como sea necesario (numColumnas-columna). Si numFilas < fila, llamará al siguiente método auxiliar: static ListaColumna aumentarFilas (ListaColumna lista, int filas) Que se encarga de añadir tantos nodos (con contenido 0) como haga falta. Al finalizar, devolverá una referencia al último nodo añadido. A su vez, aumentarColumnas devolverá al método de llamada una referencia al nodo correspondiente a la posición (filas, columnas). Los otros dos métodos se ejecutan si la posición buscada ya existía en la matriz o devolverColumna. Devuelve una referencia a la columna de posición n. Su cabecera es: static Lista devolverColumna (Lista lista, int n) o buscarFila. Devuelve una referencia al elemento de la posición fila de la columna lista. static ListaColumna buscarFila (ListaColumna lista, int fila) En cualquiea de los dos casos, una vez localizada (o añadida) la posición correspondiente, se asigna al campo contenido referenciado en ese momento el valor pasado como argumento (auxF.Contenido=valor) Método recuperarContenido: Este método deberá detectar si los argumentos fila y/o columna suponen una situación de “fuera de rango” (fila > numFilas y/o columna > numColumnas). Su cabecera es: public int recuperarContenido (int fila, int columna) Los valores de fila y columna se utilizarán como condiciones de terminación (> 0) enviándolos a las siguientes llamadas decrementados en una unidad. Utiliza los métodos auxiliares descritos anteriormente: o devolverColumna, que devuelve una referencia a la columna de posición n. o buscarFila, que devuelve una referencia al elemento de la posición fila de la columna lista. Como resultado devuelve auxF.contenido, o 0 si la posición no existe. Código. static Lista devolverColumna (Lista lista, int n) { Lista aux; if (n > 0) if (lista != null) aux = devolverColumna (lista.sig, n-1); else aux = null; else aux = lista; return aux; } static ListaColumna buscarFila (ListaColumna lista, int fila) { ListaColumna resul; if (fila > 0) resul = buscarFila (lista.sig, fila-1); else resul = lista; return resul; } public int recuperarContenido (int fila, int columna) { ListaColumna auxF = null; Lista auxC = null; int resul = 0; if (numFilas >= fila && numColumnas >= columna) { auxC = devolverColumna (matriz, columna); if (auxC != null) auxF = buscarFila (auxC.columna, fila); if (auxF != null) resul = auxF.contenido; } return resul; } static ListaColumna aumentarFilas (ListaColumna lista, int filas) { ListaColumna resul, aux; if (filas > 0) { if (lista.sig == null) { aux = new ListaColumna (0); lista.sig = aux; } resul = aumentarFilas (lista.sig, filas - 1); } else resul = lista; return resul; } Ejercicios de Estructuras de Datos I static ListaColumna aumentarColumnas (Lista lista, int columnas, int filas) { ListaColumna resul = null, auxF; Lista aux; if (columnas > 0) { if (lista.sig == null) { aux = new Lista (); lista.sig = aux; } resul = aumentarColumnas (lista.sig, columnas - 1, filas); } if (lista.columna == null) lista.columna = new ListaColumna (0); auxF = aumentarFilas (lista.columna, filas); if (columnas == 0) resul = auxF; return resul; } public void escribirContenido (int fila, int columna, int valor) { ListaColumna auxF = null; Lista auxC = null; if (numColumnas >= columna && numFilas >= fila) { auxC = devolverColumna (matriz, columna-1); if (auxC != null ) { auxF = buscarFila (auxC.columna, fila); } } else { if (matriz == null) { matriz = new Lista (); } auxF = aumentarColumnas (matriz, columna, fila); if (columna < numColumnas) aumentarColumnas (matriz, numColumnas, fila); else if (fila < numFilas) aumentarColumnas (matriz, columna, numFilas); if (fila > numFilas) numFilas = fila; if (columna > numColumnas) numColumnas = columna; } if (auxF != null) auxF.contenido = valor; } Listas 141