Estructuras de Datos Unidad 03

29
UNIVERSIDAD NACIONAL AUTÓNOMA DE MÉXICO FACULTAD DE CONTADURÍA Y ADMINISTRACIÓN Licenciatura En Informática Estructu ras de Datos

Transcript of Estructuras de Datos Unidad 03

Page 1: Estructuras de Datos Unidad 03

UNIVERSIDAD NACIONAL AUTÓNOMA DE MÉXICO

FACULTAD DE CONTADURÍA Y ADMINISTRACIÓN

Licenciatura En Informática

Estructuras

de Datos

Page 2: Estructuras de Datos Unidad 03

Autor: L.I. María de Lourdes Isabel Ponce Vásquez

FEBRERO - JUNIO 2011

2

Page 3: Estructuras de Datos Unidad 03

Contenido

UNIDAD 3. LISTAS...................................................................................................................33.1. Introducción....................................................................................................................33.2. Definición........................................................................................................................33.3. Representación...............................................................................................................3

3.3.1. Representación de Listas con Arreglos....................................................................43.3.2. Representación de Listas Dinámicas.......................................................................4

3.4. Operaciones...................................................................................................................53.4.1. Lista Vacía...............................................................................................................63.4.2. Lista Llena................................................................................................................63.4.3. Agregar un elemento................................................................................................63.4.4. Eliminar un elemento...............................................................................................63.4.5. Recorrer la lista........................................................................................................73.4.6. Buscar un elemento.................................................................................................7

3.5. Aplicaciones....................................................................................................................73.5.1. Representación de polinomios.................................................................................73.5.2. Solución de colisiones (hash)..................................................................................8

3.6. Lista Doblemente Enlazada............................................................................................93.6.1. Definición.................................................................................................................93.6.2. Representación........................................................................................................93.6.3. Operaciones...........................................................................................................10

3.7. Lista Circulares (Anillo).................................................................................................113.7.1. Definición...............................................................................................................113.7.2. Representación......................................................................................................113.7.3. Operaciones...........................................................................................................13

3.8. Tablas de Dispersión....................................................................................................143.8.1. Representación......................................................................................................153.8.2. Elementos para una tabla hash..............................................................................163.8.3. Operaciones...........................................................................................................163.8.3.1. Inserción..............................................................................................................163.8.3.2. Búsqueda............................................................................................................163.8.3.3. Borrar..................................................................................................................173.8.4. Funciones Hash.....................................................................................................173.8.4.1. Hashing por residuo de la división......................................................................173.8.4.2. Hashing por doblamiento o pliegue.....................................................................183.8.5. Tratamiento de colisiones......................................................................................193.8.5.1. Encadenamiento.................................................................................................193.8.6. Ventajas y desventajas..........................................................................................193.8.7. Aplicaciones...........................................................................................................20

3

Page 4: Estructuras de Datos Unidad 03

4

Page 5: Estructuras de Datos Unidad 03

UNIDAD 3. LISTAS

3.1. 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.

3.2. 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.

3.3. 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

Page 6: Estructuras de Datos Unidad 03

3.3.1. 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

Lista Lista

1 5

2 14 3 2

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 implementas con arreglos son llamadas Listas Contiguas.

3.3.2. Representación de Listas Dinámicas

La mejor forma de representar listas es precisamente mediante apuntadores, este tipo de implementación proporciona mayor flexibilidad que las listas contiguas, ya que no es necesario el desplazamiento de elementos cuando se realizan inserciones o eliminaciones.

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.

6

LONGITUD

LONGITUD

LONGITUD

PRIMERO

Page 7: Estructuras de Datos Unidad 03

Representación de Listas DinámicasPRIMERO 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.

Para agregar un nuevo elemento a una lista ordenada se requiere:

Localizar la posición donde se desea agregar. Obtener la dirección del nuevo elemento (NUEVO) mediante una variable de

memoria dinámica. Hacer que el apuntador del nuevo nodo apunte al siguiente nodo de acuerdo a la

posición donde se va a agregar. Hacer que el nodo anterior apunte al nuevo nodo. 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 está llena y se ha alcanzado la posición de overflow.

Para retirar un elemento de la lista se requiere:

