to get the file

Anuncio
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
Descargar