Listas Lineales
Transcripción
Listas Lineales
Listas Lineales Estructura de Datos 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. 1 ISC Gregorio García Estrada Listas Lineales Estructura de Datos 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. 2 ISC Gregorio García Estrada Listas Lineales Estructura de Datos 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. 3 ISC Gregorio García Estrada Listas Lineales Estructura de Datos 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. 4 ISC Gregorio García Estrada Listas Lineales Estructura de Datos 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. 5 ISC Gregorio García Estrada Listas Lineales Estructura de Datos /** * 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. 6 ISC Gregorio García Estrada Listas Lineales Estructura de Datos */ 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. 7 ISC Gregorio García Estrada Listas Lineales Estructura de Datos La salida producida es: Pág. 8 ISC Gregorio García Estrada Listas Lineales Estructura de Datos 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. 9 ISC Gregorio García Estrada Listas Lineales Estructura de Datos 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 a) b) Almacenamiento matriz [2][3] Å 3 tablero [3][1] Å verdadero c) juego [1][8] Å nuevo ficha () Recuperación int x Å matriz [4][4] si tablero [2][2] entonces e1 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. 10 ISC Gregorio García Estrada Listas Lineales Estructura de Datos La suma de dos matrices es la matriz obtenida de sumar pares correspondientes de elemento, de tal forma que si: ⎛a ⎜ 11 ⎜a A = ⎜ 21 ⎜ ... ⎜a ⎝ n1 a ... a ⎞ 12 1n ⎟ a ... a ⎟ 22 2n ⎟ ⎟ a ... a ⎟ n2 nn ⎠ y b ... b ⎞ ⎛b 21 1n ⎟ ⎜ 11 b ... b ⎟ ⎜b 22 2n ⎟ B = ⎜ 21 ... ⎜ ⎟ ⎜b b ... b ⎟ n2 nn ⎠ ⎝ n1 entonces a +b ... a + b ⎞ ⎛a +b 11 12 12 1n 1n ⎟ ⎜ 11 ⎜ a 21 + b 21 a22 + b 22 ... a 2n + b 2n ⎟ A +B = ⎜ ⎟ ... ⎜ ⎟ ⎜a + b a +b ... a + b ⎟ ⎝ n1 n1 n2 n2 nn nn ⎠ La diferencia A-B, es la matriz obtenida de restar los elementos de B de los elementos correspondientes de A: ⎛ a11 − b11 ⎜ ⎜ a − b 21 A − B = ⎜ 21 ... ⎜ ⎜a − b n1 ⎝ n1 a12 − b12 a 22 − b 22 a n2 − b n2 ... a1n − b1n ⎞ ⎟ ... a 2n − b 2n ⎟ ⎟ ⎟ ... a nn − b nn ⎟⎠ 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. 11 ISC Gregorio García Estrada Listas Lineales Estructura de Datos { 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. 12 ISC Gregorio García Estrada Listas Lineales Estructura de Datos Pág. 13 ISC Gregorio García Estrada Listas Lineales Estructura de Datos 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. 14 ISC Gregorio García Estrada Listas Lineales Estructura de Datos 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. 15 ISC Gregorio García Estrada Listas Lineales Estructura de Datos 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. 16 ISC Gregorio García Estrada Listas Lineales Estructura de Datos 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 lista () bool vacía () void inserta (Objeto x) Descripción Crea una lista vacía. Determina si existen o no elementos en la lista 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 Determina la posición donde se encuentra el elemento x exito) 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. 17 ISC Gregorio García Estrada Listas Lineales Estructura de Datos posición p de la lista. Determina la posición donde se encuentra el último elemento de la lista. int cola () 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 real E1, E2, E3 Cad prom } //Nombre del alumno //Calificación de 3 exámenes //Calificación numérica del promedio de exámenes de esta forma, la lista se vería como: Pág. 18 ISC Gregorio García Estrada Listas Lineales Estructura de Datos 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. 19 ISC Gregorio García Estrada Listas Lineales Estructura de Datos 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. 20 ISC Gregorio García Estrada Listas Lineales Estructura de Datos 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. 21 ISC Gregorio García Estrada Listas Lineales Estructura de Datos 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. 22 ISC Gregorio García Estrada Listas Lineales Estructura de Datos 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. 23 ISC Gregorio García Estrada Listas Lineales Estructura de Datos 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. 24 ISC Gregorio García Estrada Listas Lineales Estructura de Datos } 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. 25 ISC Gregorio García Estrada Listas Lineales Estructura de Datos 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) b) c) d) La lista en la cual se desea insertar un elemento se encuentra vacía. El elemento que se desea insertar, deberá ser la cabeza de la lista. El elemento que se desea insertar, deberá ser la cola de la lista. 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. 26 ISC Gregorio García Estrada Listas Lineales Estructura de Datos 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. 27 ISC Gregorio García Estrada Listas Lineales Estructura de Datos 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. 28 ISC Gregorio García Estrada Listas Lineales Estructura de Datos 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. 29 ISC Gregorio García Estrada Listas Lineales Estructura de Datos Nodo cola () comienza aux Å cabeza mientras aux.liga <> nil aux Å aux.liga regresa aux termina // se recorre la lista 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. 30 ISC Gregorio García Estrada Listas Lineales Estructura de Datos 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. 31 ISC Gregorio García Estrada Listas Lineales Estructura de Datos 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. 32 ISC Gregorio García Estrada Listas Lineales Estructura de Datos 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 termina // equivalente a nodo.liga 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. 33 ISC Gregorio García Estrada Listas Lineales Estructura de Datos 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. 34 ISC Gregorio García Estrada Listas Lineales Estructura de Datos 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 numelem Å 0 mientras p <> nil comienza numelem++ p Å p.liga termina contar Å numelem termina //referencia al primer elemento de la lista //se inicializa el número de elementos // mientras no se termine la lista //p avanza al siguiente elemento de la lista 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. 35 ISC Gregorio García Estrada Listas Lineales Estructura de Datos 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. 36 ISC Gregorio García Estrada Listas Lineales Estructura de Datos 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. 37 ISC Gregorio García Estrada Listas Lineales Estructura de Datos 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. 38 ISC Gregorio García Estrada Listas Lineales Estructura de Datos 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. 39 ISC Gregorio García Estrada Listas Lineales Estructura de Datos } 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. 40 ISC Gregorio García Estrada Listas Lineales Estructura de Datos //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) b) c) d) La lista en la cual se desea insertar el elemento se encuentra vacía El elemento que se desea insertar deberá ser la cabeza de la lista El elemento que se desea insertar deberá ser la cola de la lista 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. 41 ISC Gregorio García Estrada Listas Lineales Estructura de Datos 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. 42 ISC Gregorio García Estrada Listas Lineales Estructura de Datos 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. 43 ISC Gregorio García Estrada Listas Lineales Estructura de Datos 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. 44 ISC Gregorio García Estrada Listas Lineales Estructura de Datos 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. II. Pág. 45 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 Los casos a y b son muy parecidos ISC Gregorio García Estrada Listas Lineales Estructura de Datos 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) b) c) d) el único elemento la cabeza de la lista la cola de la lista 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. 46 ISC Gregorio García Estrada Listas Lineales Estructura de Datos 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. 47 ISC Gregorio García Estrada Listas Lineales Estructura de Datos 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. Pág. 48 ISC Gregorio García Estrada Listas Lineales Estructura de Datos Lista clone () //crea una copia de la lista comienza copiaL Å nueva Lista () //se construye una nueva lista vacía refL Å cabeza //referencia a this refCopiaL Å copiaL.cabeza mientras refL <> nil //mientras no se recorra toda la lista comienza nuevoNodo Å nuevo Nodo (refL.info, refCopiaL, nil) si refCopiaL = nil entonces copiaL.cabeza Å nuevoNodo nuevoNodo.ligaIzq Å refCopiaL refCopiaL Å nuevoNodo refL Å refL.ligaDer termina termina 3. Multiplicación de polinomios Recuerde con un ejemplo cómo se realiza la multiplicación de polinomios 6x6 7 18x8 18x8 6x6 6x 7 + 6x 6 + 6x + 24x5 + 24x5 + 8x4 + 12x4 + 20x4 + 8x3 X + 8x3 + 4x3 + 12x4 + 18x4 + 4x2 3x2 + 4x2 + 2x2 + 3x2 + 9x2 + 2x +x + 2x +x +1 +1 +1 + 3x +1 Como puede observarse, para realizar la multiplicación de dos polinomios, se toma el primer término de uno de los polinomios y se multiplica por todos los términos del otro polinomio, después se toma el segundo término del primer polinomio y se multiplica nuevamente por todos los términos del segundo polinomio y así sucesivamente hasta que se llega al último término del primer polinomio, el cual nuevamente se multiplica por cada uno de los términos del segundo polinimio. Del ejemplo puede verse que se obtiene un nuevo polinomio cada vez que se multiplica un nuevo término. Un polinomio puede representarse a través de una lista. Ejercicios Escribir un algoritmo para: 1. 2. 3. 4. Intercalar dos listas doblemente ligadas ordenadas Ordenar una lista ligada desordenada en forma creciente Ordenar una lista doblemente ligada desordenada en forma creciente Copiar una lista doblemente ligada Pág. 49 ISC Gregorio García Estrada Listas Lineales Estructura de Datos 5. Dividir una lista doblemente ligada en dos 6. Restar dos polinomios almacenados en listas ligadas 7. Intercambiar los elementos de las posiciones p y siguiente de p en una lista: a) ligada b) doblemente ligada 8. Suprimir todas las apariciones del elemento x en una lista: a) ligada b) doblemente ligada 9. Escribir una lista ligada en orden ascendente 10. Escribir una lista ligada en orden descendente 11. Borrar el mayor valor de una lista desordenada de números enteros 12. Determinar si dos listas son iguales 13. Intercalar dos listas ordenadas dejando el resultado en una tercera lista 14. Intercalar dos listas ordenadas dejando el resultado en una de ellas 15. De al menos dos aplicaciones en las que es conveniente usar listas ligadas 16. De al menos dos aplicaciones en las que es conveniente usar listas doblemente ligadas Pág. 50 ISC Gregorio García Estrada