Localizar el elemento a retirar, si no se encuentra, se emitirá un mensaje que indique que no se pudo localizar el dato.

Guardar la posición de memoria a la que apunta el nodo a borrar. Hacer que la liga siguiente del nodo anterior al que se desea borrar, apunte al

siguiente nodo después del que se está borrando. Si el nodo borrado es el primero o último, se deberá mover el apuntador

correspondiente. Retornar la posición de memoria como disponible.

3.4. Operaciones

Algunas de las operaciones básicas que pueden realizarse sobre las listas son:

Agregar un elemento (al principio, al final o en medio de la lista) Eliminar un elemento (al principio, al final o en medio de la lista) Recorrer la lista Buscar un elemento

Y las operaciones auxiliares:

Verificar Lista Vacía Verificar Lista Llena

3.4.1. Lista Vacía

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.

7

NuloÚLTIMO

Page 8: Estructuras de Datos Unidad 03

3.4.2. 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.

3.4.3. 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.

Existen cuatro casos a considerar al momento de insertar en una lista:

Insertar en una lista vacía Insertar al principio de la lista Insertar al final de la lista Insertar en medio (antes / después de un elemento)

3.4.4. Eliminar un elemento

Esta operación extrae un elemento de la lista siempre y cuando la lista no esté vacía (PRIMERO < > 0 en el caso de arreglos, PRIMERO < > NULO en el caso de los apuntadores), después de extraer el elemento de la lista, si éste ha sido extraído de la primera o de la última posición de la lista, se deberá modificar el apuntador correspondiente para apuntar al siguiente elemento contenido en la lista (el valor de PRIMERO se incrementa ó el valor de ÚLTIMO se decrementa para el caso de arreglos, PRIMERO se coloca en la posición apuntada por siguiente de PRIMERO o ÚLTIMO se coloca en el nodo que apunta a ÚLTIMO, este proceso no es directo).

Existen cuatro casos a considerar al momento de eliminar en una lista:

Eliminar en una lista que tiene un solo elemento Eliminar el primero de la lista Eliminar el último de la lista Eliminar en medio (antes / después de un elemento)

3.4.5. Recorrer la lista

8

Page 9: Estructuras de Datos Unidad 03

La operación de recorrido consiste en visitar cada uno de los nodos que forman la lista. La visita de un nodo puede definirse por medio de una operación muy simple (por ejemplo la impresión de la información del mismo), o por medio de operaciones tan complejas como se requiera.

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.

3.4.6. 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.

Si la lista está ordenada, la búsqueda se realiza hasta encontrar el elemento o mientras el elemento buscado sea menor al visitado, en las listas desordenadas se debe buscar exhaustivamente hasta el último elemento de la lista para asegurarse si se encuentra o no en ella.

3.5. Aplicaciones

Algunas de las aplicaciones computacionales de las listas más conocidas son:

Representación de polinomios Resolución de colisiones (hash)

3.5.1. 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:

P(x) = 3X4 + 0.5X3 + 6X - 4

9

PRIMERO

Page 10: Estructuras de Datos Unidad 03

3 4 0.5 3 6 1 -4 0

El nodo contiene dos campos de información, COCIENTE y EXPONENTE.

3.5.2. Solución de colisiones (hash)

Entre los métodos de búsqueda que existen, hay uno que se llama por transformación de claves (hash), es un método en donde la información que se desea almacenar tiene una clave, esta se convierte en una posición de memoria para poder tener acceso a la información de manera directa, lo cual provoca que el método sea muy eficiente, sin embargo, puede ocurrir que dos o más claves generen la misma dirección de memoria, a esto se le llama colisión, en este caso, se puede generar una lista de todas las claves que generan esa posición de memoria de manera que, cuando se busca en la dirección y si no se encuentra la información buscada, se recorre la lista correspondiente para localizar el elemento.

10

NuloÚLTIMO

Page 11: Estructuras de Datos Unidad 03

3.6. 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.

3.6.1. 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.

3.6.2. 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.

Representación de Listas Doblemente Enlazadas

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:

Localizar la posición donde se desea agregar.

11

NuloÚLTIMO

Nulo

PRIMERO

Page 12: Estructuras de Datos Unidad 03

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.

