Pág. 1 ISC Gregorio García Estrada
3. LISTAS
3.1 Arreglos
3.1.1 Arreglos unidimensionales
Un arreglo se usa para agrupar, almacenar y organizar datos de un mismo tipo. En un arreglo cada valor se almacena en una posición numerada específica dentro del arreglo. El número correspondiente a cada posición se conoce como índice.
La estructura de un arreglo es:
Normalmente el primer objeto del arreglo tiene el índice 0, aunque esto varía de lenguaje en lenguaje.
Declaración:
La declaración de un arreglo en pseudocódigo que se utilizará es:
tipo [ ] nombreArreglo
Un arreglo es un objeto por lo que para crear el espacio se utiliza el operador nuevo:
nombreArreglo Å nuevo tipo [max]
donde tipo puede ser un tipo de datos primitivo o un objeto, nombre es el nombre del arreglo y max es el número de localidades del arreglo. Es una buena costumbre que max sea declarado como una constante.
Pág. 2 ISC Gregorio García Estrada También puede declararse de la siguiente forma:
tipo [] nombreArreglo Å nuevo tipo [max]
Operaciones:
• almacenamiento, una operación de almacenamiento inserta un valor en el arreglo en una localidad específica.
nomArreglo [i] Å valor
• recuperación, una operación de recuperación regresa el valor almacenado en una localidad específica del arreglo
valor Å nomArreglo[i]
donde i es la posición o índice del arreglo, que puede ser un entero o bien una expresión que al ser evaluada regresa un entero
Por ejemplo:
const int MAX Å 10
int [] arregloEnteros Å nuevo int [MAX]
arregloEnteros [0] Å 1 arregloEnteros [1] Å 10 a Å 5
arregloEnteros [a-2] Å arreglosEnteros [0] + arregloEnteros [1]
Esquemáticamente arregloEnteros se vería como:
• número de localidades, como dato miembro público y constante de un arreglo se puede accesar el número de localidades:
int identificador Å nomArreglo.longitud Ejemplos:
a) Arreglos que utilizan datos primitivos
1. Leer una lista de números, almacenarlos en un arreglo e imprimirlos en orden inverso
Pág. 3 ISC Gregorio García Estrada clase NumerosReves
{ principal comienza
const int MAX Å 10 //Número máximo de posiciones del arreglo //creación del arreglo de reales
real [] numeros Å nuevo real [MAX]
//se piden al usuario los datos escribe “El tamaño del arreglo es: “ + numeros.longitud para indice Å 0 hasta numeros.longitud –1
comienza
escribe “Dame el número que se encuentra en ” + índice lee numeros [indice]
termina
//se escribe al revés el arreglo escribe “Los números al revés son: “
para indice Å numeros.longitud-1 hasta 0, -1 escribe numeros [indice]
termina }
2. Considere los siguientes datos miembro y la función constructora por default:
clase EjemplosArreglo
{ const int MAXLOC Å 100 int [] info
EjemplosArreglo () comienza
info Å nuevo int [MAXLOC]
para i Å 0 hasta info.longitud-1 comienza
escribe “Dame el valor del arreglo en la posición “ + i lee info [i]
termina termina
}
A continuación se presentan algunas funciones miembro que podrían pertenecer a la clase EjemplosArreglo
a) Calcular la suma de los elementos de un arreglo
int sumaElem () comienza
suma Å 0
para i Å 0 hasta info.longitud-1 suma Å suma + info[i]
regresa suma termina
b) Calcular el promedio de los elementos de un arreglo
Pág. 4 ISC Gregorio García Estrada real promedio ()
comienza
regresa (sumaElem() / info.longitud) termina
c) Determinar el elemento más grande del arreglo
int maximo () comienza
max Å info [0] //El mayor es el 1ro. hasta que se demuestre lo contrario para i Å 1 hasta info.longitud-1
si max < info[i] entonces max Å info [i]
regresa max termina
d) Ordenar un arreglo por selección
El algoritmo de selección ordena un arreglo de valores poniendo un valor particular en su posición final, i.e., el algoritmo selecciona el valor que debería ir en una posición y lo pone ahí.
Considere un orden ascendente y el siguiente arreglo de números.
Se busca el elemento menor del arreglo y se pone en la 1ra. posición,
y entonces el arreglo queda como:
Note que ahora la celda amarilla contiene al elemento más pequeño y que está en la posición que quedara cuando el arreglo esté completamente ordenado.
A continuación se busca el siguiente más pequeño que deberá de estar en la segunda posición del arreglo:
Pág. 5 ISC Gregorio García Estrada y se intercambia con el elemento que ocupa la 2da. posición del arreglo. Y posteriormente se busca el siguiente más pequeño:
Ahora el 5 deberá de ocupar la tercera posición del arreglo. Y se buscará el elemento más pequeño de los restantes:
Por último se coloca el 6 en su posición correspondiente, intercambiándolo con el 8. Y así el arreglo queda ordenado:
Nótese que el último elemento quedará automáticamente ordenado.
A continuación se presenta el algoritmo:
void ordenSeleccion () comienza
int min, temp
para indice Å 0 hasta info.longitud –2 comienza
min Å indice
para busca Å indice+1 hasta info.longitud-1 si info[busca] < info [min] entonces
min Å busca //se intercambian temp Å info [min]
info [min] Å info [indice]
info [indice] Å temp termina
termina
b) Arreglos que utilizan objetos
La creación de un arreglo y la creación de objetos son dos cosas separadas. El mecanismo para trabajarlos es el mismo.
Considere la siguiente clase en Java para crear CD:
Pág. 6 ISC Gregorio García Estrada /**
* Define un disco compacto
* */
class CD {
// datos miembro String titulo;
String autor;
int año;
double precio;
/**
* Constructor para objetos de la clase CD
*/
CD(String t, String au, int a, double p) { Titulo = t;
autor = au;
año = a;
precio = p;
} /**
* Escribe los datos miembro
*/
void escribe () {
System.out.println ("Título: " + titulo);
System.out.println ("autor: " + autor);
System.out.println ("año: " + año + " $ " + precio);
System.out.println ("~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
} }
Y la clase discoteca donde se utiliza un arreglo de CD
/**
* Maneja un arreglo de CD */
class discoteca
{ CD [] arregloCD;
int numCD;
final int MAXIMO = 100;
/**
* Constructora de la discoteca
*/
public discoteca() {
numCD = 0;
arregloCD = new CD [MAXIMO];
} /**
* Inserta un CD
Pág. 7 ISC Gregorio García Estrada
*/
void insertaCD (String tit, String au, int a, double costo) { // se crea un CD
if (numCD > MAXIMO)
System.out.println ("Ya no hay lugar");
else {
CD nuevoCD = new CD (tit,au,a,costo);
arregloCD[numCD] = nuevoCD;
numCD++;
} }
/**
* Escribe en pantalla todos los CD
*/
void escribe () {
for (int i = 0; i < numCD; i++)
arregloCD[i].escribe ();
} /**
* Calcula el costo total de la inversión
*/
double costo () {
double c = 0;
for (int i = 0; i < numCD; i++) c = c + arregloCD[i].precio;
return c;
}
public static void main (String [] a) { //Se crea una discoteca privada
discoteca privada = new discoteca ();
privada.insertaCD ( "Storm Front", "Billy Joel", 1960, 140.00);
privada.insertaCD ("Come On Over", "Shania Twain", 1990, 145.50);
privada.insertaCD ("Graceland", "Paul Simon", 1991, 123.60);
System.out.println ("\nDiscoteca privada");
privada.escribe ();
//Se crea una discoteca clasica discoteca clasica = new discoteca ();
clasica.insertaCD ("Concierto No. 5 para piano", "Tchaikowsky", 1763, 57.90);
clasica.insertaCD ("Carmen", "Bizet", 1896, 67.25);
System.out.println ("\nDiscoteca clásica");
clasica.escribe ();
//Reporta inversiones
System.out.println ("El costo de lo invertido en la discoteca privada es $"+ privada.costo ());
System.out.println ("El costo de lo invertido en la discoteca clasica es $"+ clasica.costo ());
} }
Pág. 8 ISC Gregorio García Estrada La salida producida es:
Pág. 9 ISC Gregorio García Estrada
3.1.2 Arreglos de dos dimensiones
Los arreglos examinados hasta el momento han sido unidimensionales en el sentido de que representan una lista simple de valores. Un arreglo bidimensional, como su nombre lo indica, tiene valores en dos dimensiones, en algunas ocasiones se le llama también tabla y se tiene acceso a sus elementos a través de la columna y el renglón del arreglo.
Para definir un arreglo en dos dimensiones es necesario poner el tipo de la información que se desea almacenar (un tipo primitivo o un objeto) y especificar que se tendrán dos dimensiones:
tipo [] [] nombre Ejemplos:
a) Definir un arreglo bidimensional de enteros:
int [] [] matriz
b) Definir una tabla de booleanos:
bool [][] tablero
c) Definir un arreglo bidimensional de fichas ficha [] [] juego
Como en los arreglos de una sola dimensión, el tamaño de las dimensiones se especifica cuando el arreglo es creado y el tamaño de cada dimensión puede ser diferente.
Pág. 10 ISC Gregorio García Estrada Así, para el ejemplo anterior:
a) matriz Å nuevo int [5][5]
b) tablero Å nuevo bool [10][MAX], donde MAX almacena un valor entero
c) juego Å nuevo ficha [NUMCELDAS] [NUMCELDAS-2], donde NUMCELDAS almacena un valor entero
Las operaciones que pueden realizarse con cada elemento de un arreglo son:
• almacenamiento
• recuperación
En cada operación es necesario especificar unívocamente el elemento por lo que es necesario dar el índice para cada dimensión:
Por ejemplo:
Ejemplo Almacenamiento Recuperación a) matriz [2][3] Å 3 int x Å matriz [4][4]
b) tablero [3][1] Å verdadero si tablero [2][2] entonces e1
c) juego [1][8] Å nuevo ficha () ficha f Å juego [0][0]
Ejemplos:
1. Escribir una función estática que lea los elementos de una matriz de tamaño MAX.
estatica int [][] leeMatriz () comienza
matriz Å nuevo int [MAX][MAX]
para i Å 0 hasta MAX-1 para j Å0 hasta MAX-1 comienza
escribe “Dame el elemento “ + i + “,” + j lee matriz[i][j]
termina regresa matriz termina
2. Suma de dos matrices
Las operaciones de suma y resta de matrices están definidas para dos matrices si tienen el mismo número de:
a) renglones b) columnas
Pág. 11 ISC Gregorio García Estrada
La suma de dos matrices es la matriz obtenida de sumar pares correspondientes de elemento, de tal forma que si:
⎟⎟
⎟⎟
⎟
⎠
⎞
⎜⎜
⎜⎜
⎜
⎝
⎛
=
⎟⎟
⎟⎟
⎟
⎠
⎞
⎜⎜
⎜⎜
⎜
⎝
⎛
=
bnn n2 ...
n1 b b
...21 b22 ... b2n b11 b21 ... b1n b
B y ann n2 ...
n1 a a
...21 a22 ... a2n a11 a12 ... a1n a
A
entonces
⎟⎟
⎟⎟
⎟
⎠
⎞
⎜⎜
⎜⎜
⎜
⎝
⎛
+ +
+
+ +
+
+ +
+
= +
bnn ann n2 ...
n2 b n1 a n1 b a
...b21 a22 b22 ... a2n b2n aa2111 b11 a12 b12 ... a1n b1n B
A
La diferencia A-B, es la matriz obtenida de restar los elementos de B de los elementos correspondientes de A:
⎟⎟
⎟⎟
⎟
⎠
⎞
⎜⎜
⎜⎜
⎜
⎝
⎛
−
−
−
−
−
−
−
−
−
=
−
nn nn n2 n2 n1 n1
2n 2n 22 22 21 21
1n 1n 12 12 11 11
b a ...
b a b a
...
b a ...
b a b a
b a ...
b a b a B A
Una función para sumar matrices podría ser:
estática int [] [] suma (int [][] A, int [][] B ) //Supone que A y B son del mismo tamaño comienza
int [][] result Å nuevo int [A.longitud][A.longitud]
para i Å 0 hasta A.longitud-1
para j Å 0 hasta A.longitud-1 result [i][j] ÅA[i][j] + B[i][j]
regresa result termina
Los algoritmos mostrados anteriormente pueden escribirse dentro de una clase en Java, por ejemplo:
import Teclado;
/**
* Ejemplos de matrices */
class EjemploMatrices {
//No existen datos miembro /**
* Lee una matriz de tamaño MAX por MAX, donde MAX se pasa como parámetro
*/
static int [][] leeMatriz(int MAX)
Pág. 12 ISC Gregorio García Estrada {
int [][] matriz = new int [MAX][MAX];
for (int i = 0; i < MAX; i++)
for (int j = 0; j < MAX; j++) {
System.out.print ("matriz [" + i + "][" + j + "] = " );
matriz[i][j] = Teclado.readInt ();
}
return matriz;
} /**
* Imprime los valores de una matriz
*/
static void escribe (int [][] matriz)
{ for (int i = 0; i < matriz.length; i++) {
for (int j = 0; j < matriz.length; j++)
System.out.print (matriz [i][j] + " " );
System.out.println();
} }
static int [][] suma (int [][] A, int [][] B ) {
int [][] result = new int [A.length][A.length];
for (int i = 0; i < A.length; i++)
for (int j = 0; j < A.length; j++) result [i][j] = A[i][j] + B[i][j];
return result;
}
public static void main (String [] a) {
// EjemploMatrices ejem = new EjemploMatrices ();
//Se crea y se lee una matriz int [][] A = leeMatriz (2);
//Se crea y se lee otra matriz int [][] B = leeMatriz (2);
//Se suman A y B dejando el resultado en C int [][] C = suma (A,B);
System.out.println ("Matriz A");
escribe (A);
System.out.println ("Matriz B");
escribe (B);
System.out.println ("La suma de A y B es");
escribe (C);
} }
A continuación se muestra una ejecución:
Pág. 13 ISC Gregorio García Estrada
Pág. 14 ISC Gregorio García Estrada
Cuadrado Mágico
En la pintura Melancholia I del matemático y artista alemán Albrecht Dürer, aparece un cuadrado mágico
Abajo aparece aislado el cuadrado mágico.
¿Qué propiedades debe tener un cuadrado para ser mágico?
Pág. 15 ISC Gregorio García Estrada
lsuma renglones suma columnas suma diagonal suma antidiagonal
la suma debe ser (n3+n) / 2
clase CuadradoMágico {
int [] elem
int n //número de elementos ....
iint sumaDiagonal () comienza
int suma Å 0 para i Å1 hasta n
suma Å suma + elem [i][i]
regresa suma termina
iint sumaAntiDiagonal () comienza
int suma Å 0 para i Å1 hasta n
suma Å suma + elem [ ][ ] regresa suma
termina ...
}
Grado de similaridad:
Pág. 16 ISC Gregorio García Estrada real gradoSimilaridad (bool [][] original)
comienza
int diferentes Å 0 para i Å0 hasta longitud ()-1
para j Å 0 hasta longitud () –1
si original [i][j] <> letra [i][j] entonces diferentes ++
regresa (diferentes 100) /(n*n) termina
Ejercicios:
1. Escribir una clase que lea una cadena (String) y construya un arreglo donde se almacena la frecuencia de las letras de la cadena
2. Declare una clase que permita representar los siguientes datos de un automóvil:
dueño, marca, modelo y placas. Además, deberá tener 2 funciones constructoras y una función para escribir los datos del automóvil.
3. Utilizando la clase anterior construya un arreglo de automóviles y escriba una función que permita determinar cuál marca es la más popular
4. Escriba una función que multiplique dos matrices
5. Escriba una función que determine la suma de todos los elementos de una matriz Ejercicio de programación (Programa)
Implemente en Java la clase EjemploVector vista en clase.
Implemente en Java una clase que permita determinar si un cuadrado es mágico
Pág. 17 ISC Gregorio García Estrada
3.2 Listas lineales
En la vida diaria frecuentemente se utilizan expresiones como 'lista de alumnos', 'lista de equipos de football', 'lista de espera', etc., para enumerar elementos en un cierto orden. Así, por ejemplo, se orden alumnos por su nombre o equipos de football por el número de partidos que han ganado o personas que esperan recibir un servicio según el orden en que se presentaron.
Cuando se trata de resolver un problema, por lo general resulta conveniente utilizar un modelo matemático adecuado para su representación. Matemáticamente, una lista es una secuencia que contiene cero o más elementos de un mismo tipo. De esta forma:
a1, a2, . . ., an n >0
indica que una lista está formada por n elementos. Si n=0, se dice que la lista está vacía, es decir, no contiene elementos. Si n>0, se dice que a1 la cabeza de la lista, es decir, el primer elemento de la lista. Al último elemento de la lista, an, se le conoce como la cola de la lista.
Las listas poseen la propiedad de que sus elementos se encuentran linealmente ordenados de acuerdo a su posición (ai está en la posición i de la lista), de esta forma, decimos que ai
precede a ai+1 para i = 1,2,...,n-1 y ai-1 está después de ai-1 para i = 1,2,..,n-1.
El tipo de datos abstracto (TDA) lista
Para formar el tipo de datos abstracto (TDA) lista, se debe definir su conjunto de operaciones. En este caso como en algunos otros, el conjunto de operaciones depende de la aplicación con la que se esté trabajando. A continuación se presenta un conjunto de operaciones que pueden realizarse con el TDA lista.
Función Descripción
lista () Crea una lista vacía.
bool vacía () Determina si existen o no elementos en la lista
void inserta (Objeto x) Inserta en la lista el elemento x en la posición que conserva el orden de la lista.
void borra (Objeto x) Elimina el elemento x de la lista int busca (Objeto x, bool
exito) Determina la posición donde se encuentra el elemento x dentro de la lista, en caso de que exista; en caso de que no exista, lo señalara e indica en qué posición debería estar si existiese. La variable boolena éxito sirve para determinar cuándo el elemento forma o no parte de la lista.
int anterior (Nodo p) Determina la posición que se encuentra antes de la
Pág. 18 ISC Gregorio García Estrada
posición p de la lista.
int cola () Determina la posición donde se encuentra el último elemento de la lista.
La forma en la cual suele representarse el TDA lista en una computadora es por medio de asignación estática de memoria (representación secuencial), o por medio de asignación dinámica de memoria (representación ligada). Cada una de ellas tiene ventajas o desventajas sobre la otra. A continuación se desarrollan para su estudio.
3.2.1 Representación secuencial
Para representar en memoria contigua una lista, se puede utilizar un arreglo, en el que cada celda sea capaz de almacenar un elemento de la lista, y un índice que señale el número de elementos almacenados.
La forma de representar una lista en representación secuencial es la siguiente:
clase Lista {
Objeto info []
int último Lista () bool vacía ()
void insertar (Objeto x) void borrar (Objeto x) bool buscar (Objeto x) }
donde Objeto es el tipo de información con el cual trabajará la lista (entero, real, booleano, cadena, registro, o bien alguna otra clase).
Suponga que se desea almacenar la lista de alumnos de Estructura de datos (llamada ListaEst), con tres calificaciones de exámenes parciales y su promedio. Además, se desea que la lista se encuentre en orden alfabético. En este caso, la clase alumno podría ser:
clase alumno {
Cad Nombre //Nombre del alumno real E1, E2, E3 //Calificación de 3 exámenes
Cad prom //Calificación numérica del promedio de exámenes }
de esta forma, la lista se vería como:
Pág. 19 ISC Gregorio García Estrada fig. 1
1.2.1 Operaciones sobre una lista en representación secuencial Crear:
Para crear una lista, lo único que debe hacerse es inicializar el índice que señala el número de registros almacenados. Así, la función constructora por defáult podría ser:
Lista ()
//crea una lista vacía comienza
último Å 0
info Å nuevo Objeto [MAX]
termina Vacía:
El procedimiento anterior sirve como guía para escribir la función que determina si una lista se encuentra vacía:
bool vacía ()
//regresa verdadero si la lista se encuentra vacía, en otro caso regresa falso comienza
vacía Å último = 0 termina
Insertar:
Para insertar un nuevo estudiante en nuestra lista, primero debe encontrarse la posición en el arreglo donde colocar el objeto y mover hacia abajo un lugar a todos aquellos objetos que se encuentren abajo de él para que la lista continúe siendo ordenada. Por ejemplo, si se incorpora Guillermo en la lista de estudiantes de la fig. 1, ListEst sería:
Pág. 20 ISC Gregorio García Estrada fig. 2
Note que el problema de mantener una lista ordenada, requiere algo más que un algoritmo de ordenación.
De manera general, el algoritmo para insertar un elemento x en una lista es el siguiente:
void inserta (Objeto x)
//Inserta el elemento x en la lista de tal forma que el orden persista comienza
//busca la posición donde debe ir el elemento p Å busca (x,exito)
si exito entonces //el elemento se encuentra error (el elemento se encuentra)
otro
comienza //el elemento debe insertarse si ultimo >= Máximo entonces
error (No hay espacio) otro
comienza
moverAbajo (L,p) //recorre hacia abajo los elementos info[p] Å x //pone la información
termina termina
termina
A continuación se desarrollan los métodos utilizados para insertar un elemento:
void moverAbajo (int p)
//recorre una posición hacia abajo a todos los elementos que se //encuentran a partir de la posición p
comienza
último++ //se aumenta el número de elementos aux Å último +1
Pág. 21 ISC Gregorio García Estrada mientras aux > p
comienza
info[aux] Å info[aux-1] //la información se recorre un lugar
//hacia abajo
aux-- termina termina
Buscar:
int busca (Objeto x; var exito:booleano)
//busca el elemento X en la lista y regresa la posición donde se encuentra //en caso de que el elemento no forme parte de la lista, regresa la posición //donde debería encontrarse, la búsqueda se realiza de arriba hacia abajo //con ayuda de un centinela, colocándola una posición debajo de donde //termina la lista
comienza
i Å 0 //i toma el valor del último índice de la lista exito Å verdadero
info [MAXIMO] Å x //variable centinela mientras info[i] < x
i--
si info[i] <> x or i = MAXIMO-1entonces comienza
exito Å falso i++
termina busca Å i termina
El procedimiento para insertar un nuevo elemento en una lista que se ha desarrollado, consiste en agregar el elemento en la posición que conserva el orden alfabético o numérico de sus elementos. Sin embargo, en algunas aplicaciones el orden que debe tomarse en cuenta puede ser otro, como por ejemplo el orden de llegada.
Borrar:
Considere ahora cómo eliminar un elemento de una lista. Existen dos formas representativas de hacerlo.
a) La primera consiste en buscar el elemento que se desea eliminar y dejar en blanco la celda que lo contiene, o bien marcarla como borrada. Por ejemplo, si deseamos eliminar a Luis, el arreglo de estudiantes mostrado en la fig. 1, quedaría como:
Pág. 22 ISC Gregorio García Estrada fig. 3
Las tres principales desventajas que se presentan en este tipo de esquema son:
1. Puede ocurrir que la última posición del arreglo esté ocupada y se quiera insertar un nuevo elemento que es mayor que el valor del elemento que se encuentra en la última celda del arreglo. En este caso no se puede insertar este valor, aunque existan celdas vacías en el arreglo. Esto puede solucionarse introduciendo un procedimiento de reorganización o reacomodo que junte todas las casillas ocupadas, dejando al final aquellas que no contienen información, cuando sea necesario. Lo cual es costoso.
2. El procedimiento para buscar un elemento deberá ver todas las casillas, aún cuando no estén ocupadas.
3. El procedimiento para insertar un nuevo elemento deberá modificarse para que cuando la posición deseada se encuentre vacía lo inserte en esa casilla y no recorra las posiciones hacia abajo.
b) El segundo esquema que se presenta para borrar un elemento de una lista en representación secuencial, consiste en buscar la posición donde se encuentra el elemento y recorrer todos los elementos que están debajo de él una posición hacia arriba. Por ejemplo, si se desea eliminar a Luis de la lista mostrada en la figura 1, se obtendría:
fig. 4
El algoritmo para eliminar un elemento de una lista dada, que utiliza el segundo esquema, se
Pág. 23 ISC Gregorio García Estrada muestra a continuación:
void borra (tipoInfo x)
//elimina un elemento de una lista, si es que se encuentra comienza
p Å busca (x,exito) //se manda buscar el elemento si ¬exito entonces
error (el elemento no se encuentra) otro
comienza
MoverArriba (p) //se recorren una posición hacia arriba //todos los elementos a partir de p
último Å último-1 //disminuye el número de elementos termina
termina
void MoverArriba (int p)
//recorre una posición hacia arriba los elementos que se encuentran a partir //de la posición p de la lista
comienza
aux Å p
mientras aux < último comienza
info[aux] Å info[aux+1]
aux ++
termina termina
Nótese que no es necesario limpiar la última celda que estaba ocupada antes de eliminar un elemento. Esto se debe a que nunca se tendrá acceso a esa celda de memoria mientras no se inserte un nuevo elemento, momento en que esa localidad de memoria será reasignada con un nuevo valor.
3.2.2 Representación ligada
Existen aplicaciones en las cual es el número de elementos cambia dinámicamente durante la ejecución del algoritmo, (i.e. se realizan inserciones y/o eliminaciones). Es fácil ver que cuando se inserta o elimina un elemento que se encuentra a mitad de la lista es necesario mover la mitad de los elementos o peor aun cuando se inserta o elimina al principio de la lista, donde se necesitan mover todos los elementos; esto implica mucho trabajo, es decir es costoso.
Otro inconveniente que existe cuando se representa una lista en un arreglo, es que en algunas ocasiones es muy difícil o imposible predecir el número de elementos que van a existir. La solución a esto podría ser reservar un espacio de memoria mucho mayor que el que se considera podría necesitarse. Esta deficiencia aunque es inherente a los arreglos, se
Pág. 24 ISC Gregorio García Estrada ve reflejada en una ineficiencia en cuando al espacio de memoria requerido.
Como puede observarse las listas en representación secuencial tienen un alto costo tanto en complejidad temporal como en complejidad espacial. Una solución a este problema es el uso de la representación ligada o dinámica, donde cada elemento es representado como una entidad por separado y todos los elementos se conectan a través de referencias.
Listas ligadas
El problema de mantener una lista ordenada en un arreglo, requiere de mover datos cuando se realizan inserciones y borrados. Estos movimientos podrían eliminarse si cada elemento indicara que elemento es el que se encuentra después de él. De esta forma, no es necesario considerar la noción natural que tenemos, de que algo se encuentra ordendo, si físicamente se encuentra ordenado. Conceptualmente podemos dibujar una lista de la siguiente forma:
Una lista en representación ligada, es un conjunto de nodos, donde cada uno contiene al menos dos datos miembros, el primer sirve para guardar la información que se desea y el segundo sirve para saber quién es el elemento que se encuentra después de él. De esta forma podemos considerar que cada elemento de la lista un objeto de la siguiente clase:
clase Nodo {
//datos miembro Objeto info Nodo liga //constructoras
Nodo ()
{
Nodo Å nil }
Nodo (Objeto datos) {
info Å datos liga Å nil }
Nodo (Objeto datos, Nodo l) {
info Å datos liga Å l }
//Imprime la información del nodo
Pág. 25 ISC Gregorio García Estrada void escribe ()
}
De tal forma que la clase lista quedaría definida como:
clase Lista {
//dato miembro Nodo cabeza
//Función constructora Lista ()
//funciones miembro bool vacía ()
bool busca (Objeto o, Nodo pos) void borrar (Objeto o)
void inserta (Objeto o) }
1.3.3 Operaciones sobre una lista en representación ligada
Al igual que en la representación secuencial de una lista, las operaciones que se desarrollan, consideraran que los elementos de la lista se encuentran en orden ascendente.
Crear:
Cuando se desea crear una lista, esta debe de encontrarse vacía. Así:
Lista ()
//construye una lista vacía comienza
this Å nil termina
La forma gráfica en que representaremos que una lista está vacía es:
Vacía:
De forma semejante para determinar si una lista está o no vacía:
bool vacía ()
//regresa verdadero si la lista se encuentra vacía, en otro caso //regresa falso
Pág. 26 ISC Gregorio García Estrada comienza
vacía Å this = nil termina
Buscar:
En muchas ocasiones es necesario saber si un elemento se encuentra y la posición que ocupa en la lista, de esta forma, por medio de una referencia puede conocerse el elemento que lo contiene; en caso de que el elemento que se busca no se encuentre, se regresa la posición del elemento que debería ir después de él.
Aprovechando el hecho de que la lista se encuentra ordenada, el algoritmo puede escribirse como:
Nodo busca(Objeto x, booleano exito)
//busca el objeto x en la lista; en caso de que exista regresa una referencia //al elemento que lo contiene; en caso contrario, regresa una
//referencia al elemento que debería de ir después de él comienza
aux Å cabeza //aux sirve para moverse a través de la lista //mientras no se termine la lista y la información del nodo sea //menor que la buscada se avanza al siguiente nodo
mientras aux <> nil & aux.info < x aux Å aux.liga
éxito Å aux <> nil & aux.info = x busca Åaux
termina Insertar:
Cuando se desea insertar un elemento en una lista, lo primero que debe hacer es establecer si se permitirá o no elementos repetidos. Suponga que no se desea tener elementos repetidos. Así, lo primero que debe hacerse es determinar si el elemento se encuentra o no.
En caso de que se encuentre, se invoca al procedimiento de error que tomará las acciones pertinentes, dependiendo de la aplicación. Por otro lado si el elemento no forma parte de la lista, debe ser incorporarlo a la lista.
Para estudiar cómo insertar un elemento en una lista, se dividirá en los siguientes casos.
a) La lista en la cual se desea insertar un elemento se encuentra vacía.
b) El elemento que se desea insertar, deberá ser la cabeza de la lista.
c) El elemento que se desea insertar, deberá ser la cola de la lista.
d) El elemento que se desea insertar, deberá ocupar una posición intermedia.
En cada caso, suponga que el elemento no se encuentra, y que esto fue determinado por el procedimiento busca anterior, que fue invocado como:
Pág. 27 ISC Gregorio García Estrada posterior Å busca (x,exito)
a) La lista en la cual se desea insertar un elemento se encuentra vacía.
En este caso se debe:
0. Determinar que la lista se encuentra vacía.
1. Crear un nodo cuya información sea la que deseamos insertar y cuya liga apunte a nil, ya que no existen más elementos.
2. La lista ahora debe apuntar a este nuevo elemento.
Después de insertar el nodo, gráficamente la lista se vería como:
Las acciones enumeradas anteriormente, pueden escribirse en pseudocódigo como:
si vacía () entonces comienza
Nuevo Å nuevo Nodo (x) cabeza Å Nuevo
termina
b) El elemento que se desea insertar, deberá ser la cabeza de la lista.
En este caso se debe:
0. Determinar que debe ser la cabeza de la lista.
1. Crear un nodo cuya información sea la que deseamos insertar.
2. La liga del nuevo elemento, deberá apuntar a posterior (i.e., la cabeza actual de la lista.
3. La lista apunta ahora al nuevo elemento.
Considere estas acciones de manera gráfica:
Suponga que se desea insertar el elemento ´b´ en la siguiente lista; después de invocar a busca, la lista se ve como:
Pág. 28 ISC Gregorio García Estrada Se crea un nodo cuya información sea ´b´:
La liga del nuevo elemento deberá referenciar a la cabeza de la lista:
Se cambia el valor de la cabeza para que apunte al nuevo primer elemento de la lista:
Una forma de escribir esto es pseudocógigo, puede ser la siguiente:
Para determinar que el elemento x debe insertarse como la cabeza de la lista L, puede preguntarse si el elemento que es referenciado por posterior es el elemento al cual referencia la lista, así:
si posterior = cabeza entonces comienza
nuevoNodo Å nuevo Nodo (x) nuevoNodo.liga Å cabeza cabeza Å nuevoNodo termina
c) El elemento que se desea insertar, deberá ser la cola de la lista.
En este caso se debe:
0. Determinar que debe ser la cola de la lista.
1. Crear un nodo cuya información sea la que deseamos insertar
2. La liga del nuevo elemento, deberá apuntar a nil, ya que después de él no hay más elementos.
3. Determinar el elemento que se encuentra a la izquierda de posterior (nodoAnterior).
4. Cambiar la liga de nodoAnterior, para que ahora apunte al nodo que deseamos insertar.
Pág. 29 ISC Gregorio García Estrada Gráficamente:
Suponga que se desea insertar el elemento ´c´ en la siguiente lista; después de invocar a busca, la lista se ve como:
Se crea un nodo cuya información es c y cuya liga apunta a nil:
Determinar quién es actualmente la cola de la lista:
por último:
En pseudocódigo:
En este caso después de invocar a busca, posterior tendrá el valor de nil.
Para encontrar quién es la cola de la lista se construye una función que regrese una referencia al elemento que es la cola de la lista, note que la liga del elemento que es la cola de la lista siempre apunta a nil:
Pág. 30 ISC Gregorio García Estrada Nodo cola ()
comienza
aux Å cabeza
mientras aux.liga <> nil
aux Å aux.liga // se recorre la lista regresa aux
termina
Así, el pseudocódigo para insertar un elemento x como la cola de la lista L:
si posterior = nil entonces comienza
nuevoNodo Å nuevo Nodo (x) anterior Å cola ()
anterior.liga Å nuevoNodo termina
d) El elemento que se desea insertar, deberá ocupar una posición intermedia.
En este caso se debe:
1. Crear un nodo cuya información sea la que se desea insertar.
2. La liga del nuevo elemento, deberá apuntar al nodo posterior (encontrado por el procedimiento busca).
3. Determinar el elemento que se encuentra a la izquierda de posterior (nodo Anterior).
4. Cambiar la liga del nodoAnterior para que ahora apunte al nodo que se desea insertar.
A continuación se presenta un ejemplo:
Suponga que se desea insertar un nodo cuya información sea "e" en la siguiente lista;
después de invocar a busca, la lista se ve como:
Se crea un nodo cuya información es e y cuya liga apunte a posterior:
Pág. 31 ISC Gregorio García Estrada Se determina quién es nodoAnterior:
Se modifica la liga de nodoAnterior para que apunte a Nuevo:
En pseudocódigo:
comienza
nuevoNodo Å nuevo Nodo (x) nuevoNodo.liga Å posterior
nodoAnterior Å anterior (posterior) nodoAnterior.liga Å nuevoNodo termina
El algoritmo para encontrar el nodo anterior de una cierta posición y el algoritmo para
Pág. 32 ISC Gregorio García Estrada encontrar la cola de la lista son muy parecidos y se dejan como ejercicio al lector.
Considere ahora un algoritmo que inserte un nodo cuya información es x, en una lista L, en cualquiera de los casos anteriores. Nótese que:
i) En el caso que el elemento no se encuentre en la lista, siempre se crea un nodo con la información que se desea incorporar.
ii) Las acciones que se realizan cuando la lista está vacía y cuando el nuevo elemento debe insertarse como la cabeza de la lista son las mismas.
void inserta (Objeto x)
//Inserta el elemento x en la lista, si no existe en ella, la posición en la //cual se inserta conserva el orden ascendente de la lista
comienza
posterior Å busca (x,exito) si exito entonces
error (´El elemento ya se encuentra´) otro
comienza
nuevoNodo Å nuevo Nodo (x, posterior)
//la lista está vacía o el elemento debe insertarse como la cabeza si posterior = cabeza entonces
cabeza ÅnuevoNodo otro
comienza
nodoAnterior Å anterior (posterior) nodoAnterior.liga Å nuevoNodo termina
termina termina
Borrar:
La forma en la cual se elimina un elemento de una lista, es semejante a la inserción; después de localizar al elemento, si es que este se encuentra pueden considerarse varios casos, en donde el elemento que se desea eliminar dentro de la lista es:
a) el único b) la cabeza c) la cola
d) un nodo intermedio
De esta forma la primera parte del algoritmo sería:
nodo Å busca (x,exito) si ¬exito entonces
error (el elemento no existe)
Pág. 33 ISC Gregorio García Estrada otro
a or b or c or d
donde cada caso es mutuamente excluyente.
a) El elemento que se desea eliminar es el único de la lista.
En este caso la lista deberá quedar vacía:
si nodo=cabeza & nodo.liga= nil entonces cabeza Å nodo.liga
b) El elemento que deseamos eliminar es la cabeza de la lista.
Aquí se debe indicar que la lista empezará en el elemento que se encuentra a la derecha del elemento a eliminar.
otro
si nodo = cabeza entonces comienza
cabeza Å nodo.liga termina
c) El elemento que se desea eliminar es la cola de la lista.
En este caso se debe indicar que el elemento que se encuentra a la izquierda del elemento a eliminar deberá ser la cola, esto es que su liga referencie a nil.
otro
si nodo.liga = nil entonces comienza
nodoAnterior Å anterior (nodo)
nodoAnterior.liga Å nil // equivalente a nodo.liga termina
d) El elemento que se desea eliminar es un nodo intermedio.
Aquí el elemento que se encuentra a la izquierda del elemento que se desea eliminar deberá referenciar al elemento que se encuentra a la derecha del que se desea elimina. Así:
otro comienza
nodoAnterior Å anterior (nodo) nodoAnterior .liga Å nodo.liga termina
Ahora se escribirá un algoritmo para eliminar un elemento en una lista, que tome en cuenta
Pág. 34 ISC Gregorio García Estrada los cuatro casos desarrollados anteriormente, para esto note que:
i) El caso a) y b) son el mismo ii) El caso c) y d) son el mismo
Con las observaciones anteriores el algoritmo para eliminar un elemento x de una lista es el siguiente:
void borra (Objeto x)
//elimina el elemento x de la lista , si se encuentra comienza
nodo Å busca (x,exito) si ¬exito entonces
error (El elemento no se encuentra) otro
si nodo = cabeza entonces cabeza Å nodo. liga otro
comienza
nodoAnterior Å anterior (nodo) nodoAnterior.liga Å nodo.liga termina
termina
Las ventajas de la representación ligada sobre la representación secuencial son:
• aumentan y disminuyen a lo largo de su vida.
• mayor flexibilidad en las operaciones.
Las desventajas son:
• se requiere mayor espacio de memoria para guardar un elemento.
• si se quiere tener acceso al elemento 30, se necesita empezar en la cabeza de la lista y recorrer 29 referencias, una a una; lo que con arreglos puede hacerse directamente.
3.2.2 Ejemplos
Pág. 35 ISC Gregorio García Estrada
Ejemplo 1: Número de elementos de una lista
Crear un algoritmo que cuente el número de elementos de una lista ligada.
Este problema puede resolverse recorriendo toda la lista, esto es pasando a través de cada uno de sus elementos e irlos contando, para esto utilizaremos una variable auxiliar de tipo Nodo (p) que ayude a recorrer la lista y una variable entera (numElem) para contabilizar el número de elementos que existen en la lista.
int contar () comienza
p Å cabeza //referencia al primer elemento de la lista numelem Å 0 //se inicializa el número de elementos mientras p <> nil // mientras no se termine la lista comienza
numelem++
p Å p.liga //p avanza al siguiente elemento de la lista termina
contar Å numelem termina
Ejemplo 2: Dividir una lista en dos
Escribir un algoritmo que cree dos listas a partir de una dada, de tal forma que el primero, el tercero, el quinto, etc., elementos pertenezcan a una lista; y el segundo, el cuarto, el sexto, etc., pertenezcan a la otra lista.
Por ejemplo, suponga que se tiene la siguiente lista:
El algoritmo creara dos nuevas listas L1 y L2 como se muestra en la siguiente figura:
Pág. 36 ISC Gregorio García Estrada El algoritmo para resolver este problema surge de manera natural, lo único que hay que tener en cuenta es cuando la lista contiene un número impar de elementos, ya que en este caso no agregaremos ningún elemento a la segunda lista.
void divide (Lista L1, Lista L2) comienza
L1 Å nueva Lista () //se crea la lista vacía L1 L2 Å nueva Lista () //se crea la lista vacía L2 p1 ÅL1.cabeza //p1 es la referencia a L1 p2 Å L2.cabeza //p2 es la referencia a L2 p Å L.cabeza //p es la referencia a L
mientras p <> nil //mientras no se termine de recorrer la lista comienza //se copia el elemento referenciado por p en L1 nuevoNodo Å nuevo Nodo (p.info, nil)
si p1 = nil entonces
L1.cabeza Å nuevoNodo otro
p1.liga Å nuevoNodo p1 Å nuevoNodo
p Å p.liga
//si la lista contiene elementos por copiar se copia el referenciado //por p en L2
si p <> nil entonces comienza
nuevoNodo Å nuevo Nodo(p.info, nil) si p2 = nil entonces
L2.cabeza Å nuevoNodo otro
p2.liga Å nuevoNodo p2 Å NuevoNodo
p Å p.liga termina
termina termina
Note que las partes donde se inserta el elemento a cada una de las listas es igual, excepto que se agrega el elemento en distintas listas, esto sugiere la creación de un método, al cual llamaremos agrega, de tal forma el procedimiento divide, se vería como:
void divide (Lista L1,Lista L2)
Pág. 37 ISC Gregorio García Estrada comienza
L1 Å nueva Lista () //se crea la lista vacía L1 L2 Å nueva Lista () //se crea la lista vacía L2 p1 ÅL1.cabeza //p1 es la referencia a L1 p2 Å L2.cabeza //p2 es la referencia a L2 p Å L.cabeza //p es la referencia a L
mientras p <> nil //mientras no se termine de recorrer la lista comienza
agrega (p,L1,p1) si p <> nil entonces
agrega (p,L2,p2)
termina termina
void agrega (Nodo p, Lista nuevaLista, Nodo colaNL)
//La información que contiene la referencia p es insertada al final de la // lista nuevaLista
comienza
nuevoNodo Å nuevo Nodo(p.info, nil) //se crea un nodo si nuevaLista.vacía () entonces
nuevaLista.cabeza Å nuevoNodo
otro //si la lista no es vacía se liga con Nuevo colaNL.liga Å nuevoNodo
colaNL Å nuevoNodo //el nuevo elemento insertado es ahora
//la cola de Nueva Lista
p Å p.liga //p referencia al siguiente elemento termina
Pág. 38 ISC Gregorio García Estrada
3.3 Listas doblemente ligadas
Cuando se trabaja con listas ligadas el acceso al nodo que se encuentra después de cualquier otro resulta conveniente; pero no así, cuando se trata de accesar al nodo que se encuentra antes de él. Considere los métodos posterior (Nodo p), que devuelve una referencia al nodo que se encuentra después del nodo p, y anterior (Nodo p), que devuelve una referencia al nodo que se encuentra antes del nodo p.
Para acceder a la posición que sigue a p en una lista, basta con acceder a su liga:
Nodo posterior (Nodo p) comienza
si p = nil entonces
error (Lista vacía) otro
posterior Å p.liga termina
Cuando se desea acceder a la posición previa de p es necesario recorrer toda la lista, hasta una posición antes de p, es decir hasta que la liga del elemento actual sea p:
Nodo anterior (Nodo p)
// Supone que el Nodo p existe dentro de la lista comienza
aux Å cabeza ant Å nil
mientras aux <> p comienza
ant Å aux aux Å aux.liga termina
termina
Como puede observarse el trabajo realizado para obtener el nodo anterior es mucho mayor que el trabajo para obtener el nodo posterior, esto se debe a que se tiene una referencia al nodo posterior; siguiendo este mismo esquema podría tenerse otra referencia al nodo anterior. De esta forma un nodo estaría compuesto por tres datos miembros:
• info, para almacenar un objeto
• ligader, referencia al nodo anterior
• ligaizq, referencia al nodo posterior de forma gráfica:
Pág. 39 ISC Gregorio García Estrada En una lista doblemente ligada cada nodo no sólo tiene una referencia al siguiente sino también una referencia al nodo anterior. En forma esquemática una lista doblemente ligada se vería como:
Como puede observarse, la ventaja de las listas doblemente ligadas es el acceso rápido y directo tanto al elemento siguiente como al posterior de una posición determinada de la lista.
Por tanto, conviene utilizarlas cuando se necesita recorrer una lista en ambas direcciones.
Aunque las listas doblemente ligadas utilizan más espacio que una lista ligada, sus operaciones pueden ejecutarse más eficientemente y esto compensa el espacio extra requerido.
La clase nodo para una lista doblemente ligada es:
clase Nodo {
Objeto info Nodo ligader, ligaizq Nodo (Objeto obj) {
info Å obj
ligader Å ligaizq Å nil }
Nodo (Objeto obj, Nodo izq, Nodo der) {
info Å obj ligaizq Å izq ligader Å der }
void escribe ()
//imprime la información del nodo }
Operaciones sobre una lista doblemente ligada en representación ligada
clase ListaDobleLigada {
Nodo cabeza ListaDobleLigada ()
Nodo busca (Objeto x, bool éxito) void inserta (Objeto x)
Pág. 40 ISC Gregorio García Estrada void borra (Objeto x)
} Crear:
El procedimiento para crear una lista doblemente ligada es similar a la función constructora de una lista ligada:
ListaDobleLigada () {
cabeza Å nil }
Vacía:
De forma semejante el método para determinar si una lista doblemente ligada está o no vacía es:
bool vacía ()
//regresa verdadero si la lista se encuentra vacía, en otro caso regresa falso comienza
vacía Å cabeza = nil termina
Buscar:
El algoritmo para buscar un elemento en una lista doblemente ligada es semejante al algoritmo de búsqueda en una lista ligada, pero aprovechando las ventajas que se tienen, cuando el elemento que se busca no se encuentra se regresará la posición del elemento que debería ir antes de él. Por ejemplo, suponga la siguiente lista y que se desea buscar al elemento 3, el cual no forma parte de la lista:
el método regresará una referencia al nodo cuya información es 2.
Para desarrollar este algoritmo considere que la lista doblemente ligada está ordenada en forma creciente y que no existen elementos repetidos.
Nodo busca (Objeto x, var bool éxito)
//busca un elemento x en la lista; en caso de que exista, regresa una //referencia al elemento que lo contiene; en caso contrario, regresa un
Pág. 41 ISC Gregorio García Estrada //referencia al elemento que debería ir antes que él
comienza
si cabeza = nil entonces // la lista es vacía comienza
aux Å nil éxito Å falso termina
otro comienza
aux Å cabeza
mientras aux.ligader <> nil & aux.info < x
aux Å aux.ligader //se avanza un elemento éxito Å aux.info = x
si aux.info > x entonces aux Å aux.ligaizq termina
regresa aux termina
Insertar:
El algoritmo para insertar que será desarrollado no permitirá elementos repetidos y conservará un orden lineal en sus elementos.
El algoritmo para insertar un nodo en una lista doblemente ligada será dividido en los siguientes casos diferentes:
a) La lista en la cual se desea insertar el elemento se encuentra vacía b) El elemento que se desea insertar deberá ser la cabeza de la lista c) El elemento que se desea insertar deberá ser la cola de la lista
d) El elemento que se desea insertar deberá ocupar una posición intermedia
En cada caso, se supondrá que el elemento no se encuentra y que esto fue determinado por el método busca, anteriormente desarrollado, que regresa la posición del nodo que debería ir a la izquierda (llamado ant) del que se desea insertar, esto es:
ant Å busca (x, éxito) si éxito entonces
error (el elemento se encuentra) otro
a or b or c or d
donde a, b, c y d son mutuamente excluyentes.
a) La lista en la que se desea insertar se encuentra vacía En este caso se debe:
Pág. 42 ISC Gregorio García Estrada 0. Determinar que la lista se encuentra vacía
1. Crear un nodo con la información que se desea insertar y cuyas ligas referencien a nil 2. Referenciar la cabeza a este nuevo nodo
Después de insertar el nodo, gráficamente la lista deberá ser:
Las acciones enumeradas anteriormente en forma de pseudocódigo son:
si vacía () entonces comienza
nodoNuevo Å nuevo Nodo (x) cabeza Å nodoNuevo
termina
b) El elemento que se desea insertar deberá ser la cabeza de la lista En este caso se debe:
0. Determinar que debe ser la cabeza de la lista
1. Crear un nodo que contenga al objeto que se desea insertar, su liga izquierda deberá referenciar a nil y su liga derecha a la cabeza actual de la lista
2. La liga izquierda de la cabeza deberá apuntar al nuevo nodo 3. La cabeza deberá referenciar al nuevo elemento
Suponga que se desea insertar el elemento ‘1’ en la siguiente lista: Las acciones anteriores de manera gráfica son:
Se crea un nuevo nodo cuya información sea 1, cuya liga izquierda sea nil y cuya liga derecha referencie a la cabeza de la lista:
Pág. 43 ISC Gregorio García Estrada Se cambia el valor de la liga izquierda de la cabeza para que referencie al nuevo elemento y finalmente se indica que el nuevo elemento es ahora la cabeza de la lista, esto es:
De manera más formal estas acciones son:
otro
si ant = nil entonces comienza
nodoNuevo = nuevo Nodo (x, nil, cabeza) cabeza.ligaIzq Å nodoNuevo
cabeza Å nuevoNodo termina
c) El elemento que se desea insertar deberá ser la cola de la lista En este caso se debe:
0. Determinar que el nuevo elemento deberá ser la cola de la lista
1. Crear un nodo cuya información sea la que se desea insertar, su liga derecha deberá referenciar a nil, ya que después de él no deben existir más elementos, su liga derecha deberá apuntar al nodo anterior, que en este caso es la cola de la lista actual
2. Cambiar la liga derecha del nodo anterior para que ahora referencie al nodo que se desea insertar
Gráficamente:
Suponga que se desea insertar un elemento cuya información sea 7 en la siguiente lista;
después de invocar a busca:
Pág. 44 ISC Gregorio García Estrada Se crea un nodo cuya información es 7 y cuya liga derecha referencie a nil.
La liga derecha del nodo anterior deberá referenciar al nuevo elemento, de lo cual se obtiene la siguiente lista:
En pseudocódigo, las acciones para insertar un elemento como la cola de una lista son:
otro
si ant.ligaDer = nil entonces comienza
nuevoNodo = nuevo Nodo (x, ant, nil) ant.ligaDer ÅnuevoNodo
termina
d) El elemento que se desea insertar deberá ocupar una posición intermedia Este caso es el más general de todos y se deberá:
0. Considerar que no fue ninguno de los casos anteriores
1. Crear un nodo cuya información contenga al objeto que se desea almacenar, su liga izquierda deberá referenciar al nodo anterior y su liga derecha al elemento que se encuentra a la derecha del anterior
2. La liga izquierda del nodo que se encuentra a la derecha del anterior deberá referenciar al nuevo nodo
3. La liga derecha del nodo anterior deberá referenciar al nuevo elemento
A continuación se presenta un ejemplo. Suponga que se desea insertar un 4 en la siguiente lista:
Pág. 45 ISC Gregorio García Estrada Primero se crea un nodo cuya información sea 4, con liga izquierda a anterior y liga derecha a la referencia de la liga derecha del anterior:
Por último, la liga izquierda de la liga derecha de anterior deberá referenciar al nuevo nodo y la liga derecha de anterior a nuevo.
En pseudocódigo:
otro comienza
nuevoNodo Å nuevo Nodo (x, ant, ant.ligaDer) anterior.ligaDer.ligaIzq Å nuevoNodo
anterior.ligaDer Å nuevoNodo termina
Ahora se desarrolla un algoritmo para insertar un elemento en una lista doblemente ligada de manera general, es decir, tomando en cuenta los cuatro casos desarrollados anteriormente; considere que:
I. siempre que el elemento no forma parte de la lista se crea un nodo cuya información es la que se desea insertar y cuya liga izquierda referencia al nodo anterior, determinado por la función busca
II. Los casos a y b son muy parecidos
Pág. 46 ISC Gregorio García Estrada III. Los casos c y d son muy parecidos
void inserta (Objeto x) comienza
ant Å busca (x, éxito) si éxito entonces
error (el elemento ya existe) otro
comienza
nuevoNodo Å nuevo Nodo (x, ant, cabeza) si ant = nil entonces
comienza
si cabeza = nil entonces
cabeza.ligaizq Å nuevoNodo cabeza Å nuevo Nodo ()
termina otro comienza
nuevoNodo.ligaDer Å ant.ligaDer si ant.ligaDer <> nil entonces
ant.ligaDer.ligaIzq Å nuevoNodo ant.ligaDer Å nuevoNodo
termina termina
termina Borrar:
El algoritmo debe considerar que cuando se desea eliminar un elemento de una lista doblemente ligada este puede ser:
a) el único elemento b) la cabeza de la lista c) la cola de la lista d) un nodo intermedio
Para cada uno de estos casos puede suponerse que el elemento forma parte de la lista; lo cual fue determinado a través del método busca de la siguiente forma:
nodo Å busca (x, éxito) si ¬éxito entonces
error (el elemento no se encuentra) otro
a or b or c or d
A continuación se presenta cada caso:
Pág. 47 ISC Gregorio García Estrada a) El elemento que se desea eliminar es el único de la lista
En este caso tanto la liga derecha como la izquierda del nodo referencian a nil y la lista deberá quedar vacía. Así:
si nodo.ligaIzq = nil & nodo.ligaDer = nil entonces
cabeza Å nodo.ligaDer //que es igual a nil b) El elemento que se desea eliminar es la cabeza de la lista
Aquí la lista deberá iniciar en el elemento que se encuentra a la derecha del nodo:
otro
si nodo.ligaIzq = nil entonces comienza
cabeza Å nodo.ligaDer cabeza.ligaIzq Å nil termina
c) El elemento que se desea eliminar es la cola de la lista
En este caso la liga derecha del elemento referenciado por la liga izquierda del elemento que se desea eliminar deberá referenciar a nil, esto es:
otro si nodo.ligaDer = nil entonces nodo.ligaIzq.ligaDer Å nil
d) El elemento que se desea eliminar es un nodo intermedio
En este caso, la liga derecha que se encuentra a la izquierda del nodo que se desea eliminar deberá referenciar al elemento de la derecha del nodo que debe eliminarse y, de forma semejante, la liga izquierda que se encuentra a la derecha del nodo que se desea eliminar deberá referenciar al elemento de la izquierda del nodo en cuestión, es decir:
si nodo.ligaIzq = nil & nodo.ligaDer = nil entonces
cabeza Å nodo.ligaDer //que es igual a nil otro //B
si nodo.ligaIzq = nil entonces comienza
cabeza Å nodo.ligaDer cabeza.ligaIzq Å nil termina
otro si nodo.ligaDer = nil entonces //C nodo.ligaIzq.ligaDer Å nodo.ligaDer otro //D
comienza
nodo.ligaIzq.ligaDer Å nodo.ligaDer nodo.ligaDer.ligaIzq Å nodo.ligaIzq
Pág. 48 ISC Gregorio García Estrada termina
El algoritmo general para eliminar un nodo en una lista doblemente ligada, resulta ser elegantemente simple:
void borra (Objeto x) comienza
nodo Å busca (x, éxito) si ¬éxito entonces
error (el elemento no existe) otro
comienza
si cabeza = nodo entonces cabeza Å nodo.ligaDer si nodo.ligaDer <> nil entonces
nodo.ligaDer.ligaIzq Å nodo.ligaIzq si nodo.ligaIzq <> nil entonces
nodo.ligaIzq.ligaDer Å nodo.ligaDer termina
termina
Ejemplos:
1. Encontrar el i-ésimo nodo
Encontrar el elemento que ocupa la posición i en una lista doblemente ligada:
Nodo encuentra (int i) comienza
p Å cabeza numElem Å 1
mientras numElem < i & p <> nil comienza
p Å p.ligaIzq
numElem ++
termina
si numElem = i & i <> 0 entonces encuentra Å p
otro
encuentra Å nil termina
2. Copiar una lista doblemente ligada
Copiar la información almacenada en una lista doblemente ligada en otra lista doblemente ligada.