Professional Documents
Culture Documents
MÉXICO
Licenciatura En Informática
Estructura
s
de Datos
Autor: L.I. María de Lourdes Isabel
Ponce Vásquez
FEBRERO - JUNIO 2011
2
Contenido
UNIVERSIDAD NACIONAL AUTÓNOMA DE MÉXICO.............................................................1
FACULTAD DE CONTADURÍA Y ADMINISTRACIÓN..............................................................1
Licenciatura En Informática........................................................................................................1
Autor: L.I. María de Lourdes Isabel ............................................................................................1
UNIDAD 3. LISTAS.....................................................................................................................5
Introducción.............................................................................................................................5
Definición.................................................................................................................................5
Representación.......................................................................................................................5
Representación de Listas con Arreglos...............................................................................6
Representación de Listas Dinámicas..................................................................................6
Operaciones............................................................................................................................7
Lista Vacía...........................................................................................................................7
Lista Llena............................................................................................................................8
Agregar un elemento...........................................................................................................8
Eliminar un elemento...........................................................................................................8
Recorrer la lista....................................................................................................................9
Buscar un elemento.............................................................................................................9
Aplicaciones............................................................................................................................9
Representación de polinomios............................................................................................9
Solución de colisiones (hash)............................................................................................10
Lista Doblemente Enlazada..................................................................................................11
Definición...........................................................................................................................11
Representación..................................................................................................................11
Operaciones.......................................................................................................................12
Lista Circulares (Anillo)..........................................................................................................13
Definición...........................................................................................................................13
Representación..................................................................................................................13
Operaciones.......................................................................................................................14
Tablas de Dispersión.............................................................................................................16
Representación .................................................................................................................17
Elementos para una tabla hash.........................................................................................18
Operaciones.......................................................................................................................18
Inserción.............................................................................................................................18
Búsqueda...........................................................................................................................18
Borrar.................................................................................................................................18
Funciones Hash.................................................................................................................19
Hashing por residuo de la división.....................................................................................19
3.1.1.1. Hashing por doblamiento o pliegue......................................................................20
3.1.2. Tratamiento de colisiones........................................................................................21
Encadenamiento................................................................................................................21
Ventajas y desventajas......................................................................................................21
Aplicaciones.......................................................................................................................22
3
4
UNIDAD 3. LISTAS
Introducción
Anteriormente se mostraron los tipos de datos elementales los cuales tienen una
representación y manipulación estándar en la mayoría de los lenguajes de programación,
dentro de este grupo de datos se trabajó con arreglos, registros y apuntadores indicando
cómo funcionan, su representación y sus operaciones. Ahora toca el turno a los tipos de
datos compuestos que son aquellos que requieren la experiencia del programador para
indicar cómo funcionan, cómo se representan y las operaciones que se pueden realizar con
ellos, por supuesto es requisito tener bien claros los conceptos de los tipos de datos
elementales que servirán de base para la implementación de los tipos de datos compuestos.
En esta unidad se revisará el caso especial de las listas donde las operaciones de inserción y
eliminación se realizan sin restricciones, para después continuar con dos tipos particulares
de listas que son las filas y pilas donde estas operaciones tienen ciertas restricciones.
Definición
Una lista (list) es una secuencia de 0 o más elementos llamados generalmente nodos, de un
tipo dado almacenados en memoria. Son estructuras de datos lineales, donde cada elemento
de la lista, excepto el primero, tiene un único predecesor y cada elemento de la lista, excepto
el último, tiene un único sucesor.
El número de elementos de una lista se llama longitud. Si una lista tiene 0 elementos, se
denomina lista vacía.
En una lista se pueden agregar nuevos elementos o suprimir los elementos existentes en
cualquier posición a diferencia de las pilas y filas.
Aunque los datos en una lista pueden ser ingresados en cualquier orden, la facilidad de su
manejo permite mantener los datos ordenados en todo momento, lo cual le da mayor
potencial de uso.
En la vida diaria encontramos varios ejemplos de listas, una lista de pendientes, una lista de
alumnos, una lista de participantes en un evento, etc.
Representación
Las listas no son estructuras de datos básicas, por lo tanto requieren del ingenio del
programador para representarlas. Las listas pueden representarse mediante:
Arreglos (Contiguas)
Listas Ligadas (Enlazadas)
5
Representación de Listas con Arreglos
Para representar las listas con arreglos, se debe definir el tamaño máximo de la lista y una
variable auxiliar llamada LONGITUD, que será el apuntador al último elemento de la lista.
Lista
5 14 2
1 2 3 ... MÁX
LONGITUD
Lista Lista
1 5
2 14 3 2 LONGITUD
LONGITUD 3 2
2 14
1 5
Representación de Listas con Arreglos
Al emplear arreglos para implementar listas se tienen las mismas limitaciones de espacio de
memoria reservada propias de los arreglos; pudiendo provocar un error de desbordamiento
(overflow), así como el subdesbordamiento (underflow) si se intenta extraer un elemento de
una lista vacía. La inserción o eliminación de un elemento, excepto en el frente o al final de la
lista, necesitará recorrer los elementos de la lista una posición.
Las listas implementadas de esta forma son llamadas Listas Enlazadas y pueden ser
elaboradas con un apuntador al primer elemento de la lista o con dos apuntadores, uno al
principio y otro al final de la lista.
PRIMERO
6
ÚLTIMO
Nulo
Representación de Listas Dinámicas
PRIMERO y ÚLTIMO indican la posición del primer y último elementos de la lista
respectivamente, los cuales pueden corresponder, o no, al primer y último elemento
agregado a la lista, ya que las inserciones se pueden realizar en cualquier lugar de ella.
En el caso de no poder obtener una localidad de memoria (NUEVO), se dice que la lista está
llena y se ha alcanzado la posición de overflow.
7
Esta operación auxiliar verifica que existan elementos en la lista, de ser así retorna un valor
FALSO y en caso contrario (si la lista no tiene elementos), retornará VERDADERO. Esta
operación es importante para evitar un error de underflow.
Lista Llena
Esta operación auxiliar verifica que exista espacio en la lista para poder agregar más
elementos, de ser así retorna un valor FALSO y en caso contrario (si la lista no tiene
espacio), retornará VERDADERO. Esta operación es importante para evitar un error de
overflow.
Agregar un elemento
Esta operación ingresa un elemento a la lista siempre y cuando la lista tenga todavía espacio
(ÚLTIMO < > MAX y PRIMERO < > 1 en el caso de arreglos, ó que la operación Reservar()
no retorne NULO para el caso de los apuntadores), después de agregar el elemento en la
lista, se puede dar el caso de tener que modificar el valor de PRIMERO o ÚLTIMO para
considerar el nuevo elemento. Al principio, la lista está vacía por lo que PRIMERO y ÚLTIMO
no apuntan a ningún elemento (PRIMERO = 0 para el caso de arreglos, PRIMERO = NULO
para el caso de los apuntadores).
La mayoría de las veces se necesita mantener ordenada la lista, por lo que muchas de las
inserciones se realizarán en medio de la lista, en el caso de los arreglos, esto exige el
movimiento de varios de sus elementos para conseguir un lugar donde se pueda ingresar el
nuevo valor.
Para recorrer todos los nodos de una lista se comienza con el PRIMERO. En el caso de los
arreglos, sólo es necesario incrementar el valor de un contador hasta llegar al ÚLTIMO
elemento de la lista. Con los apuntadores se toma el valor del campo liga del primer nodo, se
avanza al segundo; a su vez, el campo liga del segundo nos dará acceso al tercero, y así
sucesivamente. En general, la dirección de un nodo, excepto el PRIMERO, está dada por el
campo liga de su predecesor.
Debido a que las listas son estructuras de datos recursivas, pueden manejarse fácilmente
con procesos recursivos.
Buscar un elemento
La operación de búsqueda de un elemento en una lista organizada estáticamente se puede
realizar mediante una búsqueda binaria; si la lista está organizada dinámicamente, la
búsqueda debe ser en modo secuencial. Se deben recorrer los nodos, tomando el campo liga
como acceso al siguiente nodo a visitar. Este proceso se realiza implícitamente en las
operaciones definidas anteriormente de inserción y borrado, si se desea mantener la lista
ordenada.
Aplicaciones
Algunas de las aplicaciones computacionales de las listas más conocidas son:
Representación de polinomios
Resolución de colisiones (hash)
Representación de polinomios
En este caso, las listas se emplean para almacenar los coeficientes diferentes de cero del
polinomio junto al exponente. Por ejemplo:
9
PRIMERO ÚLTIMO
3 4 0.5 3 6 1 -4 0
Nulo
El nodo contiene dos campos de información, COCIENTE y EXPONENTE.
10
Lista Doblemente Enlazada
En las listas simples mostradas anteriormente se pudo observar que el proceso de
eliminación por ÚLTIMO es largo e ineficiente, ya que se deben recorrer todos los nodos
anteriores a él para llegar a su antecesor mediante apuntadores auxiliares y así poder
eliminarlo, para eliminar esta desventaja, y además tener acceso a los elementos en
cualquier orden, existen las listas doblemente enlazadas.
Definición
Una lista doblemente enlazada es una estructura de datos que tiene dos campos de tipo
apuntador, uno que señala al nodo sucesor (siguiente) y otro al antecesor (anterior). Su
recorrido puede realizarse tanto de PRIMERO a ÚLTIMO como de ÚLTIMO a PRIMERO.
Cada nodo en esta lista consta de mínimo un campo de información y otros dos de tipo
apuntador (anterior y siguiente), a su vez, es apuntado por dos nodos, el anterior y el
siguiente de él.
Al incluir los dos apuntadores, se logra un manejo más eficiente de las listas, ya que se
puede conocer desde cualquier nodo cuál es el nodo sucesor y antecesor, cosa que no se
puede hacer en las listas simples.
Para tener un acceso fácil a la información de la lista, se deben tener dos apuntadores, uno
al primer elemento y otro al último.
Representación
Aunque las listas doblemente enlazadas también se pueden representar mediante arreglos
de dos dimensiones, este tipo de representación resulta ser mucho más complejo que su
contraparte, la representación dinámica. Por esto, en este curso, sólo se tratará su
representación mediante apuntadores.
ÚLTIMO
PRIMERO
Nulo Nulo
En este caso, también se requieren de preferencia dos apuntadores, uno al PRIMERO y otro
al ÚLTIMO elemento de la lista, y las inserciones se pueden realizar en cualquier lugar de
ella.
Para agregar un nuevo elemento a una lista doblemente enlazada de manera ordenada se
requiere:
11
Obtener la dirección del nuevo elemento (NUEVO) mediante una variable de
memoria dinámica.
Hacer que el apuntador siguiente del nuevo nodo apunte al siguiente nodo de
acuerdo a la posición donde se va a agregar.
Hacer que el apuntador anterior del nodo siguiente apunte al nuevo nodo.
Hacer que el apuntador siguiente del nodo anterior apunte al nuevo nodo.
Hacer que el apuntador anterior del nuevo nodo apunte al nodo anterior.
Si el nodo agregado es el PRIMERO o el ÚLTIMO se deberá mover el apuntador
correspondiente para indicar que ese es el primero o último de la lista.
En el caso de no poder obtener una localidad de memoria (NUEVO), se dice que la lista
doblemente enlazada está llena y se ha alcanzado la posición de overflow.
12
Lista Circulares (Anillo)
En las listas lineales enlazadas simples no se puede tener acceso mediante un elemento a
cualquier otro elemento antecesor de manera directa, siempre se debe colocar en el primer
elemento de la lista y partir de ahí para alcanzar cualquier otro elemento, en cambio, si en
vez de almacenar NULO en el apuntador del campo siguiente del último elemento se
almacena la posición del primer elemento de la lista, se podrá tener acceso a cualquier
elemento antecesor desde cualquier otro elemento.
Definición
Una lista circular es una estructura de datos en la cual, el elemento anterior al primero es el
último y el siguiente elemento del último es el primero.
Tanto las listas estáticas como las dinámicas y las simples como las dobles, pueden
implementarse mediante listas circulares.
Las listas circulares se logran haciendo que el apuntador siguiente del último elemento
apunte al primero, y en el caso de las listas doblemente enlazadas, el apuntador anterior del
primer elemento apunta al último elemento de la lista.
Al realizar el manejo de esta forma, sólo se requiere un apuntador al primer o de manera más
eficiente, al último elemento de la lista para realizar cualquier operación, ya que mediante él,
se pueden conocer el resto de los elementos.
Las listas circulares tienen las siguientes ventajas con relación a las listas enlazadas simples:
Cada nodo de esta lista puede acceder a cualquier otro nodo. Esto permite recorrer
la lista completa a partir de cualquier nodo, en cambio la lista simple sólo se puede
recorrer totalmente si se encuentra en el primer nodo.
Las operaciones de concatenar y dividir listas son más eficaces en estas listas que
en las enlazadas simples.
Desventaja:
13
Lista
D E A B C
Último Primero
Para agregar un elemento antes de primero, éste se puede agregar aún cuando no
haya espacio a la izquierda de primero siempre y cuando se tenga espacio en la
última posición del arreglo.
Para agregar un elemento después de último, si no hay espacio a la derecha de
último, se moverá último a la primera posición del arreglo.
Por conveniencia, el primer elemento de la lista se coloca al centro del arreglo.
Último
Representación Dinámica de Listas Circulares Simples
En este caso, como se puede notar, no existen apuntadores a nulo, ya que aún cuando sólo
exista un elemento, el último siempre apuntará al primero.
Último
Representación Dinámica de Listas Circulares Dobles
En este caso, el último elemento apunta al primero y el primero apunta al último, tampoco se
tienen nodos que apunten a nulo y cuando es el primer elemento, se apunta a sí mismo dos
veces.
Operaciones
14
Nuevamente, las operaciones a realizar sobre esta estructura son las mismas que en una
lista simple, tomando en cuenta que ahora se debe enlazar el último elemento al primero de
la lista (y el primero al último en el caso de las listas doblemente enlazadas) y no existirán
nodos apuntando a nulo.
15
Tablas de Dispersión
La búsqueda binaria proporciona un medio para reducir el tiempo requerido de búsqueda en
una lista. Este método, sin embargo, exige que los datos estén ordenados. Existe otro
método que puede aumentar la velocidad de búsqueda en el que los datos no necesitan
estar ordenados, este método se conoce como:
El término más usado es el de Hashing. Aunque existen amplios estudios sobre el tema, no
es la intención de esta unidad conocerlo a profundidad, por lo que sólo se tratarán los
conceptos importantes y relacionados con las listas.
El concepto de hashing fue usado inicialmente para manejar el acceso a las tablas de
símbolos en el núcleo, y después, para direccionar la ubicación de los registros en los
primeros dispositivos de almacenamiento de acceso directo. Desde entonces se han
realizado muchos trabajos que han contribuido al desarrollo de muchas técnicas para
instrumentar el concepto.
Un problema con este proceso, es que la función de transformación no puede ser de uno a
uno, las direcciones calculadas pueden no ser todas únicas.
K1 y K2 son claves diferentes pero generan la misma dirección; a esto se le llama colisión.
Dos llaves no iguales (K1 y K2) que se transforman y obtienen la misma dirección relativa,
son llamadas sinónimos, y deben ser procesador para encontrar la dirección adecuada.
16
Mientras más uniforme sea la distribución de las claves, más uniformes serán las direcciones
calculadas. Al elegir una técnica de hashing deben considerarse los siguientes objetivos
principales:
Cada una de las claves debe apuntar a una dirección en el espacio disponible de
almacenamiento.
Las direcciones se deben distribuir de manera que se reduzca al mínimo el
número de sinónimos.
El cálculo no debe ser demasiado complejo computacionalmente.
Cada uno de los elementos ha de tener una clave que identifica de manera única al
elemento. Por ejemplo, el campo número de cuenta de un conjunto de alumnos puede
considerarse un campo clave para organizar la información relativa al alumnado ya que el
número de cuenta es único. Hay una relación única (uno a uno) entre el campo y el registro
alumno. Podemos suponer que no existen, simultáneamente, dos registros con el mismo
número de cuenta.
Representación
Internamente, las tablas de dispersión son un arreglo. Cada una de las posiciones
del arreglo puede contener ninguna, una o varias entradas del diccionario.
Normalmente contendrá una como máximo, lo que permite un acceso rápido a los
elementos, evitando realizar una búsqueda en la mayoría de los casos. Para saber
en qué posición del arreglo se debe buscar o insertar una clave, se utiliza una
función de dispersión. Una función de dispersión relaciona a cada clave con un
valor entero. “Dos claves iguales deben tener el mismo valor de dispersión,
también llamado hash value, pero dos claves distintas pueden tener el mismo
valor de dispersión, lo cual provocaría una colisión”.
..
12
357
821
Las tablas hash se suelen implementar sobre arreglos de una dimensión, aunque se pueden
hacer implementaciones multi-dimensionales basadas en varias claves. Como en el caso de
los arreglos, las tablas hash proveen tiempo constante de búsqueda promedio, sin importar el
número de elementos en la tabla. Sin embargo, en casos particularmente malos el tiempo de
búsqueda puede llegar a ser muy alto, en función del número de elementos.
Comparada con otras estructuras de arreglos asociadas, las tablas hash son más útiles
cuando se almacenan grandes cantidades de información.
17
Con frecuencia, el tamaño del arreglo de las tablas hash es un número primo. Esto se hace
con el objeto de evitar la tendencia de tener divisores comunes con el tamaño de la tabla
hash, lo que provocaría muchas colisiones tras el cálculo. Sin embargo, el uso de una tabla
de tamaño primo no es un sustituto a una buena función hash.
Operaciones
Las operaciones básicas implementadas en las tablas hash son:
Las operaciones auxiliares que se pueden realizar sobre las tablas hash son:
Recorrer (iteración)
Vaciado
Inserción
Búsqueda
Para recuperar los datos, es necesario únicamente conocer la clave del elemento, a la
cual se le aplica la función hash.
El valor obtenido se mapea al espacio de direcciones de la tabla.
o Si el elemento existente en la posición indicada en el paso anterior tiene la
misma clave que la empleada en la búsqueda, entonces es el deseado. Si la
clave es distinta, se debe buscar en la lista de colisiones.
Borrar
18
Para borrar un elemento, se convierte su clave a un número (aplicar función hash).
El resultado de la función se mapea al espacio de direcciones del arreglo que se
emplea como soporte (se obtiene un índice). Y se verifica que exista.
Se elimina de la lista.
Funciones Hash
La eficiencia de una función hash depende de:
Existen muchas técnicas de cálculo de dirección; entre las más comunes se encuentran:
Mientras que el valor calculado real de una dirección relativa, dados tanto un valor de clave
como el divisor, es directo; la elección del divisor apropiado puede no ser tan simple. Existen
varios factores que deben considerarse para seleccionar el divisor:
1. El rango de valores que resultan de la operación "clave módulo divisor", va desde cero
hasta el divisor - 1. Luego, el divisor determina el tamaño del espacio de direcciones
relativas. Si se sabe que el arreglo va a contener por lo menos N registros, entonces
tendremos que hacer que divisor > n, suponiendo que solamente un registro puede
ser almacenado en una dirección relativa dada.
19
2. El divisor deberá seleccionarse de tal forma que la probabilidad de colisión sea
minimizada. Este es un objetivo difícil de alcanzar. Mediante investigaciones se ha
demostrado que los divisores que son números pares tienden a comportase
pobremente, especialmente con los conjuntos de valores de clave que son
predominantemente impares. Otras investigaciones sugieren que el divisor deberá ser
un número primo. Sin embargo, otras sugieren que los divisores no primos trabajan
tan bien como los divisores primos, siempre y cuando los divisores no primos no
contengan ningún factor primo menor de 20. En general, lo más común y adecuado es
elegir el número primo más próximo, pero no mayor que el espacio total de
direcciones.
Max = 5000
Entonces, el divisor será 4999. Usando ese divisor se calculan las siguientes direcciones
correspondientes a los valores de las claves siguientes:
Por ejemplo, considerando el valor de clave: 123459876 para una dirección relativa de 4
dígitos.
a) Se separa la clave:
1 2345 9876
b) Se pliegan:
1
2345
9876
20
c) Se suman:
13221
3221
Existen diversas técnicas de resolución de colisiones, entre las más populares son:
Ventajas y desventajas
Ventajas. El potencial de las tablas hash o dispersas radica en la búsqueda de
elementos; conociendo el campo clave se puede obtener directamente la posición que
ocupa, y por consiguiente, la información asociada a dicha clave.
Desventajas.
o No permiten algoritmos eficientes para acceder a todos los elementos de la
tabla, en su recorrido.
o Aumentar el tamaño del espacio de direccionamiento relativo creado al usar
una de estas funciones, implica cambiar la función hash, para que se refiera a
un espacio mayor y volver a cargar los datos.
21
o Una función hash mal diseñada generará muchas colisiones, lo que la hace
ineficiente.
o Si se reserva espacio para todos los elementos posibles, se consume más
memoria de la necesaria.
El estudio de las tablas hash acarrea el estudio de funciones hash o dispersión, que
mediante expresiones matemáticas permiten obtener direcciones según una clave que es el
argumento de la función.
Aplicaciones
Estas tablas son usadas en múltiples aplicaciones, como los arrays asociativos, criptografía,
procesamiento de datos y firmas digitales, entre otros.
Una buena función de hash es una que experimenta pocas colisiones en el conjunto
esperado de entrada; es decir que se podrán identificar unívocamente las entradas.
Tarea
Leer al menos dos fuentes adicionales sobre los temas vistos en esta unidad y hacer un resumen de esta
unidad (máximo 1 cuartilla de resumen, no olvidar bibliografía y conclusiones -en otra cuartilla).
Adicionalmente, explicar:
1. Qué son los tipos de datos compuestos y los compuestos lineales.
2. Investigar al menos dos métodos adicionales para calcular funciones hash.
3. Investigar al menos dos métodos adicionales de manejo de colisiones. (hacer un algoritmo)
4. Cuáles son las diferencias entre lista estática y dinámica
5. Cuáles son las diferencias entre lista lineal estática y lista circular estática
6. Cuáles son las diferencias entre lista lineal dinámica y lista circular dinámica
7. Cuáles son las diferencias entre lista lineal doblemente ligada y lista circular doblemente ligada
22