Para retirar un elemento de la lista doblemente enlazada se requiere:

Localizar el elemento a retirar, si no se encuentra se emitirá un mensaje que indique que no se pudo localizar el dato.

Guardar la posición de memoria a la que apunta el nodo a borrar. Hacer que la liga siguiente del nodo anterior al que se desea borrar, apunte al

siguiente nodo después del que se está borrando. Hacer que la liga anterior del nodo siguiente al que se está borrando, apunte al

nodo anterior al que se está borrando Si el nodo borrado es el PRIMERO o ÚLTIMO, se deberá mover el apuntador

correspondiente. Retornar la posición de memoria como disponible.

3.6.3. Operaciones

Las operaciones que se llevan a cabo en esta estructura son esencialmente las mismas que en una lista simple tomando en cuenta que ahora se debe enlazar con dos ligas cada elemento de la lista, la excepción es la operación de recorrido, ya que esta se puede realizar en ambos sentidos (hacia el frente y hacia atrás).

12

Page 13: Estructuras de Datos Unidad 03

3.7. 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.

3.7.1. 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:

Pueden crearse ciclos infinitos al momento de recorrerla si no se tiene suficiente cuidado para identificar el final de la lista.

3.7.2. Representación

Como se indicó anteriormente, las listas circulares también se pueden representar de manera estática o dinámica.

En su representación estática, se permite, al momento de insertar si ya no hay espacio al final del arreglo, pero hay espacio al principio del arreglo, mover el índice del último a la primera posición del arreglo, de tal forma que puede haber situaciones donde el valor del índice del último elemento de la lista sea menor que el del primero.

13

Page 14: Estructuras de Datos Unidad 03

ListaD E A B C

Representación Estática de Listas Circulares

La operación de agregar un elemento a la lista se realiza de manera similar a la lista simple, excepto que:

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.

La representación de una lista circular simplemente enlazada requiere un solo apuntador al primer o al último elemento de preferencia, tomando en cuenta que siempre el último elemento de la lista apunte al primero.

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.

También las listas doblemente enlazadas pueden representarse de manera circular:

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.

3.7.3. Operaciones

14

PrimeroÚltimo

Último

Último

Page 15: Estructuras de Datos Unidad 03

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

Page 16: Estructuras de Datos Unidad 03

3.8. 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:

Método de transformación de claves a dirección Técnicas de almacenamiento disperso ó dispersión Técnicas aleatorias Técnicas de direccionamiento directo Métodos de tabla de Hash Métodos de tablas de dispersión Métodos de mapas Hash Método Hashing

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 cálculo de dirección que es la aplicación de una función que traduce un conjunto relativamente grande de posibles valores de clave, a un rango relativamente pequeño de valores de direcciones relativas (como un diccionario), se le conoce como función hash.

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.

Estas técnicas son importantes no sólo en el direccionamiento de arreglos en memoria principal, sino también para el direccionamiento de archivos relativos. Los cálculos se aplican a las claves de los registros para calcular los subíndices de los registros respectivos en el arreglo. Por lo que un registro puede encontrarse con una sola prueba (a menos que ocurran colisiones); sin hacer búsqueda en el arreglo.

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.

Esta situación, donde:

R (K1) = R (K2) pero K1 <> K2

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

Page 17: Estructuras de Datos Unidad 03

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.

3.8.1. 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”.

1

..

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

Page 18: Estructuras de Datos Unidad 03

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.

3.8.2. Elementos para una tabla hash

Para implementar una tabla hash se requiere:

Una estructura de acceso directo (arreglo o archivo) Una estructura de datos con una clave (registro)

Una función hash cuyo dominio sea el espacio de claves

Un mecanismo para solucionar las colisiones

3.8.3. Operaciones

Las operaciones básicas implementadas en las tablas hash son:

Inserción (llave, valor) Búsqueda (llave) que devuelve valor

Borrar (llave)

Las operaciones auxiliares que se pueden realizar sobre las tablas hash son:

Recorrer (iteración) Vaciado

3.8.3.1. Inserción

Para almacenar 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 no exista. El elemento se almacena en la posición de la tabla obtenida en el paso anterior.

