Tipos abstractos de datos (TAD) y estructuras de datos
Un Tipo Abstracto de Datos (TAD) es un modelo matemático compuesto por una colección de operaciones definidas sobre un conjunto de datos para el modelo. Es decir, un TAD es una definición y representación de un tipo de dato junto con sus propiedades y las operaciones válidas sobre él, descrita sin comprometer ninguna implementación concreta. El objetivo principal de utilizar TAD es conseguir mayor flexibilidad mediante el concepto de abstracción.
La diferencia clave entre un TAD y una estructura de datos es de enfoque: un TAD se enfoca en las operaciones y comportamientos del objeto (el "qué"), mientras que una estructura de datos se enfoca en la organización de los datos en sí (el "cómo"). El principio de abstracción implica que el código que utiliza un TAD no depende ni conoce la implementación específica del TAD: la interfaz oculta los detalles internos. Si no se respeta este principio al implementar, por ejemplo, un TAD Cola, la consecuencia es que el código que utiliza la 'Cola' necesitaría conocimientos específicos sobre su estructura interna, rompiendo el encapsulamiento.
En términos de lenguaje, la abstracción funcional consiste en crear procedimientos y funciones para invocarlos mediante un nombre. En Java, el TAD se corresponde con una interface (declara las operaciones) y la estructura de datos concreta con una class que la implementa: es la clase la que debe implementar todas las operaciones definidas en la interface, no al revés.
Clasificación de las estructuras de datos
-
Contiguas (estáticas): los elementos ocupan posiciones de memoria adyacentes. Son estructuras contiguas los vectores, matrices y registros.
-
Enlazadas (dinámicas): los elementos se conectan mediante punteros. Una lista enlazada es una estructura de datos que almacena elementos en nodos conectados por punteros.
-
Lineales: cada elemento tiene a lo sumo un anterior y un siguiente (p. ej. la pila).
-
Jerárquicas: los elementos se organizan por niveles (los árboles).
Estructuras lineales notables:
| Estructura | Característica |
|---|---|
| Pila (stack) | LIFO; se agrega un elemento con la operación Push y se extrae con Pop |
| Cola (queue) | FIFO; inserción por un extremo, extracción por el otro |
| Cola circular | Los elementos se disponen de forma circular; cada elemento tiene un sucesor y un predecesor |
| Bicola / doble cola (deque) | Inserciones y extracciones por ambos extremos |
Las bicolas restringidas limitan un extremo: la bicola de entrada restringida permite inserciones por un extremo y borrados por los dos; la doble cola de salida restringida permite eliminaciones por un extremo e inserciones por los dos.
El Diccionario o Mapa es un TAD que vincula un dato (clave) con otro (valor), sin definir cómo se asocian o guardan internamente. A diferencia de listas o pilas, NO tiene un orden definido entre sus elementos. Se implementa habitualmente mediante una tabla hash.
Árboles
Un árbol es una estructura jerárquica y dinámica que se puede recorrer en amplitud (por niveles) y en profundidad. Su principal utilidad es representar información jerarquizada. El nodo raíz es el nodo situado en la parte superior del árbol.
-
Grado de un nodo: número de subárboles (hijos) que cuelgan del nodo.
-
Grado del árbol: el máximo grado de los nodos del árbol, es decir, el número máximo de hijos que tiene un nodo (NO el número total de hijos ni de nodos). Un árbol binario tiene grado 2; por ello un árbol binario lleno de 15 nodos tiene grado 2.
Los árboles binarios permiten acceso rápido a los datos y se emplean para la representación de datos jerarquizados. En un árbol binario de búsqueda (ABB) balanceado, la búsqueda y la eliminación tienen complejidad O(log n); sin embargo, acceder al elemento en la posición n (indexado) NO puede realizarse de forma eficiente.
Recorridos de un árbol binario (según la posición de la raíz):
| Recorrido | Orden |
|---|---|
| Preorden | Raíz, subárbol izquierdo, subárbol derecho |
| Inorden | Subárbol izquierdo, raíz, subárbol derecho |
| Postorden | Subárbol izquierdo, subárbol derecho, raíz |
Como la raíz es el primer nodo en preorden, si un recorrido en preorden es {3,8,2,1,5,6,9,0} la raíz es 3 (análogamente, {7,8,...}→7 y {8,3,...}→8). En postorden la raíz es el último nodo visitado, por lo que si el postorden es {L,M,K,I,J,H,B,F,G,E,D,C,A} la raíz es A.
Árboles equilibrados (autoequilibrados):
-
Árbol AVL: árbol binario de búsqueda que garantiza que la altura de sus subárboles izquierdo y derecho difieren en no más de 1 para cada nodo (factor de equilibrio −1, 0 o +1), manteniendo la altura en O(log n).
-
Árbol rojo-negro: árbol casi equilibrado con un bit de color por nodo; entre sus propiedades, la raíz es un nodo negro.
Tablas hash (tablas de dispersión)
Una tabla hashing es una tabla que relaciona claves con posiciones de memoria. La función hash (o función de dispersión) es la que transforma la clave en un número que identifica la posición donde se localiza el valor; su misión es transformar las claves en direcciones de memoria. El TAD tabla de dispersión es un tipo de datos heterogéneo de tamaño variable, y sobre él se definen las operaciones Insertar, Buscar y Eliminar. La operación que mejor soporta de forma eficiente es la búsqueda, con complejidad temporal promedio O(1).
Una colisión se produce cuando dos claves diferentes generan el mismo valor de función hash y apuntan al mismo índice: los registros no pueden almacenarse en la misma posición. La resolución de colisiones consiste en encontrar otra ubicación para almacenar el nuevo registro. Las dos técnicas más populares son el encadenamiento (chaining) y el direccionamiento abierto (open addressing), denominadas también hashing abierto y hashing cerrado respectivamente:
-
Encadenamiento / hashing abierto: las claves se almacenan en listas enlazadas (vinculadas) adjuntas a cada celda de la tabla. Su principal desventaja es que el rendimiento de la caché es pobre, al dispersar los nodos por memoria. Puede mejorarse sustituyendo las listas por árboles autoequilibrados.
-
Direccionamiento abierto / hashing cerrado: ante una colisión se busca una posición libre alternativa dentro de la misma tabla mediante un sondeo del array (secuencia de sondeo).
El factor de carga se define como el cociente entre el número de elementos almacenados en la tabla y el tamaño total de la tabla; se utiliza para decidir qué método de resolución de colisiones emplear y cuándo redimensionar. Como la tabla hash implementa el TAD Diccionario, un valor de factor de carga controlado mantiene el rendimiento cercano a O(1).
Organización de ficheros
En la organización de ficheros, un archivo de registro o archivo de transacciones es un archivo secuencial: los registros se añaden en orden de llegada y se procesan de forma consecutiva, típicamente para actualizar (contra) un archivo maestro.
Fuentes
-
NIST — Dictionary of Algorithms and Data Structures (DADS): hash table (https://xlinux.nist.gov/dads/HTML/hashtab.html)
-
NIST — DADS: collision (https://xlinux.nist.gov/dads/HTML/collision.html) y collision resolution scheme (https://xlinux.nist.gov/dads/HTML/collisionres.html)
-
NIST — DADS: open addressing (https://xlinux.nist.gov/dads/HTML/openAddressing.html)
-
NIST — DADS: red-black tree (https://xlinux.nist.gov/dads/HTML/redblack.html)
-
NIST — DADS: AVL tree (https://xlinux.nist.gov/dads/HTML/avltree.html)
-
Oracle — The Java Tutorials, Interfaces (https://docs.oracle.com/javase/tutorial/java/concepts/interface.html)