Académique Documents
Professionnel Documents
Culture Documents
Ingeniería de Sistemas
Docente:
Ing. Walter A. Carpio
Arequipa, Perú
2010
Universidad Tecnológica del Perú Ingeniería de Sistemas
Algoritmos y Estructuras de Datos II TAD y Listas Enlazadas
1 Preliminares
Uno de los puntos más cruciales asociados con el diseño de un nuevo sistema involucra la
gestión de la complejidad del proceso de diseño. De forma habitual, los buenos diseñadores
emplean alguna forma de abstracción como herramienta para tratar esta complejidad.
Nuestro uso del término abstracción aquí se refiere a la capacidad intelectual de considerar
una entidad aislándola de cualquier ejemplar específico de esa entidad. Por ejemplo, los
diseñadores de hardware que pretenden diseñar una computadora, habitualmente se
preocupan de la funcionalidad de los circuitos integrados que piensan usar; no del
funcionamiento de los transistores que se encuentran en esos circuitos integrados.
El desarrollo de un sistema informático también se ve simplificado en gran medida mediante
el uso de la abstracción en el proceso de diseño. En el diseño de software, esto supone
especificar la funcionalidad del sistema informático en términos generales de “alto nivel”.
Una vez que puede ser demostrado que esta especificación abstracta del sistema es correcta,
es posible añadir más detalle, para, finalmente, conducir a una descripción detallada de
“bajo nivel” del sistema informático en términos que son directamente implementables
usando la sintaxis de un lenguaje de programación dado. En cada paso de este proceso, el
diseñador debe verificar que el detalle adicional añadido al diseño del sistema es correcto.
La ventaja de este enfoque está en que limita la complejidad con la que se debe tratar en
cualquier paso del proceso de diseño. Esto permite al diseñador concentrarse en el conjunto
del diseño del sistema sin tener que enfangarse con detalles de implementación.
Mientras avanza el proceso de diseño comentado más arriba, los distintos tipos de datos
necesarios, así como las operaciones que den ser ejecutadas con estos datos se revelan de
forma manifiesta. En este punto puede ser utilizado un tipo especial de abstracción conocido
como abstracción de datos. Esto involucra una descripción abstracta o lógica, tanto de los
datos requeridos por el sistema informático, como de las operaciones que pueden ser
ejecutadas con esos datos. El uso de la abstracción de datos durante el desarrollo de
software permite al diseñador concentrarse en cómo son usados los datos en el sistema para
resolver el problema que le ocupa, sin tener que preocuparse de cómo los datos son
representados y tratados en la memoria de la computadora.
Pág 2
Universidad Tecnológica del Perú Ingeniería de Sistemas
Algoritmos y Estructuras de Datos II TAD y Listas Enlazadas
objetos. Es importante apreciar que las operaciones que manipulan los objetos están
incluidas en la especificación de un TAD. Por ejemplo, el TAD CONJUNTO puede ser
definido como una colección de elementos que son tratados por operaciones como la unión,
la intersección y la diferencia de conjuntos.
Téngase en cuenta que la especificación de un TAD no conlleva ninguna consideración de
implementación. La implementación de un TAD supone una traducción de la especificación
del TAD en la sintaxis de un lenguaje de programación particular. Esta traducción se
compone de las declaraciones de variable apropiadas, que sean necesarias para definir los
datos, y un procedimiento o rutina de acceso que implemente cada una de las operaciones
requeridas por el TAD.
En este punto es útil distinguir entre TADs, tipos de datos y estructuras de datos. El término
tipo de datos se refiere a la implementación del modelo matemático especificado por un
TAD. Esto es, un tipo de datos es una representación informática de un TAD. El término
estructura de datos se refiere a una colección de variables en memoria que están
relacionadas de alguna forma específica. Este texto trata del uso de estructuras de datos para
implementar distintos tipos de datos de la forma más eficiente posible.
Un lenguaje de programación habitualmente ofrece cierto número de tipos de datos
predefinidos. Por ejemplo, el tipo de datos int disponible en el lenguaje de programación
C ofrece una implementación del concepto matemático de número entero. Esto es, el TAD
entero define el conjunto de números dado por la unión del conjunto {-l, -2, … -∞) y el
conjunto {0, 1, 2, …, ∞}. El TAD ENTERO también especifica las operaciones que pueden
ser realizadas con números enteros. Estas operaciones incluyen la suma, la resta, la
multiplicación y la división enteras, junto con cierto número de otras operaciones. La
especificación del TAD ENTERO no incluye ninguna indicación de cómo el tipo de datos
debería ser implementado. Por ejemplo, es imposible representar todos los números enteros
en la memoria de una computadora; sin embar go, el rango de números que serán
representados debe ser determinado en la implementación del tipo de datos.
Adicionalmente, un formato para el almacenamiento de los números enteros en la memoria
de la computadora debe ser seleccionado cuando se implemente el TAD ENTERO. El
formato escogido podría ser complemento a uno, complemento a dos, signo-cantidad,
decimal codificado en binario o algún otro formato. Estos son sólo unos pocos de los
aspectos que deben ser considerados al implementar el TAD ENTERO. Nótese, sin embargo,
que estos detalles son ignorados en la especificación del TAD. Además, los programadores
generalmente no tienen que preocuparse con estas consideraciones de implementación
cuando usan el tipo de datos en un programa.
En muchos casos, el diseño de un programa informático requerirá tipos de datos que no se
encuentran disponibles en el lenguaje de programación usado para implementar el
programa. En estos casos, debemos ser capaces de construir los tipos de datos necesarios
usando los tipos de datos predefinidos. Como veremos, esto a menudo supone la
construcción de estructuras de datos realmente complicadas. Los tipos de datos construidos
de esta manera se denominan tipos de datos definidos por el usuario. Nuestro estudio de
Los tipos de datos se centrará en los tipos de datos definidos por el usuario. Con esto en
mente, consideraremos el desarrollo de nuevos tipos de datos desde dos puntos de vista:
visión lógica y visión de implementación.
La visión lógica de un tipo de datos debería ser usada durante el diseño del programa. Este
es simplemente el modelo aportado por la especificación de un TAD. La visión de
implementación de un tipo de datos considera la manera en la que los datos son
representados en la memoria, y cómo las funciones de acceso son implementadas. Desde
Pág 3
Universidad Tecnológica del Perú Ingeniería de Sistemas
Algoritmos y Estructuras de Datos II TAD y Listas Enlazadas
este punto de vista, nos preocupará principalmente de qué manera las distintas estructuras de
datos e implementaciones de rutinas de acceso afectan a la eficiencia de las operaciones
realizadas por el tipo de datos. Sólo debería haber una visión lógica de un tipo de datos; sin
embargo, pueden existir distintos enfoques para implementarlo.
Un programa de aplicación debería manipular un tipo de datos en función de su
representación lógica más que de su almacenamiento físico. Es decir, la comunicación entre
el programa de aplicación y la implementación del tipo de datos debería producirse
solamente a través de la interfaz que aportan las rutinas de acceso especificadas en el TAD.
Esto significa que, mientras la interfaz que ofrece el tipo de datos no sea cambiada, la
implementación del tipo de datos podría ser completamente alterada sin afectar al programa
de aplicación que usa el tipo de datos.
Conviene pensar en esto como en una muralla que separa el programa de aplicación de la
implementación del tipo de datos. La única comunicación posible hacia el tipo de datos es a
través de los parámetros de las rutinas de acceso. El único resultado de vuelta hacia el
programa de aplicación es a través de la información que devuelven las rutinas de acceso.
Sin usar las rutinas de acceso adecuadas, es imposible penetrar la muralla que la
implementación de un tipo de datos. Esta situación se ilustra Figura 1.1.
Pág 4
Universidad Tecnológica del Perú Ingeniería de Sistemas
Algoritmos y Estructuras de Datos II TAD y Listas Enlazadas
directamente al tipo de datos, burlando de este modo las rutinas de acceso, entonces la
modificación de las estructuras de datos usadas para implementar el tipo de datos podrían
invalidar enteramente el programa de aplicación. Esto hace al programa de aplicación
extremadamente difícil de mantener porque la implementación del tipo de datos debe ser
tenida en cuenta cada vez que el programa de aplicación es modificado.
Otra ventaja ofrecida por la encapsulación de datos se refiere a la reutilización. Un tipo de
datos debidamente encapsulado puede ser fácilmente reutilizado en otros programas de
aplicación que requieran la funcionalidad aportada por el tipo de datos, sin tener que
conocer cómo fue implementado el tipo de datos.
Pág 5
Universidad Tecnológica del Perú Ingeniería de Sistemas
Algoritmos y Estructuras de Datos II TAD y Listas Enlazadas
7. Sucesor(S, k). Devuelve el elemento S que tiene el menor valor de clave que es mayor
que k, o el valor nulo si no existe tal elemento.
Además, cuando consideremos el TAD CONJUNTO DINÁMICO asumiremos que las
siguientes operaciones están disponibles:
1. Vacío(S). Devuelve un valor booleano, con verdadero indicando que S es un conjunto
dinámico vacío y falso indicando que S no lo es.
2. HacerVacío(S). Limpia S de todos los elementos, haciendo que S se convierta en un
conjunto dinámico vacío.
Dado que estas dos últimas operaciones son a menudo triviales de implementar,
generalmente no las consideraremos en nuestro análisis.
En muchas situaciones una aplicación sólo requerirá el uso de unas pocas de las operaciones
de conjunto dinámico. De hecho, algunos grupos de estas operaciones son utilizados tan
frecuentemente que se les dan nombres especiales. Por ejemplo, el TAD que suministra las
operaciones Buscar, Insertar y Eliminar se denomina TAD DICCIONARIO. También
presentaremos los TADs PILA, COLA y COLA DE PRIORIDAD, que son tipos especiales
de conjuntos dinámicos.
En nuestro análisis se demostrará que no hay una estructura de datos óptima para la
implementación de los conjuntos dinámicos. Por el contrario, la mejor elección de
implementación dependerá de qué operaciones se necesita soportar, la frecuencia con la que
las operaciones concretas son utilizadas y posiblemente muchos otros factores. Como
siempre, cuanto más conozcamos acerca de cómo una aplicación concreta utilizará la
información, mejor podremos ajustar las estructuras de datos asociadas para que esta
información pueda ser accedida eficientemente.
3 Listas
Las listas son una de las estructuras de datos más fundamentales de entre las empleadas para
almacenar una colección de elementos. De hecho, las listas se utilizan tan frecuentemente
que tiene sentido definir un TAD LISTA. La importancia del TAD LISTA reside en que
puede usarse para implementar una amplia variedad de otros TADs. Es decir, el TAD
LISTA sirve a menudo como una pieza básica en la construcción de TADs más
complicados. Vamos a definir el TAD LISTA. Demostraremos entonces cómo se pueden
usar las listas para implementar el TAD CONJUNTO DINÁMICO, así como cierto número
de otros TADs.
3.1 EL TAD LISTA
Una lista puede definirse como una n-tupla dinámica ordenada:
Pág 6
Universidad Tecnológica del Perú Ingeniería de Sistemas
Algoritmos y Estructuras de Datos II TAD y Listas Enlazadas
No impondremos ninguna restricción en el tipo de los elementos que una lista puede
almacenar. Si todos los elementos almacenados en una lista son del mismo tipo, entonces se
dice que la lista es homogénea. Sin embargo, si distintos tipos de elementos están
almacenados en la lista (ej: un elemento es entero y otro es un número real), entonces la lista
se dice que es heterogénea.
A continuación se ofrecen las operaciones que definiremos para acceder a los elementos de
las listas. Para cada una de estas operaciones, L representa una lista concreta. También se
asume que una lista tiene una variable de posición actual que se refiere a cierto elemento de
la lista. Esta variable puede usarse para iterar sobre los elementos de una lista.
1. Insertar (L, x, i). Añade el elemento x a L en la posición i, haciendo que los elementos li,
li+1, … ,ln pasen a ser los elementos li+1, li+2, … ,ln+1 y que la longitud de la lista sea n + 1.
Si esta operación tiene éxito, se devuelve el valor booleano verdadero; en otro caso se
devuelve el valor booleano falso.
2. Añadir (L, x). Añade el elemento x a la cola de L, haciendo que la longitud de la lista sea
n + 1. Si esta operación tiene éxito, se devuelve el valor booleano verdadero; en otro
caso, se devuelve el valor booleano falso.
3. Obtener (L, i). Devuelve el elemento almacenado en la posición i de L, o el valor nulo si
la posición i no existe.
4. Eliminar (L, i). Elimina el elemento almacenado en la posición i de L, haciendo que los
elementos li+1, li+2, … ,ln pasen a ser los elementos li, li+1, … ,ln-1 y que la longitud de la
lista sea n-1. Si esta operación tiene éxito, se devuelve el valor booleano verdadero; en
otro caso, se devuelve el valor booleano falso.
5. Longitud (L). Devuelve |L|, la longitud de L.
6. Inicio (L). Sitúa la posición actual de L a la cabeza (posición primera) y devuelve el valor
1. Si la lista es vacía, se devuelve el valor 0.
7. Actual (L). Devuelve la posición actual de L.
8. Siguiente (L). Incrementa la posición actual de L y devuelve su valor. Esto es, si la
posición actual es i, la posición actual pasa a ser i + 1, y se devuelve i + l.
Téngase en cuenta que sólo las operaciones Insertar, Añadir, Eliminar, Inicio y Siguiente
modifican las listas a las que son aplicadas. El resto de las operaciones simplemente
consultan las listas con el fin de obtener información acerca de ellas. Desde luego, éstas son
sólo un pequeño número de las operaciones que pueden definirse sobre listas. No obstante,
las encontraremos bastante útiles cuando construyamos otros TADs que estén basados en el
TAD LISTA.
3.2 LISTAS ENLAZADAS
Otro planteamiento para implementar el TAD LISTA emplea una lista enlazada, que asigna
memoria para almacenar los elementos de la lista conforme se necesita durante la ejecución,
y que conecta los elementos de la lista usando punteros. La memoria es, por tanto,
desasignada cuando ya no se necesita más un elemento de la lista. Esquemáticamente, una
lista enlazada se representa por una secuencia de nodos conectados por enlaces, tal y como
se ilustra en la Figura 5.8 (a). Debido a que cada nodo de la lista está conectado al siguiente
por un solo enlace, esta estructura de datos se llama lista simplemente enlazada. Un nodo
en una lista simplemente enlazada contiene dos campos: datos, que contiene un elemento de
la lista, y siguiente, que almacena un enlace (ej: puntero) al siguiente nodo de la lista.
Téngase en cuenta que tenemos el acceso a la propia lista por medio de un puntero a la
cabeza de la lista. Como se muestra en la Figura 5.8 (a), el campo siguiente del último nodo
Pág 7
Universidad Tecnológica del Perú Ingeniería de Sistemas
Algoritmos y Estructuras de Datos II TAD y Listas Enlazadas
de una lista simplemente enlazada contiene un símbolo especial que indica el final de la
lista. Asumiremos que éste es el puntero nulo.
También puede utilizarse una estructura de datos más sofisticada conocida como lista
doblemente enlazada para implementar el TAD LISTA. Cada nodo en una lista doblemente
enlazada contiene tres campos —como muestra la Figura 5.8. (b), un campo almacena un
elemento de la lista y los otros dos almacenan enlaces a los nodos precedente y siguiente de
la lista. Obsérvese que en este caso se usan punteros nulos para marcar ambos extremos de
la lista.
También es posible mejorar estas estructuras de datos con distintas características útiles. Por
ejemplo, en una lista enlazada circular en lugar de colocar el puntero nulo en el campo
siguiente del nodo cola, almacenamos un puntero a la cabeza de la lista (para listas
doblemente enlazada, también necesitaríamos almacenar un puntero al nodo cola en el
campo anterior del nodo cabeza).
A continuación consideramos la eficiencia de las operaciones del TAD LISTA asumiendo
el empleo de estructuras de datos formadas por listas simplemente enlazadas y dobles.
3.2.1 Operaciones de las listas
El primer tema que hay que observar acerca de las listas enlazadas es que ya no tenemos
acceso directo a un elemento arbitrario de la lista. Para acceder al i-ésimo elemento,
debemos recorrer la lista, comenzando por la cabeza, hasta llegar al elemento deseado. Así,
cualquier operación del TAD LISTA que conlleve una posición en la lista (ej: Insertar,
Obtener y Eliminar) recorrerá en el peor caso toda la lista para encontrar la posición
deseada en la lista enlazada. En la Figura a continuación se demuestra cómo se llevan a cabo
las operaciones Insertar y Eliminar en una lista doblemente enlazada. La sección (a)
muestra la configuración inicial de la lista doblemente enlazada. En la sección (b), se realiza
una inserción la posición i. El nodo de la posición i antes de la inserción se sitúa en la
posición i + 1 después de la inserción. En la sección (c), se elimina el nodo de la posición i.
Así, el nodo de la posición i + 1 antes de la eliminación se sitúa ahora en la posición i. Para
ambas operaciones, una vez que tenemos la dirección del nodo de la posición i, somos
capaces de desplazarnos hacia adelante (usando el puntero contenido en el campo siguiente)
o hacia atrás (usando el puntero contenido en el campo anterior) a otros nodos de la lista con
el fin de llevar a cabo las manipulaciones de punteros necesarias.
Pág 8
Universidad Tecnológica del Perú Ingeniería de Sistemas
Algoritmos y Estructuras de Datos II TAD y Listas Enlazadas
En las listas simplemente enlazadas no es posible desplazarse hacia atrás a otros nodos de la
lista. Esto hace que sea difícil realizar las reasignaciones de punteros necesarias durante las
operaciones Insertar o Eliminar. Por ejemplo en la Figura 5.10 (a) mostramos una lista
simplemente enlazada en la cual queremos insertar un nodo en la posición i. Si tenemos
acceso al nodo de la posición i - 1, entonces es cosa sencilla «empalmar» el nuevo nodo en
la lista, como muestra la Figura 5.10 (b). Similarmente, eliminar un nodo de una lista
simplemente enlazada requiere que modifiquemos el campo siguiente del nodo de la
posición i - l cuando «recortamos» el nodo apropiado de la lista. De este modo, con listas
simplemente enlazadas, es necesario controlar el nodo anterior de la lista conforme se
realiza el recorrido asociado con una inserción o una eliminación.
Pág 9
Universidad Tecnológica del Perú Ingeniería de Sistemas
Algoritmos y Estructuras de Datos II TAD y Listas Enlazadas
El siguiente seudocódigo detalla cómo determinar la dirección del nodo de la posición i -1:
4 Bibliografía
[GREG97] Gregory Heileman; Libro: Estructuras de Datos, Algoritmos y POO; Mc
Graw Hill; 1997.
Pág 10