12 TEMA 12 Organizacón Lógica de Los Datos - Estructuras Dinámicas
12 TEMA 12 Organizacón Lógica de Los Datos - Estructuras Dinámicas
ESTRUCTURAS
DINÁMICAS.
1. Introducción.
2. Conceptos básicos.
3. Listas.
3.1. Tipos.
3.2. Operaciones.
4. Pilas.
5. Colas.
6. Árboles.
6.1. Terminología.
6.2. Características.
6.3. Tipos.
6.3.1. Árbol binario.
6.3.1.1. Operaciones
6.3.2. Árbol B.
6.3.2.1. Operaciones.
6.3.3. Árbol B+.
7. Grafos.
7.1. Tipos.
7.2. Operaciones.
1. Introducción.
Un dato es una unidad de información relativa a un objeto y manipulable por los ordenadores.
Un dato puede ser de varios tipos y la manera de representar un dato internamente por la
máquina debe ser unívoca, es decir, que siempre se representará igual. Es por eso que un
determinado tipo de datos siempre ocupa lo mismo dentro de la memoria principal.
La elección del tipo de estructura de datos idónea a cada aplicación dependerá esencialmente
del tipo de aplicación y del lenguaje de programación. Las estructuras de datos dinámicas por
excelencia son las listas (lineales, pilas y colas), árbol (ordenado, binario, balanceado,
multicamino, B) y grafos.
2. Conceptos básicos.
Antes de pasar a explicar cada una de las estructuras dinámicas fundamentales es necesario
entender el concepto de puntero y tipo de datos abstracto.
Se entiende como puntero a un objeto cuyo valor se refiere a otro valor almacenado en otra
parte de la memoria utilizando su dirección. Un puntero referencia a una ubicación en
memoria o en algunos casos tiene un valor reservado para indicar que no se refiere a un objeto
válido denominado puntero nulo. Sirven para crear estructuras finitas.
Cuando se configuran estructuras de datos como listas, colas y árboles se cuenta con punteros
o indicadores que ayudan a controlar la estructura dinámica enlazando unos datos con otros.
Por otro lado, el concepto de tipo de dato abstracto o TDA, está relacionado con la
programación orientada a objetos. Un TDA es una colección de datos y procedimientos
asociados a esos datos. De esta manera, tienen atributos, métodos y una interfaz para lograr el
encapsulamiento y la ocultación. Las estructuras de datos dinámicas se implementan como un
TDA que ofrece una interfaz completa para añadir, eliminar, recorrer, ordenar o las
operaciones que sean necesarias en esa estructura, pero no permiten un acceso directo a la
implementación de esa estructura.
3. Listas.
Las listas son estructuras de datos dinámicas lineales, es la manera más simple de
interrelacionar o vincular un conjunto de elementos a tenerlos en una sola fila. En este caso,
sólo se necesita un vínculo por cada elemento para hacer referencia a su sucesor. Así una lista
está formada por un conjunto de objetos llamados nodos, cada objeto almacena información
propia que se desea guardar y un puntero o más para enlazar el nodo con otros nodos y
construir la estructura. Las listas permiten inserciones y eliminación de nodos en cualquier
punto de la lista en tiempo constante, pero no permiten un acceso aleatorio.
3.1.Tipos.
Existen diferentes tipos de listas enlazadas en función de dónde referencie cada puntero del
nodo.
Las listas enlazadas simples, tienen un puntero que apunta al siguiente. Si este es null
es el último elemento de la lista. Estas no se pueden recorrer en sentido contrario.
Las listas circulares, es un tipo de lista lineal en el que el último elemento apunta al
primero. Estas listas no tiene primer ni último elemento. Se recorre la lista en una sola
dirección. La ventaja es que siempre se puede llegar a cualquier nodo siguiendo los
enlaces.
Las listas doblemente enlazadas, son aquellas en la que cada elemento dispone de dos
punteros, uno que apunta al elemento anterior y otro que apunta al elemento
posterior. La ventaja es que se puede acceder indistintamente a elementos posteriores
como a elementos anteriores de forma sencilla. Este hecho mejora las capacidades de
localización de elementos en una lista.
Las listas doblemente circulares, tienen dos punteros y además, el último apunta al
primero y el primero al último. Si no se tiene anidado, una búsqueda puede resultar en
un ciclo infinito.
Para todo este tipo de listas se puede contar con un nodo llamado centinela, apuntando
siempre a un elemento considerado como la cabecera de lista. Este puntero sirve como
referencia para, en una búsqueda, saber dónde estamos situados en la lista.
3.2.Operaciones.
4. Pilas.
Una pila es un caso particular de lista en la que los elementos se añaden o eliminan sólo por un
extremo. Se denomina también lista LIFO (last in first out) y tanto las inserciones como las
eliminaciones se realizan sólo al principio de la lista. Cualquier elemento añadido pasa a ser el
primero de la lista, además sólo se puede eliminar el elemento que ocupa el primer lugar de la
lista.
Las pilas se utilizan en hardware y software para almacenar las direcciones de instrucciones
desde las que se hacen llamadas a subrutinas. Al extremo donde se realizan las inserciones y
eliminaciones se le denomina cima o tope.
Tiene dos operaciones básicas: Push, para apilar o insertar el elemento y Pop , para extraer o
retirar un elemento. Además de la operación cima que devuelve el valor del elemento que está
arriba.
5. Colas.
Las colas son otro tipo de estructura dinámica de datos que se implementan de manera similar
a las listas, diferenciándose de ellas en el modo de insertar y eliminar datos.
Se denomina cola a una lista en la que las inserciones se realizan sólo en el final y sólo se
pueden acceder o eliminar en un instante dado el primer elemento de la lista. Las colas se
suelen denominar también listas FIFO (first in first out).
Las colas se utilizan para almacenar datos que necesiten ser procesados según el orden de
llegada, Se suelen utilizar en SSOO para la gestión de trabajos no interactivos (procesamiento
por lotes o batch).
Las operaciones propias aparte de la de crear son Push o encolar, que añade un nodo al final y
éste pasa a denominarse internamente último; Pop o desencolar, que elimina el primer
elemento de la cola y el siguiente pasa a ser el primero; y frente, que consulta el valor que
almacena el primer elemento.
Un caso particular son las denominadas colas circulares en las que se considera que después
del último elemento se accede de nuevo al primero, de esta manera se reutilizan las posiciones
extraídas, el final de la cola es a su vez el principio, creándose un círculo cerrado.
6. Árboles.
Un árbol es una estructura jerárquica de datos que imita la forma de unas ramificaciones. Son
un subconjunto importante de los grafos para describir estructuras que representan algún tipo
de dependencia.
Un nodo es la unidad sobre la que se construye el árbol y se le llama raíz y de este nodo parten
todas las conexiones a los demás nodos. A los nodos que dependen de otro nodo se les conoce
como hijos o descendientes y del que dependen se le llama nodo padre. De esto se puede
concluir que cada nodo padre es una raíz de un subárbol. Un nodo que no tiene hijos se
conoce como hoja o nodos terminales.
6.1.Terminología.
- Grado: entidad de hijos que tiene un árbol, es decir, número de subárboles que tiene
un nodo. Naturalmente, el grado de las hojas es 0.
- Niveles: son los nodos que se encuentran en la misma profundidad, por convención, el
nodo raíz del árbol se le considera el nivel 0.
- Longitud: el número de ramas que tienen que ser recorridas desde la raíz hasta un
nodo en concreto.
- Altura: longitud del camino más largo.
- Camino: lista de los nodos que se recorren desde un nodo padre a alguno de los nodos
hijo.
6.2.Características.
- Árboles ordenados: es un árbol en el cual las ramas de cada nodo están ordenadas.
Así, en cualquier nodo de todos los nodos del subárbol izquierdo tiene un valor menor
o igual al valor del nodo raíz de ese subárbol. De modo similar cualquier nodo de todos
los nodos del subárbol derecho tiene un valor mayor o igual del nodo raíz de ese
subárbol.
- Árbol equilibrado o booleano: un árbol está balanceado si y sólo si en cada nodo las
alturas de sus dos subárboles difieren como máximo en 1. El equilibrio se consigue por
medio de las operaciones de inserción y borrado. En la inserción para reestablecer el
equilibrio se expresan en su totalidad como secuencias de reasignación de punteros.
Así, los punteros se intercambian resultando una rotación simple o doble y ajustando
los factores de equilibrio. En el borrado, la operación es más complicada, ya que
requiere la rotación en todos los nodos del camino de búsqueda.
6.3.Tipos.
Los tipos de árboles se definirán en función del grado del árbol, distinguiendo entre árboles
binarios cuyo grado es 2 y árboles B y B + cuyo grado es mayor que 2.
Árbol de grado 2 definido como un conjunto finito de nodos que es vacío o bien consta de una
raíz con dos subárboles izquierdo y derecho de la raíz. Su implementación es sencilla, ya que
cada nodo se define como una estructura fija y con un puntero al hijo de la izquierda y otro al
de la derecha.
6.3.1.1. Operaciones.
Recorrido: para recorrer un árbol se puede hacer según los siguientes algoritmos
formulados fácilmente como procedimientos recursivos:
- Preorden: recorre el nodo padre seguido de los hijos de izquierda a derecha R-I-D.
- Inorden: recorre primero los hijos de izquierda, luego raíz y luego a derecha I-R-D
- Postorden: recorre los hijos de izquierda a derecha y luego el nodo raíz. I-D-R.
Búsqueda: si el árbol no está ordenado se recorrerá el árbol con algunos de los tres
métodos hasta encontrar el elemento a buscar o en su defecto que no encuentre el
árbol. Deberemos recorrer el árbol totalmente en la mayoría de los casos, ya que no se
sigue ninguna ordenación y el elemento a buscar puede estar situado en cualquier
posición.
Si el árbol está ordenado, se van haciendo comparaciones de las claves del nodo a buscar
y del nodo de la rama que nos encontramos y se irá recorriendo al hijo derecho o
izquierdo en función de si la clave del nodo a buscar sea mayor o menor que la clave
donde se está posicionando.
Inserción: Si el árbol no está ordenado, el elemento se podría insertar en la primera
hoja que se encontrase debido a que no se sigue ningún orden. Si el árbol está
ordenado, la inserción se realiza descendiendo por el árbol a partir del nodo raíz
dirigiéndose de izquierda a derecha según el valor a insertar sea inferior o superior.
Cuando alcanza el nodo del árbol en que no se puede continuar (hoja), el nuevo
elemento se engancha en función de su valor.
Eliminación: en este caso es indiferente que el árbol esté ordenado o no, tan solo
influirá a la hora de buscarlo. La eliminación de un elemento debe conservar el orden
de los elementos del árbol. Se considera diferentes casos según el nodo:
- Si el elemento es una hoja se suprime sin más.
- Si el elemento no tiene más que un descendiente se sustituye por él.
- Si el elemento tiene dos descendientes se sustituye por el elemento más a derecha o a
la izquierda de modo que permita conservar la ordenación.
El árbol binario AVL o árbol binario de búsqueda es un tipo especial de árbol binario que toma
su nombre de las iniciales de los apellidos de sus inventores. Se caracterizan por estar siempre
balanceados y ordenados. Para conseguir esta propiedad de equilibrio y ordenación la
inserción y borrado de los nodos se ha de realizar de una forma especial, ya que si se rompe la
condición de equilibrio hay que realizar las rotaciones de nodos y la inserción o borrado
manteniendo la ordenación. Un árbol AVL debe guardar información adicional a cada nodo, un
contador de la diferencia entre las alturas de sus dos subárboles. La principal característica de
los árboles AVL es su excelente tiempo de ejecución para las diferentes operaciones por lo que
se usan para almacenar valores que deban ser fácilmente encontrados.
6.3.2. Árboles B.
Estructuras de datos de árbol balanceados de búsqueda pero que cada nodo puede poseer
más de dos hijos existiendo una relación de orden entre ellos. Un árbol B de orden M (máximo
número de hijos que puede tener cada nodo) es un árbol que satisface las siguientes
necesidades:
6.3.2.1. Operaciones.
- Construcción inicial.
El modo más eficiente para construir el árbol B inicial no sería insertar todos los nodos en el
conjunto inicial sucesivamente, sino construir el conjunto inicial de nodos hoja y después
construir los nodos internos a partir de ese conjunto. De manera que se separarán en
conjuntos de tamaño máximo más 1 y el último de cada uno de estos grupos subirá al
siguiente nivel manteniendo todos los nodos hoja al mismo nivel.
- Búsqueda.
Consiste en situarse en el nodo raíz del árbol, si la clave se encontrara ahí hemos terminado y
si no es así seleccionamos entre los hijos que se encuentran entre dos valores de clave y se
repite el proceso. Si se llega a una hoja es porque la clave no se encuentra en el árbol.
- Inserción.
Consta de dos pasos recursivos: buscamos la hoja donde deberíamos encontrar el valor. Si no
está completo el elemento se inserta. Si el nodo está completo dividimos en dos nuevos nodos
conteniendo cada uno de ellos la mitad de las claves y tomando una de estas para insertarla en
el padre. Y se repetirá si el padre está completo hasta raíz, que si también está completa se
incrementa la altura del árbol creando el nodo raíz con una única clave.
- Borrado.
El borrado exige asegurarse de que haya al menos “n” elementos en el nodo correspondiente.
Por ello habrá que contemplar una vez localizado el nodo donde se encuentra la clave; si no es
una hoja, intercambiar con el valor de la clave más a la izquierda del hijo a la derecha y borrar
la clave. Si el nodo contiene al menos el mínimo de claves se acaba. Si el nodo tiene un número
menor que el mínimo se redistribuyen con el hermano y si ningún hermano tiene más del
mínimo, unión de dos nodos junto con las claves del padre y se vuelve a propagar el borrado.
Son una variante de los árboles B, se diferencian que los árboles B + toda la información se
encuentra almacenada en las hojas. En el raíz y los nodos intermedios se encuentran
almacenados claves para llegar a un dato. Principales características de los árboles B + son:
- La raíz almacena como mínimo un dato y como máximo n-1 datos y como mínimo 2
hijos.
- Los nodos intermedios tienen como mínimo (m-1)/2 y como máximo m-1 datos.
- Todos los nodos hoja están a la misma altura y están ordenados.
- La información está en las hojas por lo que los nodos intermedios duplican clave.
7. Grafos.
Un grafo consiste en un número de nodos (puntos o vértices) y un grupo de arcos que unen
parejas de nodos. Cada arco se identifica por un par de vértices A: (v i, vj) y el grafo se
representa por el par G: (V, A).
7.1.Tipos.
Si los nodos o vértices apuntan unos a otros, los arcos tienen dirección y se dice grafos
dirigidos y si están relacionados pero no apuntan son grafos no dirigidos.
Si a cada arco se le asocia un valor llamado peso el grafo es ponderado.
Si no existen arcos entre el mismo elemento y no existe más de un arco para unir dos
nodos, el grafo es sencillo, si tiene lazos o ciclos es múltiple.
Si siempre existe un camino entre cualquier nodo del grafo el grafo es conectado y
cuando hay vértices que no están unidos por ningún camino se denomina grafo
desconectado.
Si para todos los nodos del grafo existe un camino de él a todos y todos a él, el grado
es fuertemente conexo pero si existe camino de él a todos sólo en 1 sentido,
débilmente conexo
7.2.Operaciones.
- Implementación: para implementarlo se puede usar la técnica del árbol con un grado
genérico y usar una lista de adyacencia, si el grafo es pequeño. Si el grafo es más
complejo se recurre a la matriz de adyacencia con tantas filas y columnas como nodos
contiene el grafo para representar los arcos entre estos elementos.
- Búsqueda: uno de los algoritmos más comunes en grafos son los de exploración del
mismo pudiendo usar una búsqueda en profundidad (DFS) en donde se comienza en
cualquier vértice y en cada paso se avanza a un nuevo vértice adyacente, cuando todos
los adyacentes hayan sido visitados se retrocede al vértice desde el que se comenzó y
se repite el proceso con los hermanos del nodo procesado; o usar una búsqueda por
anchura (BFS) donde se visitan todos los vecinos de un vértice antes de pasar al
siguiente, por lo que no hay que retroceder. Una vez etiquetados todos los vecinos, se
continúa con el siguiente vértice adyacente.
- Cálculo camino mínimo: algoritmo clásico con grafos son el algoritmo de Dykstra que
consiste en ir explorando todos los caminos que parten del vértice origen y que llevan
a todos los demás vértices, cuando se obtiene el camino más corto desde el vértice
origen al resto de vértices que compone el grafo se detiene. El algoritmo de Floyd
consiste en iterar n veces siendo n el número de nodos. En cada iteración se coge un
nodo intermedio y se calcula si la distancia es mayor que la distancia entre el primero y
el intermedio más el intermedio al segundo. Si es mayor se asume que la distancia es
ésta suma. No devuelve qué camino tendrá.