Académique Documents
Professionnel Documents
Culture Documents
P
Escuela Académico Profesional de Ingeniería de Sistemas
Práctica Nº4
Guía 4:
Algoritmos voraces, Conjuntos y dispersión
Código: 09200030
El algoritmo de Kruskal es un algoritmo de la teoría de grafos para encontrar un árbol recubridor mínimo en un grafo
conexo y ponderado. Es decir, busca un subconjunto de aristas que, formando un árbol, incluyen todos los vértices y donde el
valor total de todas las aristas del árbol es el mínimo. Si el grafo no es conexo, entonces busca un bosque expandido mínimo
(un árbol expandido mínimo para cada componente conexa). El algoritmo de Kruskal es un ejemplo de algoritmo voraz.
El algoritmo de Prim es un algoritmo perteneciente a la teoría de los grafos para encontrar un árbol recubridor mínimo en
un grafo conexo, no dirigido y cuyas aristas están etiquetadas.
En otras palabras, el algoritmo encuentra un subconjunto de aristas que forman un árbol con todos los vértices, donde el peso
total de todas las aristas en el árbol es el mínimo posible. Si el grafo no es conexo, entonces el algoritmo encontrará el árbol
recubridor mínimo para uno de los componentes conexos que forman dicho grafo no conexo.
El algoritmo fue diseñado en 1930 por el matemático Vojtech Jarnik y luego de manera independiente por el científico
computacional Robert C. Prim en 1957 y redescubierto por Dijkstra en 1959. Por esta razón, el algoritmo es también
conocido como algoritmo DJP o algoritmo de Jarnik.
El algoritmo incrementa continuamente el tamaño de un árbol, comenzando por un vértice inicial al que se le van agregando
sucesivamente vértices cuya distancia a los anteriores es mínima. Esto significa que en cada paso, las aristas a considerar son
aquellas que inciden en vértices que ya pertenecen al árbol.
El árbol recubridor mínimo está completamente construido cuando no quedan más vértices por agregar.
// Inicializamos todos los nodos del grafo. La distancia la ponemos a infinito y el padre de cada nodo a NULL
// Encolamos, en una cola de prioridad donde la prioridad es la distancia, todas las parejas <nodo,distancia> del grafo
por cada u en V[G] hacer
distancia[u] = INFINITO
padre[u] = NULL
Añadir(cola,<u,distancia[u]>)
distancia[s]=0
mientras cola != 0 hacer
// OJO: Se entiende por mayor prioridad aquel nodo cuya distancia[u] es menor.
u = extraer_minimo(cola) //devuelve el minimo y lo elimina de la cola.
por cada v adyacente a 'u' hacer
si ((v ∈ cola) && (distancia[v] > peso(u, v)) entonces
padre[v] = u
distancia[v] = peso(u, v)
Actualizar(cola,<v,distancia[v]>)
2. Considere una competición mundial en la cual hay dos equipos A y B, que juegan un máximo de 2n -1 partidas, y en donde
el ganador es el primer equipo que consiga n victorias. Suponemos que no hay posibilidad de empate y que los resultados de
todos los partidos son independientes y que para cualquier partida dada hay una probabilidad constante P de que A gane, y
por tanto una probabilidad constante Q = 1-P de que gane B. Sea P(i,j) la probabilidad de que el equipo A gane el campeonato,
cuando todavía le falta i victorias más para conseguirlo, mientras que el equipo B necesita j victorias para ganar. Por ejemplo,
antes del primer partido del campeonato, la probabilidad de que gane el equipo A es P( n , n): ambos equipos necesitan
todavía n victorias para ganar el campeonato. Si A necesita cero victorias más, entonces lo cierto es que ya ha ganado el
campeonato y por tanto P(0, i ) = 1 con 1 <= i <=n. De manera similar, si el equipo B necesita cero victorias más, entonces ya
ha ganado el campeonato y por tanto P(i,0) = 0, con 1 <= i <=n .
Como no se puede dar una situación en la que A y B ganen todas las partidas que necesitan P(0,0) = 0. Carece de significado
dado que A gana cualquier partida con una probabilidad P y pierde con una probabilidad Q
4. Construya los algoritmos para manejar TADs conjuntos, con representación de listas :
a) Miembro
Funcion miembro(Entero b)
Inicio
booleano mfalso
Nodo g
g = CAB
Mientras (g NO NULO)
Si(b = g.dato)
Mtrue
Fin_Si
g g.sgte;
Fin_Mientras
Devolver m
Fin
b) Union
Funcion union(Conjunto A,Conjunto B)
Inicio
Cadena msj ""
Nodo x
Nodo y
xCAB
yA.CAB
Si(x=NULO ˄ y=NULO)
msj" Conjuntos vacios"
Sino
msj" Union de conjuntos A y B"
Fin_Si
Mientras (x NO NULO)
B.adiciona(x.dato)
x x.sgte
Fin_Mientras
Mientras (y NO NULO)
Si(!B.miembro(y.dato))
B.adiciona(y.dato)
Fin_Si
y = y.sgte
Fin_Mientras
Devolver msj
Fin
c) Interseccion
Funcion interseccion(Conjunto A, Conjunto B)
Inicio
String msj ""
Nodo x
Nodo y
x=CAB
y=A.CAB
Si(x=null ˄ y=null)
msj=" Conjuntos vacios"
Sino
msj=" Interseccion de conjuntos A y B"
Si
Mientras (y != null)
x=this.CAB;
while (x !=null)
Si(A.miembro(x.dato))
B.adiciona(x.dato)
Fin_Si
Fin_Mientras
x x.sgte
Fin_Mientras
Devolver msj
Fin
d) Diferencia
Funcion diferencia(Conjunto A, Conjunto B)
Inicio
String msj ""
Conjunto C
Nodo x
Nodo y
Nodo z
YA.CAB
ZC.CAB
XCAB
Si(x=null ˄ y=null)
msj" Conjuntos vacios"
Sino
Msj" Diferencia de conjuntos A y B"
Interseccion(A,C)
xCAB
Mientras(x NO NULO)
SI( NO C.miembro(x.dato))
B.adiciona(x.dato)
Fin_Si
x = x.sgte
Fin_Mientras
Devolver msj
Fin
e) Complemento
Funcion complemento(Conjunto A, Conjunto B)
Inicio
String msj ""
Msjthis.diferencia(A,B)
return msj
Fin_Si
5. Una aplicación de algoritmos voraces lo constituye el codigo de Huffman. Investigue de que se trata.
La codificación de Huffman es una técnica para la compresión de datos, ampliamente usada y muy efectiva
Ejemplo : Fichero con 100.000 caracteres. Se sabe que aparecen 6 caracteres diferentes y la frecuencia de aparición de cada
uno de ellos es :
a b c d e f
Frecuencia 45 13 12 16 9 5
( en miles )
¿ Cómo codificar los caracteres para comprimir el espacio ocupado utilizando un código binario ?
Solución 1 : Código de longitud fija
Para 6 caracteres se necesitan 3 bits (300000 bits)
Solución 2 : Código de longitud variable en el que los más frecuentes tienen el código más corto. Restricción : ningún código
es prefijo de otro.
( 224000 bits )
Descodificación : Fácil porque ningún código es prefijo de otro código ⇒ NO hay ambigüedad.
Ejemplo :
101011101111011100 ≡ badadcf
¡ Es la única posibilidad !
Un árbol binario es una forma de representar el código prefijo que simplifica el proceso de descodificación :
el camino de la raíz a la hojas con la interpretación 0 a la izquierda y 1 a la derecha nos da el código de cada hoja.
0 100 1
86 14
0 1 0
58 28 14
0 1 0 1 0 1
55
a:45 0 1
25 30
0 1 0 1
0 1
f:5 e:9
Dado T el árbol binario que corresponde a una codificación prefijo, es fácil averiguar el número de bits necesarios para
codificar el fichero :
Para cada carácter c diferente del alfabeto C que aparece en el fichero,
sea f(c) la frecuencia de c en la entrada,
sea dT(c)la profundidad de la hoja c en el árbol T, entonces el número de bits requeridos es :
Algoritmo Greedy
Huffman inventó un algoritmo voraz que construye una codificación prefijo óptima.
Construye un árbol binario de códigos de longitud variable de manera ascendente de modo que [MIN] B(T).
Ejemplo de funcionamiento
Fase 1. : Caracteres colocados en orden creciente de frecuencia.
Fase 2. y posteriores : Fusionar hasta obtener un sólo árbol manteniendo la ordenación creciente.
0 1
f:5 e:9
14 d:16 25 a:45
0 1 0 1
0 1 0 1
0 1
f:5 e:9
a:45 55
0 1
25 30
0 1 0 1
0 1
f:5 e:9
Podemos suponer que f[b] ≤ f[c] y que f[x] ≤ f[y]. Además, se puede deducir que f[x] ≤ f[b] y f[y] ≤ f[c].
Se puede construir un nuevo árbol, T’, en el que se intercambia la posición que ocupan en T las hojas b y x.
= f[x].dT(x) + f[b].dT(b) –
f[x].dT’(x) – f[b].dT’(b) =
= f[x].dT(x) + f[b].dT(b) –
f[x].dT (b) –f[b].dT (x) =
6. Cuales son los elementos básicos para construir una estructura HASH
- Una función de dispersión que nos permita a partir de la clave obtener el índice donde estará el dato asociado a esa clave. Es
frecuente que existan dos claves distintas para las que la función de dispersión produzca el mismo indice.Esto se denomina
colisión, y las dos claves distintas que dieron lugar al mismo índice, se dicen sinónimas respecto a la función de dispersión
utilizada.
Encadenamiento. En lugar de buscar un agujero libre, lo que se hace es disponer de una serie de bloques como área de
desborde. Cuando se produce una colisión, el registro se sitúa en el área de desborde y mediante un puntero en el bloque
colisionado, se apunta a la dirección del bloque de desborde y la posición relativa del registro dentro del bloque. Además,
todos los registros que han colisionado en un mismo bloque se van encadenando mediante punteros.
Dispersión múltiple. Esta técnica de resolución de colisiones consiste en utilizar una segunda función de dispersión cuando
la primera ha producido una colisión. El objetivo es producir una nueva dirección que no provoque colisión. Normalmente, la
segunda función da una dirección de bloque situada en un área de desborde.
Direccionamiento abierto. Cuando se produce una colisión, el sistema hace una búsqueda lineal a partir del bloque al que
iba destinado el registro para encontrar un agujero donde insertarlo. Si se llega al final del archivo sin encontrar un agujero, se
continúa la búsqueda desde el principio.
Aunque la dispersión es el método de acceso directo más rápido a través del campo de dispersión, no es muy útil cuando
también se quiere acceder al archivo a través de otro campo. Ya que la mayoría de las funciones de dispersión que se utilizan
no mantienen el orden entre los registros, tampoco es útil cuando se quiere leer los registros ordenadamente.
Cuando se quiere hacer una búsqueda por un campo que no es de dispersión, resulta tan costoso como realizar la búsqueda en
un archivo desordenado. En este caso se prefiere realizar una búsqueda secuencial. Asimismo cuando se quiere eliminar un
registro, se presentan dos casos: El registro esta en el bloque, o se encuentra en una lista de desborde. Si esta en el bloque, se
elimina el registro del bloque y se remplaza por un registro de la lista de desborde. Si el registro a borrar está en la lista de
desborde, sólo hay que eliminarlo de ella. En este caso, será necesario mantener una lista enlazada de posiciones de desborde
no utilizadas. Si al actualizar un registro se modifica el campo de dispersión, es muy probable que el registro tenga que
cambiar de posición, por lo que habrá que borrarlo e insertarlo de nuevo.
9. Defina primera forma normal, segunda forma normal y tercera forma normal
10. Proponga un ejemplo en el que ilustre la primera forma normal, la segunda tercera forma normal
12. Que funciones de dispersión conoce. Proporciones un ejemplo de cada una de ellas
Método de residuo
Se toma la clave y se divide por el tamaño de la tabla o del archivo, y el residuo determina la posición relativa en la tabla o el
archivo. Normalmente para disminuir el número de colisiones es conveniente usar un numero primo igual o ligeramente
menor que el tamaño de la tabla.
M Tamaño de la tabla
I= RESIDUO(CLAVE/M) + 1
Genera valores entre 0 y M-1
I Permite determinar la posición en
la tabla
Método de cuadrados
Se eleva la clave al cuadrado, se toman los números centrales y se multiplican por el factor de conversión, con el objeto de
ajustarla el tamaño de la tabla
Ejemplo
Dada la clave de un producto 8254
El factor de conversión es 0.4
8254 * 8254 =68128516
128*0.4=51.2
Luego Aleatorio(8254) es 51
Método de desfasamiento
Se Suman los números de ambos extremos de la clave sobre los números centrales. Se suman y se multiplican por un factor
de conversión. Esta técnica se puede emplear cuando la clave es bastante grande con respecto a tamaño de la tabla.
Ejemplo
Dada la clave de un producto 483259782
El factor de conversión es 0.4
259 + 483 + 782 = 524
La suma se multiplica por el factor de conversión
524* 0.4 = 209.6
Luego Aleatorio(483259782) es 209