Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
Y Estructura de Datos
Emiliano Salvatori
Junio 2019
1. Introducción
Algoritmos de implementacion general
Insertar, Borrar, Buscar
Caminos mı́nimos
Ordenación
La velocidad del programa: ya que puede variar con respecto a cómo se ordene la información.
Memoria usada: también depende de la forma en que el programa organice los datos a utilizar.
La elección de un Algoritmo junto con una estructura de datos NO soluciona todos los problemas, sino
que resuelve algunos y complica otros. La elección de cada uno, vendrá dada por el tipo de solución que se le
quiera dar al problema en sı́. Recordar que la elección de un algoritmo como de una estructura de datos es
interdependiente, están relacionados exclusivamente.
Ejemplos:
Si se tiene una lista desordenada, ¿Cómo buscar el elemento? Pues mediante una búsqueda secuencial.
Esto hace que el tiempo de ejecución sea de orden lineal, ya que recorre TODOS los elementos
Si la lista está ordenada ¿Cómo buscar un elemento? Pues mediante búsqueda binaria. Ésto genera menor
cantidad de tiempo en la búsqueda del elemento, ya que es a lo sumo la búsqueda es de orden logarı́timica.
¿Cómo se mide el tiempo que tarda cada algoritmo en procesar una función?
Para ello hay dos maneras:
Manual: Tomando el tiempo que tarda.
Automática: usando alguna instrucción del lenguaje que permita medir la ejecución.
En el análisis de Algoritmos existen modelos matemáticos para poder analizarlos, como por ejemplo:
X = Costo ∗ F recuencia
Tanto el Costo como la Frecuencia, son todas las operaciones implicadas en el algoritmo.
Costo: Depende de la máquina, del compilador, del lenguaje
Frecuencia: depende del Algoritmo y de la entrada de datos que tenga
1
2 ÁRBOLES BINARIOS 2
2. Árboles Binarios
Definición
Se define Arbol Binario como una colección de nodos que:
Puede estar vacı́a.
Puede tener un nodo raı́z el cual está conectado por medio de aristas a unos subnodos.
Caracteristicas
Las caracterı́sticas de un Árbol Binario son:
Terminologı́a
Camino: se denomina camino a la secuencia realizada desde un nodo raı́z hasta un subnodo.
Longitud: de un camino se denomina a la cantidad de aristas por las que se debe atravesar para llegar
desde la raı́z hasta el nodo destino.
Longitud = 0 desde cada nodo a sı́ mismo.
Existe un único camino desde la raı́z a un determinado nodo.
Profundidad: de un determinado Nodo es el camino desde la raı́z hasta el nodo destino.
La raı́z tiene profundidad cero (lo mismo que la longitud a sı́ mismo).
Grado: de un nodo es la cantidad de hijos que tiene ese nodo.
Altura: de un nodo es la longitud del camino más largo desde ese nodo hasta la hoja.
Las hojas tienen altura cero.
La altura de un árbol es la altura del nodo raı́z –¿¡Importante!.
Ancestro/Descendiente: en caso de existir un camino de un nodo hacia otro subnodo, se dice que el
primero es Ancestro del segundo y que el segundo es Descendiente del primero.
Arbol binario Lleno: Se denomina de esta forma al árbol cuyos nodos tienen todos grado 2 (es decir que
todos tienen dos nodos hijos) y que todas las hojas del árbol están al mismo nivel.
Un simple nodo, de altura = 0, se denomina lleno.
Si el árbol tiene una altura de 10, se dice que es lleno si sus subárboles son llenos dando una altura de 9.
Para saber la cantidad de nodos de un árbol lleno (recordar que el arbol que tiene TODOS sus subnodos
con hijos) se debe hacer:
2nivel+1 − 1
Arbol binario completo: Se dice que es completo cuando el nivel superior está lleno y está completo de
izquierda a derecha. Si Altura(o nivel) = h, entonces si h - 1 = árbol lleno y el nivel inferior a h está completo
de izquierda a derecha, entonces se está hablando de un árbol completo. Por lo que la cantidad de nodos varı́a
entre:
(2h )y(2h+1 − 1)
Recorridos
Preorden: Se procesa primero la raı́z, luego sus hijos izquierdo y luego el derecho.
Inorden: Se procesa el hijo izquierdo, luego la raı́z y el último el hijo derecho.
Postorden: Se procesan primero los hijos izquierdo y derecho y luego la raı́z.
Por Niveles: Se procesan los nodos teniendo en cuenta sus niveles, primero la raı́z, luego los hijos y luego
los subhijos posteriores.
3 ÁRBOLES AVL 3
3. Árboles AVL
Definición
El nombre AVL proviene de los matemáticos que idearon la estructura de datos: Adelson-Velskii-Landis. Se
denomina un árbol AVL a un árbol binario que se encuentra balanceado. Se dice que está balanceado si:
Para cada nodo del árbol la diferencia entre la altura del subarbol izquierdo y el subarbol derecho es
menor o igual a uno. RECORDAR: La altura de un árbol vacı́o es -1.
Caracterı́sticas
La propiedad de balanceo garantiza que la altura del árbol es de órden logarı́tmico por lo tanto:
La búsqueda de determinada clave será orden logarı́tmica (recordar que en el peor de los casos en los ABB
la búsqueda es de orden lineal y en el MEJOR de orden logarı́tmico).
La insersión tiene orden logarı́timco.
Para mantener la propiedad de balanceo al insertar un elemento se debe reestablecer la propiedad de
balanceo.
Para ello es necesario que la estructura permita que cada nodo guarde información de su altura.
Inserción
Cuando se realiza una inserción, es necesario que se reestablezca la propiedad de balanceo. Para ello hay
que tener en cuenta que en cada incersión se debe volver de la recursión controlando el balanceo (consultando
a cada nodo por si la altura del sub árbol izquiero o derecho es menor o igual a uno. Cuando se inserta un nodo
nuevo, el camino que se recorre para realizar la inserción, se recorre LUEGO de la inserción (saliendo de la
recursión) y en el camino de vuelta cada nodo puede sufrir:
Cambio en sus alturas
Desbalanceo
El desbalanceo sólo puede ocurrir en el camino que se realizó para insertar un elemento en el árbol, ya que
el cambio de altura se realizó al insertar el nuevo elemento.
Rotaciones
Las rotaciones que se pueden realizar dependiendo del desbalanceo son: Rotaciones Simples y Rotaciones
Dobles Para saber qué tipo de rotaciones se deben aplicar a la hora de encontrar un desbalanceo en un nodo
luego de insertar un elemento, se hace lo siguiente:
ROTACION SIMPLE:
Si el nodo insertado es menor que el hijo izquierdo.
Si el nodo insertado es mayor que el hijo derecho.
ROTACION DOBLE:
Si el nodo insertado es mayor que el hijo izquierdo.
Si el nodo insertado es menor que el hizo derecho.
4 ÁRBOLES GENERALES 4
Eliminación
Se supone que deberı́a ser lo mismo que en la insersión. Con la eliminación de un nodo es posible que se
pierda la propiedad de balanceo establecida en el árbol, por lo que en cada caso habrı́a que recorrer el camino
inverso a la búsqueda para encontrar el elemento a eliminar y preguntar por su altura. En caso de que haya
algún desbalanceo, se procede a realizar las rotaciones ya mencionadas. [*HABRIA* que preguntar bien a lo
que se quiere referir los ejemplos de la filmina]
4. Árboles Generales
Definición
Los Árboles Generales NO son árboles Binarios, es decir que cada nodo NO requiere estar conectado como
máximo a 2 nodos, sino que puede estar conectado a varios. Un árbol es una colección de nodos tal que:
Puede estar vacı́a, por lo que se denomina Árbol Vacı́o.
Puede estar formado por un nodo raı́z y un conjunto de árboles (cantidad mayor a 0) donde cada subárbol
está conectado a la raı́z por medio de una arista.
Caracterı́sticas
El grado de un árbol general vendrá dado por el grado del nodo con mayor grado. El grado se refiere al
número mayor de hijos que tiene alguno de los nodos del Árbol y esta limitado por el Orden, ya que este indica
el número máximo de hijos que puede tener un nodo.
Se denomina Arbol lleno al arbol T de grado K y altura H, si cada nodo interno de T tiene grado K y
todas sus hojas están al mismo nivel H.
Se denomina Arbol completo al arbol T que de grado K y de altura H que es lleno hasta H-1 y que en el
nivel H está completo de izquierda a derecha.
¿Dónde se emplean?
Éste tipo de estructuras se utilizan para emplearlos en:
Organigramas de Empresas.
Arboles Genealógicos.
Taxonomı́a.
Sistemas de archivos (como en cualquier sistema SO).
Organización de un libro en capı́tulos y secciones.
5 HEAP BINARIA 5
¿Cómo se representan?
Mediante una lista de hijos, donde cada nodo tiene:
tiene información de sı́ mismo.
Listas dinámicas tiene la ventaja de dar mayor flexibilidad en el uso. Las listas enlazadas permiten guardar
un puntero sus subnodos hijos.
Hijo más izquierdo y hermano derecho. Donde cada nodo tiene:
Información propia del nodo.
Recorridos
Los recorridos dentro de éstos árboles pueden ser como los recorridos de los Árboles Binarios de Búsqueda:
Preorden: se procesan primero la raı́z y luego los hijos
Inorden: se procesa el primer hijo, luego la raı́z y por último los restantes hijos.
Postorden: se procesan primero los hijos y luego la raı́z.
Por niveles: Se procesan los nodos teniendo en cuenta sus niveles, primero la raı́z,luego los hijos y los hijos
de éstos, etc.
5. Heap Binaria
Caracterı́sticas
Una Heap Binaria es un Árbol Binario el cual es un árbol completo (es decir tiene todos sus niveles - 1
como si fuera un árbol lleno y está completo de izquierda a derecha). Lo que permite esto es que se guarde los
elementos dentro de una estructura de datos como ser un array (arreglo). Una heap binaria puede ser tanto Min
Heap o Max Heap.
Min Heap: el elemento posicionado en el nodo raı́z (como primer elemento en un arreglo) es el menor
elemento de todo el árbol (o en su defecto IGUAL a sus nodos hijos).
Max Heap: el elemento posicionado en el nodo raı́z (como primer elemento en un arreglo) es el mayor
elemento de todo el árbol (o en su defecto IGUAL a sus nodos hijos).
De ésta forma se puede caracterizar una Heap Binaria como una cola de prioridades, donde dependiendo de
qué tipo de Heap sea (Max o Min) se puede dar 2 tipos de prioridades distintas a los elementos almacenados.
Si es Max Heap entonces la prioridad máxima vendrá dado por valores altos; en cambio si es Min Heap, la
prioridad máxima vendrá dado por los valores más bajos.
IMPORTANTE: Al poder ser guardadas dentro de un array, se elimina la necesidad de tener punteros que
apunten a sus subelementos y como sigue prevaleciendo la estructura de árbol binario, todas las operaciones
son de orden logarı́tmico en el peor de los casos. Por lo tanto las funciones requeridas para operar con una Heap
Binaria son de fácil implementación.
Una Heap Binaria consta de:
Un arreglo que contiene los datos del árbol.
Un valor que indica el número de elementos almacenados.
5 HEAP BINARIA 6
Propiedades
Cumple con dos propiedades:
Estructural
De Orden
Como se dijo anteriormente, al cumplir la propiedad de ser un árbol binario, se puede guardar la estructura
en un arreglo, por lo que:
La raı́z será almacenada en la posición 1
Para un elemento que está en la posición i: * El hijo izquierdo será el que esté en la posición 2*i. * El hijo
derecho será el que esté en la posición 2*i+1. * El padre estará en la posición i/2.
Operaciones posibles
Las colas de prioridades soportan dos operaciones posibles:
Insert: inserta un elemento en la estructura.
Delete: Encuentra, recupera y elimina un elemento (Si es Max el elemento será el mayor,si es Min el
elemento será el menor).
Insertar: El elemento a insertar se coloca al final del arreglo. Para garantizar y en todo caso restaurar la
propiedad de orden de los elementos se debe realizar un filtrado ascendente.
Lo que hace el filtrado ascendente ir situando el nuevo valor dependiendo si es una Max Heap o una Min
Heap; se intercambia el valor de forma ascendente hasta que su padre es mayor al nuevo valor (Max Heap)
o menor (Min Heap).
En el peor de los casos el algoritmo recorre la altura de la heap, por lo que serı́a de *orden logarı́tmico*
Eliminación: Para el eliminar un dato de una Heap Binaria se hace lo siguiente:
Se guarda el dato de la raı́z en alguna variable.
Se intercambian los elementos entre la primer posición (que fue guardada previamente) y la última posición.
De ésta forma en la primer posición (en el nodo raı́z) se tiene el elemento con menos prioridad.
Min Heap: Se tiene el elemento más grande en la raı́z luego de haber hecho el cambio (Recordar que los
más chicos son los que se encuentran arriba de todo).
Max Heap: Se tiene el elemento más chico en la raı́z luego de haber hecho el cambio (Recordar que los
más grandes son los que se encuentran arriba de todo).
Se debe realizar un filtrado hacia abajo posicionando nuevamente el elemento de la primera posición donde
corresponde. Ésto se hace para reestablecer nuevamente la propiedad de Orden de la estructura de datos.
Lo distinto es que el filtrado descendente, va a preguntar siempre por el valor de ambos nodos hijos; en
cambio en el filtrado ascendente sólo se hace preguntando por el padre (en un solo sentido).
6. Tablas de Dispersión
Caracterı́sticas
La dispersión se utiliza para realizar inserciones, eliminaciones y búsquedas en un tiempo promerdio cons-
tante. Un ejemplo de ello son los diccionarios que albergan elementos relacionados con una clave:valor. Para
obtener el valor(dato guardado), se debe buscar por la clave. La forma de almacenar la tabla de dispersión es
mediante una lista de tamaño fijo N que contenga las claves. La caracterı́stica es que:
A cada clave se le hace corresponder un número entre 0 y N-1 y se coloca en el elemento correcto de la
lista.
A la correspondencia se le denomina Función de dispersión.
Se crea una función que calcula la posición en la lista (array). Cuando se solicita el ı́ndice, le devuelve el
dato guardado en la posición solicitada. Un ejemplo puede ser que una función hash() calcule el ı́ndice mediante
la suma de las letras a guardar como dato. Al solicitar el dato mediante el ı́ndice de la lista de elementos, el
dato ya está alojado dentro de la clave y la única forma es obtenerlo mediante la función hash()
Requisitos
Todas las funciones de dispersión deben:
Dispersión Abierta
El problema es cuando se encuentran colisiones. Por ejemplo en el caso anterior, que se encuentre que el dato
a guardar sea ”josé” y que ”roberto” también den el mismo valor para el ı́ndice; a esto se le denomina Colisión.
La Dispersión abierta maneja las colisiones teniendo una lista de todos los elementos que se dispersan en
el mismo valor es decir, cuando se da una colisión en la cual se solicita el mismo ı́ndice, en ese elemento se tiene
una sublista con todos los elementos colisionados.
Para efectuar la búsqueda se utiliza la función de dispersión para determinar qué lista recorrer.
Para insertar un elemento, se recorre la lista adecuada, de haber una colisión, entonces en la sublista se
sitúa o al comienzo o al final.
No sólo se pueden tener estructuras de listas enlazadas para almacenar las colisiones, sino también se pueden
usar árboles binarios de búsquedas o AVL.
Dispersión Cerrada
A diferencia de la dispersión abierta, lo que se realiza con la dispersión Cerrada es buscar celdas alter-
nativas hasta encontrar una vacı́a. Esto lo hace utilizando además de la función de dispersión otra función más
que sea o de tipo lineal o de tipo cuadrática:
Dispersión Cerrada con Exploración Lineal: Es la forma más primaria y simple de resolver una colisión
entre claves, al aplicar una función de dispersión. Supóngase que se tiene un elemento de clave x , la dirección que
devuelve la función h(x) = p , si esta posición ya está ocupada por otro elemento se ha producido una colisión.
7 GRAFOS 8
La forma de resolver está colisión con exploración lineal consiste en buscar la primera posición disponible que
siga a p.
La secuencia de exploración que se genera es lineal:
p + 0, p + l, p + 2...p + n
y ası́ consecutivamente hasta encontrar una posición vacı́a. La tabla se ha de considerar circular, de tal forma
que siendo
p−l
la última posición, la siguiente sea la posición O.
Los pasos a seguir para un arreglo de 11 elementos serı́an según para cada iteración la siguiente función:
Donde % 11 = Mod de 11 (11 tiene que ver con la cantidad de elementos de la lista enlazada).
Donde hash(x) es la función de dispersión.
Donde f(n) es una función lineal.
Donde n es el iterador.
Se pregunta: ¿Ese ı́ndice está vacı́o? SI, entonces se sitúa en ese lugar.
Se debe tener en cuenta que para tener un mayor rendimiento el tamaño de la lista a utilizar debe ser de
número primo, ası́ tiene menos números para los ı́ndices por los cuales iterar (ya que tiene menos divisores).
Dispersión Cerrada con Exploración Cuadrática: La resolución de colisiones con la exploración lineal
provoca que se agrupen los elementos de la tabla según se va acercando el factor de carga a 0. Una alternativa
para evitar la agrupación es la exploración cuadrática. Suponiendo que a un elemento con clave x le corresponde
la dirección p y que la posición de la tabla indexada por p está ocupada, el método de exploración o prueba
cuadrática busca en las direcciones
p + 0, p + 1, p + 4, p + 9...p + i2
considerando la tabla como un array circular (es una función cuadrática). El nombre de cuadrática para esta
forma de explorar se debe al desplazamiento relativo de
i2
7. Grafos
Definición
En matemáticas y ciencias de la computación, un grafo es un conjunto de objetos llamados vértices o nodos
unidos por enlaces llamados aristas o arcos, que permiten representar relaciones binarias entre elementos de un
conjunto. Son objeto de estudio de la teorı́a de grafos. Es un conjunto de vértices o nodos, con una relación
entre ellos.
Los grafos pueden ser Dirigidos o No Dirigidos.
7 GRAFOS 9
Los Grafos Dirigidos son aquellos grafos los cuales la relación entre sus nodos no son simétricas, son dos
pares ordenados de nodos a partir del cual se llega a un Nodo pasando del primero hacia el segundo, pero nunca
en relación inversa; por lo tanto se dice que las aristas tienen un sentido, una dirección. Las aritas representa
la relación de pares ordenados entre los nodos. Se tiene un orden sobre cómo recorrer el conjunto de nodos, es
decir el grafo.
Los Grafos No Dirigidos son aquellos grafos los cuales sus nodos tienen una relación simétrica; se puede
llegar de A hacia B (en caso de que estén unidos por una arista) y de B hacia A, en ambos sentidos, por lo que
se dice que sus aristas no tienen sentido, no tienen dirección.
Los caminos en un grafo está definido como las aristas por las que se tiene que pasar para llegar de un
nodo a otro; siempre que esas aristas estén conectadas al propio nodo camino. Observar que serán distinto los
caminos dependiendo de si el grafo es Dirigido o No Dirigido.
La longitud de un camino está definida como la cantidad de aristas por las que se tiene que pasar para
llegar de un nodo A hasta un nodo B. Las aristas también pueden tener peso, o tener un costo, dependiendo el
abordaje que se tenga del grafo, en tal caso serı́a un *Grafo Ponderado*.
El Camino Simple es aquel en el que todos sus vértices (excepto tal vez, el primero y el último) son
distintos.
Se denomina Ciclo a los nodos que están interconectados de tal manera que el camino pasa por el nodo
inicio, de forma que se puede realizar el mismo camino varias veces.
Se denomina un Grafo Conexo si entre cada dos nodos hay un camino.
Se denomina Grafo No Dirigido Conexo cuando existe un camino desde cualquier nodo a cualquier otro.
Si un Grafo Dirigido tiene también la propiedad de que exista un camino desde cualquier nodo a otro, se
dice que ese Grafo Dirigido es fuertemente Conexo.
En cambio, si un Grafo Dirigido NO es fuertemente conexo, pero el grafo subyacente (sin sentido en las
aristas, por lo tanto Grafo NO Dirigido) es conexo, se dice que el Grafo es Débilmente Conexo
Se dice que un nodo es Adyacente a otro si entre ellos existe una arista que los una.
Se dice que un nodo es Alcanzable desde otro, si existe un camino desde el primero que permita llegar
hasta el segundo.
Representaciones
Los grafos pueden ser representados por Matrices de adyacencias. Estas matrices permiten alojar toda la
información de los nodos y los distintos caminos entre ellos. En la misma matriz, también es posible guardar los
pesos de los distintos caminos, alojando de esa manera la información de un Grafo Ponderado. Las Matrices de
adyacencia son útiles cuando el grafo es pequeño. En caos de que el grafo esté conformado con muchos nodos,
la matriz se vuelve muy grande, por las dimensiones que significa mantener varios nodos.
Recorridos
Existen dos tipos de recorridos de orden lineal, dentro de los grafos:
En profundidad (DFS): Para éste tipo de recorrido se mantiene la premisa de que si no se haya finalizado
de explorar uno de los caminos no se comienza con el siguiente. Un camino deja de explorarse cuando se llega a
un vértice ya visitado. El recorrido finaliza cuando se hayan visitado todos los nodos alcanzables desde el nodo
inicial. Un recorrido en profundidad parte de un nodo inicial, visita toda una rama, luego otra hasta que todos
los nodos hayan sido visitados. Los pasos son los siguientes:
1. Marca todos los nodos como no visitados
2. Elige un nodo como punto de partida
9 // Recordar que esta es la lista que se ve hacia hacia la imp lementa cion
10 HashSet <T > visitados = new HashSet <T >() ;
11
12 // Es el que llama r ecursiva mente a lo demas
13 DFS ( origen , visitados ) ;
14
15 }
16
17 // Como modificamos la firma que ahora tiene dos parametros
18 private void DFS ( Vertice <T > origen , HashSet <T > visitados ) {
19
20 // La Key es lo que genera el Hash
21 visitados . Add ( origen . getDato () ) ;
22
23 Console . WriteLine ( origen . getDato () ) ;
24
25 foreach ( Arista <T > A in origen . getAdyacentes () ) {
26
27 Vertice <T > V = A . getDestino () ;
28
29 if ( ! visitados . Contains ( V . getDato () ) ) {
30 DFS (V , visitados ) ;
31 }
32 }
33 }
En amplitud (BFS): Un recorrido en anchura se refiere a recorrer un grafo por niveles, es decir, partiendo
de un nodo inicial recorro todos sus vecinos, posteriormente los vecinos de los vecinos hasta que todos los nodos
hayan sido visitados. Es decir, tomando en cuenta un nodo inicial, se visitan todos los nodos adyacentes a ese.
En ambos recorridos se debe tener una lista para mantener los nodos visitados y de esta forma hacer más
eficiente el algoritmo.
Ordenación Topológica
La ordenación topológica se suele iniciar en aquellos nodos que poseen pocas aristas, para que no haya
problemas en recorrer varias veces el mismo camino de inicio. La ordenación es posible si el grafo es cı́clico.
Recordar que la ordenación NO es única, sino que se puede realizar de varias formas. Los algoritmos usuales
para el ordenamiento topológico tienen un tiempo de ejecución de la cantidad de nodos más la cantidad de
aristas:
O(|V | + |E|)
Siendo V = Nodos y E = Aristas. Uno de los algoritmos, trabaja eligiendo los vértices del mismo orden como
un eventual orden topológico. Primero, busca la lista de los ”nodos iniciales” que no tienen aristas entrantes y
los inserta en un conjunto S; donde al menos uno de esos nodos existe si el grafo es acı́clico.