o Si en la posición de la tabla ya había otro elemento, se ha producido una colisión. Este problema se puede solucionar asociando una lista en cada posición de la tabla.

3.8.3.2. 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.

18

Page 19: Estructuras de Datos Unidad 03

3.8.3.3. Borrar

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.

3.8.4. Funciones Hash

La eficiencia de una función hash depende de:

La distribución de los valores de las claves que realmente se usan. El número de valores de las claves que realmente están en uso con respecto al

tamaño de espacio de direcciones. El número de registros que pueden almacenarse en una dirección dada sin causar

colisiones (se supone 1). La técnica usada para resolver el problema de colisiones.

Existen muchas técnicas de cálculo de dirección; entre las más comunes se encuentran:

Método de residuo de la división Método de cuadrado medio Método de doblamiento o pliegue

En general el proceso de hashing debe realizar lo siguiente:

Representar la clave en forma numérica (tomando por ejemplo el código ASCII) Desglosar y sumar (tomar los números y sumarlos para obtener un solo número) Realizar el cálculo de la dirección

3.8.4.1. Hashing por residuo de la división

Algunas de las primeras investigaciones sobre hashing condujeron a una función hash conocida como el método del residuo de la división, o simplemente de la división. Este método parece ser el mejor en la mayoría de los casos. La idea de este método es la de dividir el valor de la llave entre un número apropiado, y después utilizar el residuo de la división como dirección relativa para el registro.

dirección relativa = clave módulo divisor + 1 ódirección relativa = clave módulo divisor

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

19

Page 20: Estructuras de Datos Unidad 03

tendremos que hacer que divisor > n, suponiendo que solamente un registro puede ser almacenado en una dirección relativa dada.

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.

Ejemplo: Si se considera un espacio máximo de direccionamiento de 5000 registros, se puede determinar el número primo usado como divisor:

Max = 5000

Entonces, el divisor será 4999. Usando ese divisor se calculan las siguientes direcciones correspondientes a los valores de las claves siguientes:

clave Dirección relativa123456789 123456789 mod 4999 + 1 =

1486987654321 1892123456790 1487555555555 1689000000504 0505100064183 4200200120472 0505 **colisión**

3.8.4.2. Hashing por doblamiento o pliegue

En esta técnica el valor de la llave es separado en varias partes, cada una de las cuales (excepto la última) tiene el mismo número de dígitos que tiene la dirección relativa objetivo. Estas particiones son después plegadas una sobre otra y sumadas. El resultado, con el dígito de mayor orden truncado, si es necesario, es la dirección relativa.

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:

12345

20

Page 21: Estructuras de Datos Unidad 03

9876

c) Se suman:

13221

d) Se trunca el dígito de mayor orden, por lo que la dirección relativa es:

3221

La siguiente tabla muestra varias direcciones calculadas por este método:

Clave Dirección relativa123456789 3221987654321 8999555555555 6110000000472 0472117400000 2740027000040 2740 **colisión**

3.8.5. Tratamiento de colisiones

Si dos llaves generan una dirección al mismo índice, los registros correspondientes no pueden ser almacenados en la misma posición. En estos casos, cuando una casilla ya está ocupada, debemos encontrar otra ubicación donde almacenar el nuevo registro, y hacerlo de tal manera que podamos encontrarlo cuando se requiera.

Existen diversas técnicas de resolución de colisiones, entre las más populares son:

El encadenamiento (listas ligadas) Direccionamiento abierto.

3.8.5.1. Encadenamiento

Con la técnica más simple de encadenamiento, cada posición del arreglo está unido a una lista de los registros insertados que colisionan en la misma casilla. La inserción consiste en encontrar la casilla correcta y agregar al final de la lista correspondiente. El borrado consiste en buscar y quitar de la lista.

3.8.6. 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.

21

Page 22: Estructuras de Datos Unidad 03

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.

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.

3.8.7. 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.

Muchos sistemas relacionados con la seguridad informática usan funciones o tablas de dispersión o hash.

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ámica5. Cuáles son las diferencias entre lista lineal estática y lista circular estática6. Cuáles son las diferencias entre lista lineal dinámica y lista circular dinámica7. Cuáles son las diferencias entre lista lineal doblemente ligada y lista circular doblemente ligada

22