Trabajo Final TDA-LISTA Julia de Frutos Svenja Heydorn Eduardo Fernández I.- Definición 1-TDA Un TDA se puede definir como un modelo matemático con una serie de operaciones definidas en ese modelo. Como propiedades fundamentales que proporcionan podemos destacar la generalización y la encapsulación. En general los TDA son los tipos de datos primitivos (enteros, caracteres, ...), al igual que las operaciones para operarlos, que son generalizaciones de operaciones primitivas (suma, resta, etcétera). Cuando dice que encapsula cierto tipo de datos se refiere a que gracias a ellos, es posible localizar la definición del tipo y además, todas las operaciones con ese tipo se pueden localizar en una sección del programa. Por lo tanto, si se desea cambiar la forma de implementar un TDA, se puede realizar sin perder la semántica del mismo y en alguna zona del código muy concreta. Más concretamente, los TDA están representados por estructuras de datos conectadas entre sí, manipulados con operaciones sobre esas estructuras. 2-TDA-lista En general, una lista es una secuencia de elementos de la forma: a1, ... ,an, donde n>=0, y cada elemento ai es de tipo genérico. El tamaño de la lista es n, pero si n=0, se dice que la lista es vacía. Para todas las listas, menos esta última, el primer elemento es a1 y el último elemento es an. Se dice que ai+1 es sucesor de ai (i<n) y que ai-1 es predecesor de ai (i>1). Por lo tanto, los elementos pueden estar ordenados en función de su posición (i en el caso de ai). A diferencia de los conjuntos puede haber elementos repetidos en la lista, y a diferencia de las matrices y registros, el numero de elementos de la lista no es fijo, es variable y por lo tanto, no está limitado desde el principio. En una máquina de información cada elemento suele denominarse nodo (celda o caja) que puede contener uno o más punteros. Las listas admiten una serie de operaciones, que podemos agrupar en operaciones de construcción(crear), operaciones de posicionamiento(fin, primero, siguiente, anterior), operaciones de consulta(vacía, longitud) y finalmente operaciones de modificación(modificar, borrar, insertar), que se detallarán e implementarán más adelante. Los TDA-lista se pueden realizar mediante memoria estática(vectores o arrays), o mediante asignación de memoria dinámica y punteros. Cuando se procesan conjuntos de datos cuyo espacio de almacenamiento no se puede predecir a priori ( en tiempo de compilación) y además la actividad de los mismos (inserciones y borrados) es frecuente, las estructuras de datos estáticas (los arrays) no son adecuadas para su implementación. Las razones son varias: 1 Trabajo Final Julia de Frutos Svenja Heydorn Eduardo Fernández 1. Los arrays deben ser declarados en tamaño en el programa fuente, de modo que si se elige uno mayor que el necesario, entonces se desperdicia espacio de memoria. 2. La operación de añadir datos al final de la lista (el arrays) es un proceso rápido; sin embargo, las inserciones y eliminaciones en el interior del array son lentas y complejas, ya que puede ser necesario desplazar cada elemento del array para hacer espacio al nuevo elemento, o bien cerrar el espacio dejado por una eliminación. Pese a esto, nos podemos encontrar fácilmente con las listas mas sencillas realizadas mediante vectores. En la primera posición encontramos el número de elementos de la lista y en las demás los elementos en sí. Un ejemplo es: 4 l alpha beta gamma delta Las listas son unas estructuras de datos muy útiles para los casos en los que se necesita almacenar información de la que no se conoce su tamaño con antelación. También son valiosas para las situaciones en las que el volumen de datos se puede incrementar o decrementar dinámicamente durante la ejecución del programa, como ya hemos dicho. Por lo tanto, usando punteros y memoria dinámica podemos encontrar las listas simplemente enlazadas, retratadas a continuación. Estas listas poseen nodos o registros que contienen el elemento correspondiente a su posición en la lista y un puntero al siguiente nodo que contiene su sucesor. Es el puntero llamado “siguiente”. Claro está, el puntero siguiente de la última celda apunta a nil. En el caso de existir una celda llamada cabeza o encabezamiento, ésta contiene la longitud de la lista en ese momento y un puntero que apunta al primer nodo. En las listas que hemos visto en clase la cabecera tiene un puntero más que apunta al último nodo. Es importante recordar que el puntero no es más que una variable que contiene la dirección dónde está almacenado otro dato. Existe otro modelo de lista que mejora la eficiencia respecto al modelo anterior. Se trata de las listas doblemente enlazadas. Es de utilidad cuando se quiere recorrer la lista hacia atrás. Hay que añadir un puntero más, en cada celda de modo que se apunta al nodo anterior. 2 Trabajo Final Julia de Frutos Svenja Heydorn Eduardo Fernández Estos nuevos punteros van a duplicar el costo de las inserciones y eliminaciones por el hecho de manejar más punteros, pero por otro lado se simplifican las eliminaciones al no tener que llamar al nodo anterior. Un tipo común y particular de este tipo de listas son las listas enlazadas circularmente. Se pueden hacer con cabecera o sin ella. En ellas, el apuntador anterior del primer nodo apunta al ultimo nodo. Un ejemplo es: Ya hemos apuntado algunas ventajas u inconvenientes con respecto a usar en las implementaciones de listas vectores o listas enlazadas. Añadimos , a continuación algunas en cuanto al coste de las operaciones. Con vectores: Algunas de coste O(1): crear_lista(), borrar_lista(),tamaño(), vacía? (), llena? (),final? (), siguiente(),primero(), ultimo(), contenido() Otras operaciones y sus costes: buscar()-O(n/2) o O(log(n)); recorrer_lista()-O(n); insertar(),eliminar()-O(n + log(n)) Con listas enlazadas: Coste O(1): crear_lista(), borrar_lista(),tamaño(), vacía? (), llena? (),final? (), siguiente(),primero(), ultimo(), contenido() Otras operaciones y sus costes: buscar(), anterior(),insertar(),eliminar()-O(n/2); recorrer_lista()-O(n). 3 Trabajo Final II.- Ejemplos Julia de Frutos Svenja Heydorn Eduardo Fernández En el campo de la informática son numerosos los ejemplos en los que usamos listas enlazadas. A continuación, se muestran dos de ellos. El primero es el llamado polinomio TDA, en el que se define un tipo de dato abstracto para polinomios de una variable, con coeficientes ai. Se trata de almacenar los coeficientes en la lista de modo que se puedan efectuar con facilidad las operaciones suma, multiplicación... Otro ejemplo mas complicado tiene lugar cuando se quiere realizar informes en una universidad por ejemplo de 40000 estudiantes y 2500 cursos, sobre el numero de matriculados en cada clase y las clases en las que está inscrito cada alumno. Necesitamos una lista por clase que contenga cada estudiante y una lista por estudiante que contenga las clases que cursa. Un ejemplo de implementación sería con dos listas combinadas en una. Es decir todas las listas son circulares y a la vez tienen cabecera llamada Ci en función de la clase y Ei en función del alumno. Como ejemplo partimos de la clase C3 y vamos a recorrer su lista. El primer nodo corresponde a un estudiante, si recorremos a su vez esta lista del estudiante hasta llegar a la cabecera conoceremos al estudiante E4. A continuación, regresamos a la lista de clase C3 (dónde habíamos guardado la posición en la que nos habíamos quedado) y pasamos a la siguiente celda, que pertenecerá a E5. Y así podemos continuar de manera que conozcamos en cuantas clases está matriculado cada alumno. 4 Trabajo Final Julia de Frutos Svenja Heydorn Eduardo Fernández III.- Operaciones del TDA lista Aquí se muestran las cabeceras de todas y cada una de las operaciones del TDA lista en C utilizando vectores. 1- Declaración de tipos #define TAM 1000 typedef T elemento; typedef int posicion; typedef struct{ int longitud; elemento info[Tam]; } Lista; typedef int logico; T es el tipo de dato que nosotros tenemos. Puede ser entero, caracter, lógico o cualquier otra estructura que se nos ocurra. 2- Operaciones elementales Independientes de la implementación logico vacia(Lista *L){ } logico llena(Lista *L){ } A la variable tam se le asigna el tamaño de la lista (L.longiutd). Si bien no es necesaria ya que podemos utilizar la función tamaño en su lugar, porque nos devuelve el tamaño de la lista. Vacia nos devuelve cierto si la lista está vacia y falso si no lo está. Llena nos devuelve cierto si la lista está llena y falso si no lo está. La variable de tipo logico es un entero al implementarlo en C. El valor de falso en C es 0 y el de cierto 1. Dependientes de la implementación logico crear_lista(Lista *L){ } No de devuelve cierto si la lista se ha creado correctamente int tamaño(Lista *L){ } Nos devuelve el tamaño de la lista logico final(Lista *L, posicion p){ } Nos devuelve cierto si es el final de la lista 5 Trabajo Final Julia de Frutos Svenja Heydorn Eduardo Fernández logico contenido(Lista *L, posicion p, elemento *E){ } Nos devuelve falso si el elemento está junto con lo que vale el elemento E. Si no está, nos devuelve cierto. logico primero(Lista *L, posicion *p){ } Si la lista no está vacia p vale 1 y la función devuelve falso. logico ultimo(Lista *L, posicion *p){ } Si la lista no está vacia p vale el tamaño de la lista y la función devuelve falso. logico siguiente(Lista *L, posicion p, posicion *q){ } Si la lista no está vacia y p no es el último, q vale una posiciona mas que p. Siguiente devuelve falso. logico anterior(Lista *L, posicion p, posicion *q){ } Si la lista no está vacia y p no es el primero q guarda una posición que p. Siguiente devuelve falso. 3- Operaciones no elementales: Independientes de la implementación void recorrer_lista(Lista *L){ } logico buscar(Lista *L, elemento E, posicion *p){ } Si el elemento E no está p guarda la posición donde debería de estar E y la función devuelve cierto. Dependientes de la implementación logico insertar(Lista *L, elemento E){ } La función devuelve cierto si no hemos podido insertar correctamente el elemento E. logico eliminar(Lista *L, elmento E){ } La función devuelve cierto si no hemos podido eliminar correctamente el elemento E. 6 Trabajo Final IV.- Bibliografía Julia de Frutos Svenja Heydorn Eduardo Fernández Weiss, "Estructuras de datos y Algoritmos", Addison-Wesley Pub. Aho, Hopcroft y Ullman. “Estructuras de datos y Algoritmos”. AddisonWesley, 1998. www.kernel-labs.org www.itlp.edu.mx/publica/tutors.htm www.infor.uva.es/~belar/ 7