tema 3. listas. - ocw upmocw.upm.es/.../contenidos/tema3nuevo/listas_ej.pdf · en particular en el...

30
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.

Upload: ngocong

Post on 21-Oct-2018

221 views

Category:

Documents


0 download

TRANSCRIPT

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.

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”.

9 4 6 8 4 5 4 4

D=3 D=2 D=1

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 -1, si el valor del último nodo es menor que el número de nodos de la lista.

o +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 5 null 5

1

4 2

lista2 1 1 null 3

lista3 7 5 null 3 5

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.

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.

1 2 3 2 -6

2 0 3 2 6 0 1

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;

}

5 4 3 6 7

Figura c)

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

también 10 (4+6). Por tanto, al coincidir ambas

sumas, se deberán borrar los elementos que

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

que ocupan posiciones pares es también 10 (7 + 3). Por

tanto, al coincidir ambas sumas, se deberán borrar los

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 +

6). Por tanto, al no concidir ambas sumas, se

deberán borrar los elementos que ocupan posiciones

impares, es decir, el 5, el 3 y el 7.

5 4 3 6 2

Figura a)

4 7 6 3

Figura b)

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.

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.

3 5 4 2 1 lista

4 5 3 1 2 lista

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.

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.

Lista 1 1 5 null 4

1

3 2

Lista 2 1 4 null 3

Lista 3 1 7 null 6 2

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:

Situación final:

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.

3 5

lista2

1 5 25 *

7 12 16

lista1

1 5 25 * 7 12 16

lista1

3 5 25 * 12 16

lista2

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.

Figura 1.

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..

9 10

0 13 14 15 21 *

cabecera centinela

lista

13 9

0 10 14 15 21 *

cabecera centinela

lista

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.

Figura a

5

1

3

2

6

4

Figura b

5

1

3

4

2

Figura c

5

1

3

2

4

n=6

1

3

2

n=6

1

3

2

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 2 3 4 5 6 7 8

1 10 12 21 13

2 3 4 7 6 0 8 5 0

Figura 1 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.

Figura 3 Figura 4

0 1 2 3 4 5 6 7 8

1 10 1 12 21 13

4 3 7 2 6 0 8 5 0

10 13 12 21 1

10 13 12 21

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 Listas 135

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);

}

}

}

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.

Figura 1 Figura 2

12 13 21 10 12 1 13 21 10

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 1 2 3 4

0 1 2 0 2 4 9 0 0 0 2 4 9 1 3 8 7 0 0 1 3 8 7 2 0 0 0 0 0 3 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

Columnas con el contenido de la matriz

El diseño propuesto implica accesos múltiples a los diferentes nodos.

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.

matriz col col col *

2

3

*

3

4

8

*

9

7

*

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 Listas 141

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;

}