Sei sulla pagina 1di 11

Complejidad Temporal

Y Estructura de Datos

Emiliano Salvatori
Junio 2019

1. Introducción
Algoritmos de implementacion general
Insertar, Borrar, Buscar
Caminos mı́nimos

Ordenación

¿Para qué sirve la materia?


Para saber organizar la información de acuerdo al problema a resolver; la estructura de datos son formas de
organización de los datos. Ya que de la organización depende:
La implementación de algunas operaciones, dependiendo de cómo se ordene la información algunas
operaciones son más eficientes que otras, también son más fáciles de implementar que si la información
está ordenada de forma distinta.

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:

Cada nodo puede tener a lo sumo dos nodos hijos.


Cuando un nodo NO tiene ningún hijo, se lo denomina nodo HOJA.
Los nodos que dependen de un mismo nodo padre, se denominan nodos HERMANOS.

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.

Rebalanceo del árbol


Luego de insertar un elemento, se debe corroborar y en caso de producirse, restaurar el balanceo. Y se hace
de la siguiente forma:
Se recorre el camino que siguió a la insersión pero de forma inversa.
Se controla en cada nodo, la propiedad de Balanceo
En caso de que haya un desbalanceo, se restaura la propiedad mediante el método denominado ROTACIÓN
el cual reestructura el árbol preservando la condición de orden propia los AVL.
El proceso termina cuando se llega a la raı́z del árbol y se comprueba que está balanceado.

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]

Tiempo de ejecución de las operaciones en los AVL


Las operaciones:
Busqueda
Inserción
Eliminación
Todas estas operaciones pueden llegar a recorrer en el PEOR de los casos, la altura completa del árbol. Pero
el recorrido será de orden logarı́tmico (ya que el árbol AVL tiene la propiedad de estar balanceado). Se debe
tener en cuenta que:
Busqueda: La misma será de orden logarı́tmico cuando el árbol sea de tipo AVL.
Inserción: el método es similar a un ABB, se debe actualizar la altura en cada nodo y realizar rotaciones
si es necesario. Provoca una única reestructuración (*PREGUNTAR:* si se refiere a una única rotación)
Eliminación: el método es similar a un ABB, se debe actualizar la altura en cada nodo y realizar rota-
ciones si es necesario. Provoca varias reestructuraciones (*PREGUNTAR:* si se refiere a realizar varias
rotaciones)
TODAS las operaciones son de orden logarı́tmico por lo que son bastante eficientes; el balanceo es poco
costoso.

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.

Una lista con todos sus hijos.


La misma puede estar representada mediante:
Arreglos aunque la desventaja es el espacio que ocupa, ya que se debe de definir en tiempo de compilación.

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.

Referencia al hijo más izquierdo.


Referencia al hermano derecho.

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

Conversión de una Heap


Para convertir una lista en una Heap Binaria es lo siguiente:
Si no se tiene una lista, entonces se ingresan los elementos de a uno de forma ordenada, pero ¿cuál serı́a
el inconveniente? Que ésta operación serı́a lineal.
De otra forma se podrı́an ingresar los datos de forma desordenada y tener un algoritmo que permita
ordenar ésta lista de forma que quede la estructura de una Heap Binaria. Éste algoritmo lo que hace
básicamente es: elegir el menor de los hijos y compararlo con el padre hasta llegar al último elemento. De
ésta forma el algoritmo serı́a de orden logarı́tmico.
Se comienza desde el elemento que se encuentra en la mitad del arreglo, es decir Tamaño/2. En ésta
posición se garantiza que caerá en el último nodo padre; los que queden sin procesar,serán hojas. Se
procesa el nodo y se pasa al siguiente restándole 1 a la posición del elemento por donde se comienza.
Por cada nodo se debe preguntar: ¿Mis hijos derechos o izquierdos son menores (MinHeap) mayores
(MaxHeap) que yo?. En caso de que sea cierto, intercambiar los nodos de forma que siempre el de mayor
prioridad sea el padre de los demás.
Se termina cuando se llega a la raı́z del árbol.
6 TABLAS DE DISPERSIÓN 7

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:

Calcularse de una forma sencilla, siendo lineal su tiempo de ejecución.


Distribuir de forma uniforme las claves.

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:

f uncionHash() = valorhasheado + f (n)

Donde las funciones pueden ser:


Lineal:
f (n) = n
Cuadrática:
f (n) = n2
Por lo tanto la insersión se denomina Exploración lineal si se elije una función lineal o Exploración cuadrática
cuando se elije una función cuadrática como segunda función.

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:

p = (hash(x) + f (n)) %11

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.

Resolviendo para cada iteración:

p = hash(x) + f (0)] %11

Se pregunta: ¿Ese ı́ndice está vacı́o? NO

p = hash(x) + f (1)] %11

Se pregunta: ¿Ese ı́ndice está vacı́o? NO

p = hash(x) + f (2)] %11

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

3. Se marca ese nodo como visitado


4. Consulta por el nodo adyacente, en caso de no haber sido visitado se llama recursivamente 3 y 4.
Ejemplo de código DFS:
1
2 public void DFS ( Vertice <T > origen ) {
3
4 /*
5 * Es la forma en que se mantiene una lista de nodos visitados . Se utiliza HashSet
porque
6 * es mas rapido para buscar y obtener el dato .
7 */
8
7 GRAFOS 10

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.

1. Marca todos los nodos como no visitados.


2. Elige un nodo como punto de partida.
3. Se marca ese nodo como visitado.

4. Marcar todos los nodos adyacentes al nodo U


5. Repetir el proceso para cada nodo adyacente a U, siguiendo el orden en que fueron visitados.
Ejemplo de código DFS:
1
2 public void BFS ( Vertice <T > origen ) {
3
4 // seteamos como no visitados
5 HashSet <T > visitados = new HashSet <T >() ;
6
7 // creamos una cola
8 Queue < Vertice <T > > cola = new Queue < Vertice <T > >() ;
9
10 // lo marcamos como visitado
11 visitados . Add ( origen . getDato () ) ;
12
13 // Se encola
14 cola . Enqueue ( origen ) ;
15
16 if ( cola . Count != 0) {
17
18 Vertice <T > V = cola . Dequeue () ;
19
20 foreach ( Arista <T > A in V . getAdyacentes () ) {
21
22 Vertice <T > ad = A . getDestino () ;
23
24 if ( ! visitados . Contains ( ad . getDato () ) ) {
25 cola . Enqueue ( ad ) ;
26 }
27 }
28
29 // Se procesa el dato
30 Console . WriteLine ( ad ) ;
31 }
32 }
7 GRAFOS 11

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.

La primer versión del algoritmo es:


1. Seleccionar un nodo con grado de entrada cero (esto es para tener menos cantidad de posibles caminos
cı́clicos, evitando de esta forma volver a repetir el camino).
2. Visitar el nodo.
3. Eliminar el nodo visitado junto con sus aristas.
4. Repetir el paso 1 hasta que queden visitados todos los nodos.

Potrebbero piacerti anche