Академический Документы
Профессиональный Документы
Культура Документы
1.
2.
3.
4.
5.
Recursividad
Backtracking
"Dividir para Reinar"
Recursividad y Tabulacin
Mtodos Matemticos
Funciones discretas
Notacin O
Ecuaciones de recurrencia
6. Programacin Dinmica
7. Algoritmos Avaros
8. Casos de Estudio
Recursividad
Al programar en forma recursiva, buscamos dentro de un problema otro subproblema que posea su misma estructura.
}
}
public static void main(String[] args)
{
Hanoi(Integer.parseInt(args[0]), 1, 2, 3);
}
}
Backtracking
Esta tcnica de diseo de algoritmos consiste en resolver un problema por prueba y error. Se basa en generar todas las posibles
soluciones al problema utilizando recursin, probando cada una de ellas hasta encontrar aquella que lo resuelve. Por esto mismo, el tiempo
requerido para solucionar el problema puede ser muy alto (por ejemplo, exponencial en el tamao de la entrada).
Ejemplo: El laberinto.
Se tiene una matriz de caracteres de dimensiones N = filas x columnas, que representa un laberinto. Se comienza desde una posicin (i,j)
dada y el objetivo es encontrar la salida, permitiendo slo desplazamientos horizontales y verticales. La codificacin del laberinto es la
siguiente:
Carcter '*' indica la pared, y no se puede pasar por esa celda.
Carcter ' ' (espacio en blanco) indica el pasillo, y se puede pasar por esa celda.
Carcter '&' indica la salida del laberinto, el objetivo es encontrar una celda de este tipo en el laberinto.
* * * * * *
*
*
*
* * *
*
*
* * *
*
*
*
*
*
* * * * * *
* * *
*
*
&
*
*
*
*
*
*
*
* * *
El mtodo a implementar es boolean salida(char[][] x, int i, int j), que retorna true si a desde la posicin i,j se puede encontrar una salida.
Este problema se puede resolver utilizando backtracking de la siguiente forma:
Si en la posicin (i,j) que se est revisando hay un '*', no hay salida por ese camino y se retorna false.
Si en la posicin (i,j) que se est revisando hay un '&', se encontr la salida al laberinto y se retorna true.
Si en la posicin (i,j) que se est revisando hay un espacio, se prueba recursivamente si se llega a la salida por alguna de las cuatro
celdas vecinas (i+1,j), (i-1,j), (i,j+1), (i,j-1).
Si alguna de las llamadas retorna true, existe un camino hasta la salida y se retorna true. Si todas las llamadas recursivas retornan
false, no existe un camino hasta la salida y se retorna false.
Una primera versin de la funcin en Java que instancia este algoritmo podra ser la siguiente (supuesto: todo el borde del laberinto esta
rodeado por paredes, excepto en la(s) salida(s)):
public static salida1(char[][] x, i, j)
{
if (x[i][j] == '&') return true;
if (x[i][j] == '*') return false;
if (salida1(x, i+1, j)) return true;
if (salida1(x, i-1, j )) return true;
if (salida1(x, i, j+1)) return true;
if (salida1(x, i, j-1,)) return true;
return false;
}
Esta implementacin tiene el problema que puede generar un nmero infinito de llamados recursivos. Por ejemplo, si se invoca a salida1(x,
a, b) y es una celda de pasillo, sta invocar recursivamente a salida1(x, a+1, b). Si la celda (a+1,b) es una celda de pasillo, puede
potencialmente invocar a salida1(x, a+1-1, b), generandose as un ciclo infinito de llamados recursivos. Para evitar este problema, se puede
ir marcando (por ejemplo, con el carcter '+') las celdas por donde el algoritmo ya pas, evitando caer en el ciclo infinito:
public static boolean salida(char[][] x, i, j) // correcto
{
if (x[i][j] == '&') return true;
if (x[i][j] == '*' || x[i][j] == '+') return false;
x[i][j] = '+';
if (salida(x, i+1, j)) return true;
if (salida(x, i-1, j)) return true;
if (salida(x, i, j+1)) return true;
if (salida(x, i, j-1)) return true;
return false;
}
converted by W eb2PDFConvert.com
Propuesto: Modifique la implementacin de la funcin salida para que retorne un String que contenga la secuencia de celdas (i,j) por donde
hay que pasar para llegar a la salida del laberinto. El nuevo encabezado de la funcin es public static String salida(char[][] x, int i, int j).
Finalmente, implemente la funcin salida de modo que retorne la secuencia de celdas ms corta para llegar a la salida.
Teorema
Las ecuaciones de la forma
T(n) = p*T(n/q) + K*n
tienen solucin
T(n) = O(nlogq p)
T(n) = O(n)
T(n) = O(n log n)
(p>q)
(p<q)
(p=q)
(Nota: este Teorema, conocido como el Teorema Maestro, ser demostrado ms adelante.)
Por lo tanto la solucin del problema planteado (p=4, q=2) es
T(n) = O(nlog2 4) = O(n2)
converted by W eb2PDFConvert.com
Recursividad y Tabulacin
A veces la simple recursividad no es eficiente.
(n>=2)
0
0
1
1
2
1
3
2
4
3
5
5
6 7 8 9 10 11 . . .
8 13 21 34 55 89 . . .
Se puede demostrar que los nmeros de Fibonacci crecen exponencialmente, como una funcin O(n) donde =1.618....
El problema que se desea resolver es calcular fn para un n dado.
La definicin de la recurrencia conduce inmediatamente a una solucin recursiva:
public static int F( int n )
{
if( n<= 1)
return n;
else
return F(n-1)+F(n-2);
}
Lamentablemente, este mtodo resulta muy ineficiente. En efecto, si llamamos T(n) al nmero de operaciones de suma ejecutadas para
calcular fn, tenemos que
T(0) = 0
T(1) = 0
T(n) = 1 + T(n-1) + T(n-2)
La siguiente tabla mustra los valores de T(n) para valores pequeos de n:
n
T(n)
0
0
1
0
2
1
3
2
4
4
5 6 7 8 9 10 ...
7 12 20 33 54 88 ...
=
=
=
=
fn-1+gn-1
fn-1
1
0
A = [ 1 1 ]
[ 1 0 ]
Mtodos Matemticos
Funciones discretas
En general, para estudiar la eficiencia de los algoritmos se utilizan funciones discretas, que miden cantidades tales como tiempo de
ejecucin, memoria utilizada, etc. Estas funciones son discretas porque dependen del tamao del problema, que se denotar como n. Por
ejemplo, n podra representar el nmero de elementos en un conjunto a ordenar.
Notacin: f(n) o bien fn representa el tiempo requerido por un algoritmo dado resolver un problema de tamao n. Tambin se utiliza T(n) o
Tn.
En anlisis de algoritmos, cuando n es grande (asintticamente) lo que importa es el orden de magnitud de la funcin: cuadrtica, lineal,
logartmica, exponencial, etc.. Para n pequeo, la situacin puede ser distinta.
converted by W eb2PDFConvert.com
Notacin O
Se dice que una funcin f(n) es O(g(n)) si existe una constante c > 0 y un n0 >= 0 tal que para todo n >= n0 se tiene que f(n) <= cg(n) (cota
superior de un algoritmo).
Se dice que una funcin f(n) es (g(n)) si existe una constante c > 0 y un n0 >= 0 tal que para todo n >= n0 se tiene que f(n) >= cg(n) (cota
inferior).
Se dice que una funcin f(n) es (g(n)) si f(n) = O(g(n)) y f(n) = (g(n)).
Ejemplos:
3n = O(n)
2 = O(1)
2 = O(n)
3n + 2 = O(n)
An2+ Bn + C = O(n2)
Alog n + Bn + C n log n + Dn2 = ?
3 = (1)
3n = (n)
3n = (1)
3n + 2 = (n)
3n + 2 = (n)
En orden ascendente: 1/n, 1, log log n, log n, sqrt(n), n, n log n, n1+e, n2, nk, 2n.
Ecuaciones de recurrencia
Son ecuaciones en que el valor de la funcin para un n dado se obtiene en funcin de valores anteriores. Esto permite calcular el valor de la
funcin para cualquier n, a partir de condiciones de borde (o condiciones iniciales)
Ejemplo: Torres de Hanoi
an = 2an-1 + 1
a0 = 0
Ejemplo: Fibonacci
fn = fn-1 + fn-2
f0 = 0
f1 = 1
converted by W eb2PDFConvert.com
Ejemplo: resolver la ecuacin de recurrencia obtenida para el problema de las Torres de Hanoi.
Propuesto: plantee y resuelva la ecuacin de recurrencia para la funcin salida, que resuelve el problema del laberinto.
Ecuaciones lineales con coeficientes constantes
Estas ecuaciones son de la forma:
converted by W eb2PDFConvert.com
Se define:
Teorema Maestro
Las ecuaciones de la forma:
tienen solucin:
Demostracin
"Desenrollando" la ecuacin una vez:
converted by W eb2PDFConvert.com
Finalmente, se obtiene:
Programacin Dinmica
Al igual que en dividir para reinar, la tcnica de diseo de programacin dinmica divide un problema en varios subproblemas con la
misma estructura que el problema original, luego se resuelven dichos subproblemas y finalmente, a partir de stos, se obtiene la solucin al
problema original. La diferencia radica en que la programacin dinmica se ocupa cuando los subproblemas se repiten, como en el clculo
de los nmeros de Fibonacci. En este caso, en vez de usar recursin para obtener las soluciones a los subproblemas stas se van
tabulando en forma bottom-up, y luego estos resultados son utilizados para resolver subproblemas ms grandes. De esta forma, se evita el
converted by W eb2PDFConvert.com
Propuesto: Demuestre por contradiccin que, en el problema de la multiplicacin de una cadena de matrices, necesariamente las
soluciones a los subproblemas deben ser las ptimas para poder alcanzar el ptimo global.
Con esto ya es posible formular una solucin recursiva al problema. Dado que no se conoce de antemano la posicin k ptima, se prueban
todas las opciones (k = 1, 2, ..., n-1), y el algoritmo retorna aquel k que minimice el nmero de multiplicaciones escalares. Para resolver los
subproblemas se utiliza recursin. En general, si se est resolviendo el intervalo A i...Aj, se prueba con k = i, i+1, i+2, ..., j-1. Si m[i,j]
representa el costo mnimo en multiplicaciones escalares para calcular la multiplicacin de la cadena A i...Aj, para encontrar el ptimo es
necesario resolver:
m[i,j] = 0
min {m[i,k] + m[k+i,j] + p(i-1)*p(k)*p(j)}
i <= k < j
si i == j
si i < j
Los valores m[i,j] contienen el resultado ptimo para subproblemas del problema original. Para recordar qu valor de k fue el que minimiz
el costo, se puede ir almacenando en otra tabla s, en la posicin s[i,j]. Esta solucin se puede implementar directamente utilizando
recursin, pero el algoritmo resultante es muy costoso.
Propuesto: Escriba la ecuacin de recurrencia que corresponde al costo del algoritmo recursivo.
La solucin a esta ecuacin de recurrencia es exponencial, de hecho no es mejor que el costo de la solucin por fuerza bruta. Dnde
radica la ineficiencia de la solucin recursiva? Al igual que en Fibonacci, el problema es que muchos de los llamados recursivos se repiten,
es decir, los subproblemas se "traslapan" (overlapping problems). En total, se requiere realizar un nmero exponencial de llamados
recursivos. Sin embargo, el nmero total de subproblemas distintos es mucho menor que exponencial.
bottom-up. El siguiente seudocdigo muestra cmo se puede implementar el algoritmo que utiliza programacin dinmica:
public static int multiplicacionCadenaMatrices(int[] p, int[][] m, int[][] s)
{
// Matriz Ai tiene dimensiones p[i-1] x p[i] para i = 1..n
// El primer indice para p es 0 y el primer indice para m y s es 1
int n = p.length - 1;
for (int i = 1; i <= n; i++)
{
m[i][i] = 0;
}
for (int l = 2; l <= n; l++)
{
for (int i = 1; i <= n - l + 1; i++)
{
int j = i + l - 1;
m[i][j] = Integer.MAX_VALUE;
for (int k = i; k <= j-1; k++)
{
int q = m[i][k] + m[k+1][j] + p[i-1]*p[k]*p[j];
if (q < m[i][j])
{
m[i][j] = q;
s[i][j] = k;
}
}
}
}
return m[1][n];
}
Finalmente, se puede observar que este algoritmo toma tiempo O(n3), mucho mejor que la solucin recursiva, y ocupa espacio O(n2).
Propuesto: Ocupando el resultado obtenido en la tabla s, implemente un algoritmo que calcule el valor de multiplicar la secuencia de
matrices utilizando el nmero ptimo de multiplicaciones escalares.
Nota: tambin es posible implementar el algoritmo recursivo, pero slo se hace el llamado la primera vez que se invoca cada recursin y se
almacenan los resultados en las tablas correspondientes. Si el valor ya est tabulado, se lee directamente de la tabla, evitando efectuar la
recursin para el mismo subproblema ms de una vez. A esto se le conoce como memoization.
Algoritmos Avaros
En problemas de optimizacin, un algoritmo avaro siempre elige la opcin que parece ser la mejor en el momento que la toma. Si bien esto
no resuelve todo problema de optimizacin (puede caer en un ptimo local), hay problemas para los cuales una estrategia avara encuentra
siempre el ptimo en forma eficiente.
}
La estrategia del algoritmo propuesto es avara, ya que cada vez que es posible se agrega una actividad que es mutuamente compatible
con las que ya estn en el conjunto A. Este algoritmo toma tiempo O(n) en realizar la asignacin de actividades. Falta demostrar que la
asignacin de actividades realizada por el algoritmo avaro es maximal.
Teorema: el algoritmo implementado en la funcin asignacionActividadesAvaro produce un conjunto maximal de actividades mutuamente
compatibles.
Demostracin: Sea A una solucin optima para el problema, y suponga que las actividades en A estn ordenadas por tiempo de trmino
de cada actividad. Supongamos que la primera actividad en A tiene ndice k. Si k = 1, entonces A comienza con una decisin avara. Si k >
1, se define B = A - ak U a1. Dado que A es una solucin ptima y las tareas estn ordenadas, a 1 tiene que ser mutuamente compatible con
la segunda actividad en A, por lo que B tambin es una solucin ptima. Es decir, toda solucin ptima contiene a la actividad cuyo tiempo
de trmino es el menor de todos, en otras palabras, toda solucin ptima comienza con una decisin avara. Por ltimo, la solucin A' = A a1 es una solucin ptima para el problema de asignacin de tareas para el conjunto S' = {i en S: tinii >= tfin1}, es decir, S' contiene todas
las actividades restantes en S que son mutuamente compatibles con a1 (propuesto: demuestre por contradiccin que para el conjunto S' no
existe una solucin ptima B' con ms actividades que A'). Esto muestra que el problema de asignacin de actividades tiene subestructura
ptima: se define un problema ms pequeo (S') con la misma estructura que el problema original, cuya solucin ptima es parte de la
solucin al problema original. Por induccin en el nmero de decisiones tomadas, el tomar la decisin avara en cada subproblema permite
encontrar la solucin ptima al problema original.
Casos de Estudio
Se estudiarn los siguientes problemas:
Subsecuencia de suma mxima.
Multiplicacin de matrices.
Subsecuencia comn ms larga.
converted by W eb2PDFConvert.com
Arreglos.
Punteros y variables de referencia.
Listas enlazadas.
rboles.
rboles binarios.
rboles generales.
Toda la informacin que se maneja dentro de un computador se encuentra almacenada en su memoria, que en trminos simples es una
secuencia de caracteres (bytes) en donde se encuentran las instrucciones y datos a los que se accede directamente a travs del
procesador del computador.
Los sistemas o mtodos de organizacin de datos que permiten un almacenamiento eficiente de la informacin en la memoria del
computador son conocidos como estructuras de datos. Estos mtodos de organizacin constituyen las piezas bsicas para la construccin
de algoritmos complejos, y permiten implementarlos de manera eficiente.
En el presente captulo se presentan las estructuras de datos bsicas como son arreglos, listas enlazadas y rboles, con las cuales se
implementarn posteriormente los tipos de datos abstractos.
Arreglos
Un arreglo es una secuencia contigua de un nmero fijo de elementos homogneos. En la siguiente figura se muestra un arreglo de enteros
con 10 elementos:
converted by W eb2PDFConvert.com
incluso es posible realizar aritmtica de punteros para realizar operaciones de manera muy eficiente, a cambio de "oscurecer" el cdigo del
programa y con una alta probabilidad de cometer errores de programacin dficiles de detectar.
En Java no se puede declarar punteros de manera explcita ni tampoco realizar aritmtica de punteros. Por lo tanto es imposible en Java
tener un puntero a cualquiera de los tipos primitivos: enteros, reales, caracteres y booleanos. Los strings y arreglos no son tipos primitivos
en Java.
Una variable de referencia, o simplemente una referencia, es una variable que almacena la direccin de memoria en donde se ubica un
objeto. Ntese que si bien la definicin es prcticamente idntica a la de puntero, la diferencia radica en que una referencia slo puede
apuntar a objetos residentes en memoria, lo cual excluye a los tipos primitivos. A partir de esta definicin se puede concluir que toda
variable en Java, que no sea de tipo primitivo, es una referencia.
Por ejemplo, todas las clases en Java heredan de la clase Object. Una instancia de esta clase se declara como:
Object aux=new Object();
La variable aux es una referencia a un objeto de la clase Object que permite saber la ubicacin de dicho objeto dentro de la memoria,
informacin suficiente para poder operar con l. Intuitivamente, la referencia es como una "flecha" que nos indica la posicin del objeto que
apunta:
Listas enlazadas
Una lista enlazada es una serie de nodos, conectados entre s a travs de una referencia, en donde se almacena la informacin de los
elementos de la lista. Por lo tanto, los nodos de una lista enlazada se componen de dos partes principales:
class NodoLista
{
Object elemento;
NodoLista siguiente;
}
La referencia contenida en el nodo de una lista se denomina siguiente, pues indica en dnde se encuentra el siguiente elemento de la lista.
El ltimo elemento de la lista no tiene nodo siguiente, por lo que se dice que la referencia siguiente del ltimo elemento es null (nula).
La siguiente figura muestra un ejemplo de una lista enlazada cuyos elementos son strings:
La referencia lista indica la posicin del primer elemento de la lista y permite acceder a todos los elementos de sta: basta con seguir las
referencias al nodo siguiente para recorrer la lista.
NodoLista aux=lista;
aux=aux.siguiente;
converted by W eb2PDFConvert.com
Siguiendo con el ejemplo anterior, para insertar un nuevo nodo justo delante del nodo referenciado por aux se deben modificar las
referencias siguiente del nodo aux y del nodo a insertar.
converted by W eb2PDFConvert.com
Una lista circular es aquella en donde la referencia siguiente del ltimo nodo en vez de ser null apunta al primer nodo de la lista. El
concepto se aplica tanto a listas de enlace simple como doblemente enlazadas.
En muchas aplicaciones que utilizan listas enlazadas es til contar con un nodo cabecera, tambien conocido como dummy o header, que
es un nodo "falso", ya que no contiene informacin relevante, y su referencia siguiente apunta al primer elemento de la lista. Al utilizar un
nodo cabecera siempre es posible definir un nodo previo a cualquier nodo de la lista, definiendo que el previo al primer elemento es la
cabecera.
Si se utiliza un nodo cabecera en una lista de doble enlace ya no es necesario contar con las referencias primero y ltimo, puesto que el
nodo cabecera tiene ambas referencias: su referencia siguiente es el primer elemento de la lista, y su referencia anterior es el ltimo
elemento de la lista. De esta forma la lista de doble enlace queda circular de una manera natural.
rboles
Un rbol se define como una coleccin de nodos organizados en forma recursiva. Cuando hay 0 nodos se dice que el rbol esta vaco, en
caso contrario el rbol consiste en un nodo denominado raz, el cual tiene 0 o ms referencias a otros rboles, conocidos como subrboles.
Las races de los subrboles se denominan hijos de la raz, y consecuentemente la raz se denomina padre de las races de sus subrboles.
Una visin grfica de esta definicin recursiva se muestra en la siguiente figura:
Los nodos que no poseen hijos se denominan hojas. Dos nodos que tienen el padre en comn se denominan hermanos.
converted by W eb2PDFConvert.com
Un camino entre un nodo n1 y un nodo nk est definido como la secuencia de nodos n1, n2, ..., nk tal que ni es padre de ni+1, 1 <= i < k. El
largo del camino es el nmero de referencias que componen el camino, que para el ejemplo son k-1. Existe un camino desde cada nodo
del rbol a s mismo y es de largo 0. Ntese que en un rbol existe un nico camino desde la raz hasta cualquier otro nodo del rbol. A
partir del concepto de camino se definen los conceptos de ancestro y descendiente: un nodo n es ancestro de un nodo m si existe un
camino desde n a m; un nodo n es descendiente de un nodo m si existe un camino desde m a n.
Se define la profundidad del nodo nk como el largo del camino entre la raz del arbol y el nodo nk . Esto implica que la profundidad de la raz
es siempre 0. La altura de un nodo nk es el mximo largo de camino desde nk hasta alguna hoja. Esto implica que la altura de toda hoja es
0. La altura de un rbol es igual a la altura de la raz, y tiene el mismo valor que la profundidad de la hoja ms profunda. La altura de un rbol
vaco se define como -1.
La siguiente figura muestra un ejemplo de los conceptos previamente descritos:
rboles binarios
Un rbol binario es un rbol en donde cada nodo posee 2 referencias a subrboles (ni ms, ni menos). En general, dichas referencias se
denominan izquierda y derecha, y consecuentemente se define el subrbol izquierdo y subrbol derecho del arbol.
converted by W eb2PDFConvert.com
e = i+1
Demostracin: induccin sobre i (ejercicio).
Propiedad 2:
Sea n = nmero de nodos internos. Se define:
In = suma del largo de los caminos desde la raz a cada nodo interno (largo de caminos internos).
En = suma del largo de los caminos desde la raz a cada nodo externo (largo de caminos externos).
Se tiene que:
En = In+2n
Demostracin: induccin sobre n (ejercicio).
Propiedad 3:
Cuntos rboles binarios distintos se pueden construir con n nodos internos?
n
0
1
2
3
bn
1
1
2
5
bn?
converted by W eb2PDFConvert.com
*+ab-cd
Al recorrer el rbol en inorden se obtiene:
a+b*c-d
Al recorrer el rbol en postorden se obtiene:
ab+cd-*
La expresin que se obtiene con el recorrido en postorden se conoce como notacin polaca inversa.
rboles generales
En un rbol general cada nodo puede poseer un nmero indeterminado de hijos. La implementacin de los nodos en este caso se realiza
de la siguiente manera: como no se sabe de antemano cuantos hijos tiene un nodo en particular se utilizan dos referencias, una a su primer
hijo y otra a su hermano ms cercano. La raz del rbol necesariamente tiene la referencia a su hermano como null.
class NodoArbolGeneral
{
Object elemento;
NodoArbolGeneral hijo;
NodoArbolGeneral hermano;
}
converted by W eb2PDFConvert.com
Ntese que todo rbol general puede representarse como un rbol binario, con la salvedad que el hijo derecho de la raz es siempre null. Si
se permite que la raz del rbol tenga hermanos, lo que se conoce como bosque, entonces se tiene que el conjunto de los bosques
generales es isomorfo al conjunto de los rboles binarios. En efecto, las propiedades vistas en los rboles binarios se siguen cumpliendo
en los rboles generales.
converted by W eb2PDFConvert.com
TDA lista.
TDA pila.
TDA cola.
TDA cola de prioridad
Un Tipo de dato abstracto (en adelante TDA) es un conjunto de datos u objetos al cual se le asocian operaciones. El TDA provee de una
interfaz con la cual es posible realizar las operaciones permitidas, abstrayndose de la manera en como estn implementadas dichas
operaciones. Esto quiere decir que un mismo TDA puede ser implementado utilizando distintas estructuras de datos y proveer la misma
funcionalidad.
El paradigma de orientacin a objetos permite el encapsulamiento de los datos y las operaciones mediante la definicin de clases e
interfaces, lo cual permite ocultar la manera en cmo ha sido implementado el TDA y solo permite el acceso a los datos a travs de las
operaciones provistas por la interfaz.
En este captulo se estudiarn TDA bsicos como lo son las listas, pilas y colas, y se mostrarn algunos usos prcticos de estos TDA.
TDA lista
Una lista se define como una serie de N elementos E1, E2, ..., EN, ordenados de manera consecutiva, es decir, el elemento Ek (que se
denomina elemento k-simo) es previo al elemento Ek+1. Si la lista contiene 0 elementos se denomina como lista vaca.
Las operaciones que se pueden realizar en la lista son: insertar un elemento en la posicin k, borrar el k-simo elemento, buscar un
elemento dentro de la lista y preguntar si la lista esta vaca.
Una manera simple de implementar una lista es utilizando un arreglo. Sin embargo, las operaciones de insercin y borrado de elementos en
arreglos son ineficientes, puesto que para insertar un elemento en la parte media del arreglo es necesario mover todos los elementos que
se encuentren delante de l, para hacer espacio, y al borrar un elemento es necesario mover todos los elementos para ocupar el espacio
desocupado. Una implementacin ms eficiente del TDA se logra utilizando listas enlazadas.
A continuacin se presenta una implementacin en Java del TDA utilizando listas enlazadas y sus operaciones asociadas:
Los archivos NodoLista.java, IteradorLista.java y Lista.java contienen una implementacin completa del TDA lista. La clase NodoLista
implementa los nodos de la lista enlazada, la clase Lista implementa las operaciones de la lista propiamente tal, y la clase IteradorLista
implementa objetos que permiten recorrer la lista y posee la siguiente interfaz:
TDA pila
converted by W eb2PDFConvert.com
Una pila (stack o pushdown en ingls) es una lista de elementos de la cual slo se puede extraer el ltimo elemento insertado. La posicin
en donde se encuentra dicho elemento se denomina tope de la pila. Tambin se conoce a las pilas como listas LIFO (LAST IN - FIRST
OUT: el ltimo que entra es el primero que sale).
{
Object x=arreglo[tope];
return x;
}
}
public boolean estaVacia()
{
if (tope==-1)
{
return true;
}
else
{
return false;
}
}
}
El inconveniente de esta implementacin es que es necesario fijar de antemano el nmero mximo de elementos que puede contener la
pila, MAX_ELEM, y por lo tanto al apilar un elemento es necesario controlar que no se inserte un elemento si la pila esta llena. Sin
embargo, en Java es posible solucionar este problema creando un nuevo arreglo ms grande que el anterior, el doble por ejemplo, y
copiando los elementos de un arreglo a otro:
public void apilar(Object x)
{
if (tope+1<MAX_ELEM) // si esta llena se produce OVERFLOW
{
tope++;
arreglo[tope]=x;
}
else
{
MAX_ELEM=MAX_ELEM*2;
Object[] nuevo_arreglo=new Object[MAX_ELEM];
for (int i=0; i<arreglo.length; i++)
{
nuevo_arreglo[i]=arreglo[i];
}
tope++;
nuevo_arreglo[tope]=x;
arreglo=nuevo_arreglo;
}
}
Si la llamada recursiva es lo ltimo que hace la funcin F, entonces dicha llamada se puede substituir por un ciclo while. Este caso es
conocido como tail recursion y en lo posible hay que evitarlo en la programacin, ya que cada llamada recursiva ocupa espacio en la
memoria del computador, y en el caso del tail recursion es muy simple eliminarla. Por ejemplo:
void imprimir(int[] a, int j) // versin recursiva
{
if (j<a.length)
{
System.out.println(a[j]);
imprimir(a, j+1); // tail recursion
}
}
void imprimir(int[] a, int j) // versin iterativa
{
while (j<a.length)
{
System.out.println(a[j]);
j=j+1;
}
}
En el caso general, cuando el llamado recursivo se realiza en medio de la funcin F, la recursin se puede eliminar utilizando una pila.
Por ejemplo: recorrido en preorden de un arbol binario.
// "raiz" es la referencia a la raiz del arbol
// llamado inicial: preorden(raiz)
// version recursiva
void preorden(Nodo nodo)
{
if (nodo!=null)
{
System.out.print(nodo.elemento);
preorden(nodo.izq);
preorden(nodo.der);
converted by W eb2PDFConvert.com
}
}
// primera version iterativa
void preorden(Nodo nodo)
{
Nodo aux;
Pila pila=new Pila(); // pila de nodos
pila.apilar(nodo);
while(!pila.estaVacia()) // mientras la pila no este vacia
{
aux=pila.desapilar();
if (aux!=null)
{
System.out.print(aux.elemento);
// primero se apila el nodo derecho y luego el izquierdo
// para mantener el orden correcto del recorrido
// al desapilar los nodos
pila.apilar(aux.der);
pila.apilar(aux.izq);
}
}
}
//
//
//
//
//
TDA cola
Una cola (queue en ingls) es una lista de elementos en donde siempre se insertan nuevos elementos al final de la lista y se extraen
elementos desde el inicio de la lista. Tambin se conoce a las colas como listas FIFO (FIRST IN - FIRST OUT: el primero que entra es el
primero que sale).
primero: indica el ndice de la posicin del primer elemento de la cola, es decir, la posicin del elemento a retornar cuando se invoque
sacar.
ultimo: indica el ndice de la posicin de ltimo elemento de la cola. Si se invoca encolar, el elemento debe ser insertado en el
casillero siguiente al que indica la variable.
numElem: indica cuntos elementos posee la cola. Definiendo MAX_ELEM como el tamao mximo del arreglo, y por lo tanto de la
cola, entonces la cola esta vaca si numElem==0 y est llena si numElem==MAX_ELEM.
Un detalle faltante es el siguiente: qu pasa si la variable ultimo sobrepasa el rango de ndices del arreglo? Esto se soluciona definiendo
que si despus de insertar un elemento el ndice ultimo == MAX_ELEM, entonces se asigna ultimo = 0 , y los siguientes elementos sern
insertados al comienzo del arreglo. Esto no produce ningn efecto en la lgica de las operaciones del TDA, pues siempre se saca el
elemento referenciado por el ndice primero, aunque en valor absoluto primero > ultimo. Este enfoque es conocido como implementacin
con arreglo circular, y la forma ms fcil de implementarlo es haciendo la aritmtica de subndices mdulo MAX_ELEM.
class ColaArreglo
{
private Object[] arreglo;
private int primero, ultimo, numElem;
private int MAX_ELEM=100; // maximo numero de elementos en la cola
public ColaArreglo()
{
arreglo=new Object[MAX_ELEM];
primero=0;
ultimo=-1;
numElem=0;
}
public void encolar(Object x)
{
if (numElem<=MAX_ELEM) // si esta llena se produce OVERFLOW
{
ultimo=(ultimo+1)%MAX_ELEM;
arreglo[ultimo]=x;
numElem++;
}
}
public Object sacar()
{
if (!estaVacia()) // si esta vacia se produce UNDERFLOW
{
Object x=arreglo[primero];
primero=(primero+1)%MAX_ELEM;
numElem--;
return x;
}
}
public boolean estaVacia()
{
return numElem==0;
}
converted by W eb2PDFConvert.com
}
Nuevamente en este caso, dependiendo de la aplicacin, se debe definir qu hacer en caso de producirse OVERFLOW o UNDERFLOW.
Con esta implementacin, todas las operaciones del TDA cola tienen costo O(1).
Heaps
Un heap es un rbol binario de una forma especial, que permite su almacenamiento en un arreglo sin usar punteros.
Un heap tiene todos sus niveles llenos, excepto posiblemente el de ms abajo, y en este ltimo los nodos estn lo ms a la izquierda
posible.
Ejemplo:
La numeracin por niveles (indicada bajo cada nodo) son los subndices en donde cada elemento sera almacenado en el arreglo. En el
caso del ejemplo, el arreglo sera:
La caracterstica que permite que un heap se pueda almacenar sin punteros es que, si se utiliza la numeracin por niveles indicada,
entonces la relacin entre padres e hijos es:
forma de describir esto es diciendo que el nuevo elemento "trepa" en el rbol hasta alcanzar el nivel correcto segn su prioridad.
converted by W eb2PDFConvert.com
TDA diccionario
1. Implementaciones sencillas.
Bsqueda binaria.
Bsqueda secuencial con probabilidades de acceso no uniforme.
2. Arboles de bsqueda binaria.
3. Arboles AVL.
4. Arboles 2-3.
5. Arboles B.
6. Arboles digitales.
7. Arboles de bsqueda digital.
8. Skip lists.
9. ABB ptimos.
10. Splay Trees.
11. Hashing.
Encadenamiento.
Direccionamiento abierto.
Hashing en memoria secundaria.
Dado un conjunto de elementos {X1, X2, ..., XN}, todos distintos entre s, se desea almacenarlos en una estructura de datos que permita la
implementacin eficiente de las operaciones:
bsqueda(X): dado un elemento X, conocido como llave de bsqueda, encontrarlo dentro del conjunto o decir que no est.
insercin(X): agregar un nuevo elemento X al conjunto.
eliminacin(X): eliminar el elemento X del conjunto.
Estas operaciones describen al TDA diccionario. En el presente captulo se vern distintas implementaciones de este TDA y se estudiarn
las consideraciones de eficiencia de cada una de dichas implementaciones.
Implementaciones sencillas
Una manera simple de implementar el TDA diccionario es utilizando una lista, la cual permite implementar la insercin de nuevos elementos
de manera muy eficiente, definiendo que siempre se realiza al comienzo de la lista. El problema que tiene esta implementacin es que las
operaciones de bsqueda y eliminacin son ineficientes, puesto que como en la lista los elementos estn desordenados es necesario
realizar una bsqueda secuencial. Por lo tanto, los costos asociados a cada operacin son:
bsqueda: O(n) (bsqueda secuencial).
insercin: O(1) (insertando siempre al comienzo de la lista).
eliminacin: O(n) (bsqueda + O(1)).
Otra manera sencilla de implementar el TDA es utilizando un arreglo ordenado. En este caso, la operacin de insercin y eliminacin son
ineficientes, puesto que para mantener el orden dentro del arreglo es necesario "correr" los elementos para dejar espacio al insertar un
nuevo elemento. Sin embargo, la ventaja que tiene mantener el orden es que es posible realizar una bsqueda binaria para encontrar el
elemento buscado.
Bsqueda binaria
Suponga que se dispone del arreglo a, de tamao n, en donde se tiene almacenado el conjunto de elementos ordenados de menor a
mayor. Para buscar un elemento x dentro del arreglo se debe:
Buscar el ndice de la posicin media del arreglo en donde se busca el elemento, que se denotar m. Inicialmente, m = n/2.
Si a[m] = x se encontr el elemento (fin de la bsqueda), en caso contrario se sigue buscando en el lado derecho o izquierdo del
arreglo dependiendo si a[m] < x o a[m] > x respectivamente.
En cada iteracin:
Si el conjunto es vaco (j-i < 0), o sea si j < i, entonces el elemento x no est en el conjunto (bsqueda infructuosa).
En caso contrario, m = (i+j)/2. Si x = a[m], el elemento fue encontrado (bsqueda exitosa). Si x < a[m] se modifica j = m-1, sino se
modifica i = m+1 y se sigue iterando.
Implementacin:
public int busquedaBinaria(int []a, int x)
{
int i=0, j=a.length-1;
while (i<=j)
{
int m=(i+j)/2;
if (x==a[m])
{
return m;
}
else if (x<a[m])
{
j=m-1;
}
else
{
i=m+1;
}
}
return NO_ENCONTRADO; // NO_ENCONTRADO se define como -1
}
Mtodos auto-organizantes
Idea: cada vez que se accesa un elemento Xk se modifica la lista para que los accesos futuros a Xk sean ms eficientes. Algunas polticas
de modificacin de la lista son:
Los ABB permiten realizar de manera eficiente las operaciones provistas por el TDA diccionario, como se ver a continuacin.
Bsqueda en un ABB
Esta operacin retorna una referencia al nodo en donde se encuentra el elemento buscado, X, o null si dicho elemento no se encuentra en
el rbol. La estructura del rbol facilita la bsqueda:
Si el rbol esta vaco, entonces el elemento no est y se retorna null.
Si el rbol no esta vaco y el elemento almacenado en la raiz es X, se encontr el elemento y se retorna una referencia a dicho nodo.
Si X es menor que el elemento almacenado en la raiz se sigue buscando recursivamente en el subrbol izquierdo, y si X es mayor que
el elemento almacenado en la raz se sigue buscando recursivamente en el subrbol derecho.
Ntese que el orden en los cuales se realizan los pasos anteriores es crucial para asegurar que la bsqueda en el ABB se lleve a cabo de
manera correcta.
posibles es O(n).
Caso promedio
Supuestos:
Arbol con n nodos.
Probabilidad de acceso a los elementos uniforme.
Costo de bsqueda exitosa:
Por lo tanto, la ecuacin que relaciona los costos de bsqueda exitosa e infructuosa es: (*)
Esto muestra que a medida que se insertan ms elementos en el ABB los costos de bsqueda exitosa e infructuosa se van haciendo cada
vez ms parecidos.
El costo de bsqueda de un elemento en un ABB es igual al costo de bsqueda infructuosa justo antes de insertarlo ms 1. Esto quiere
decir que si ya haban k elementos en el rbol y se inserta uno ms, el costo esperado de bsqueda para este ltimo es 1+C'k . Por lo tanto:
Reemplazando en (**):
converted by W eb2PDFConvert.com
Insercin en un ABB
Para insertar un elemento X en un ABB, se realiza una bsqueda infructuosa de este elemento en el rbol, y en el lugar en donde debiera
haberse encontrado se inserta. Como se vio en la seccin anterior, el costo promedio de insercin en un ABB es O(log(n)).
Eliminacin en un ABB
Primero se realiza una bsqueda del elemento a eliminar, digamos X. Si la bsqueda fue infructuosa no se hace nada, en caso contrario
hay que considerar los siguientes casos posibles:
Si X es una hoja sin hijos, se puede eliminar inmediatamente.
Si X tiene un solo hijo, entonces se cambia la referencia del padre a X para que ahora referencie al hijo de X.
converted by W eb2PDFConvert.com
Si X tiene dos hijos, el caso complicado, se procede de la siguiente forma: se elimina el nodo Y = minimo nodo del subrbol derecho
de X, el cual corresponde a uno de los casos fciles de eliminacin, y se reemplaza el valor almacenado en el nodo X por el valor que
estaba almacenado en el nodo Y.
Ntese que el rbol sigue cumpliendo las propiedades de un ABB con este mtodo de eliminacin.
Si de antemano se sabe que el nmero de eliminaciones ser pequeo, entonces la eliminacin se puede substituir por una marca que
indique si un nodo fue eliminado o no. Esta estrategia es conocida como eliminacin perezosa (lazy deletion).
Arboles AVL
Definicin: un rbol balanceado es un rbol que garantiza costos de bsqueda, insercin y eliminacin en tiempo O(log(n)) incluso en el
peor caso.
U n rbol AVL (Adelson-Velskii y Landis) es una rbol de bsqueda binaria que asegura un costo O(log(n)) en las operaciones de
bsqueda, insercin y eliminacin, es decir, posee una condicin de balance.
La condicin de balance es: un rbol es AVL si para todo nodo interno la diferencia de altura de sus dos rboles hijos es menor o igual
que 1.
converted by W eb2PDFConvert.com
Problema: para una altura h dada, cul es el rbol AVL con mnimo nmero de nodos que alcanza esa altura?. Ntese que en dicho rbol
AVL la diferencia de altura de sus hijos en todos los nodos tiene que ser 1 (demostrar por contradiccin). Por lo tanto, si Ah representa al
rbol AVL de altura h con mnimo nmero de nodos, entonces sus hijos deben ser Ah-1 y Ah-2.
En la siguiente tabla nh representa el nmero de nodos externos del rbol AVL con mnimo nmero de nodos internos.
Ah
nh
converted by W eb2PDFConvert.com
13
en
Insercin en un AVL
La insercin en un AVL se realiza de la misma forma que en un ABB, con la salvedad que hay que modificar la informacin de la altura de
los nodos que se encuentran en el camino entre el nodo insertado y la raz del rbol. El problema potencial que se puede producir despus
de una insercin es que el rbol con el nuevo nodo no sea AVL:
En el ejemplo de la figura, la condicin de balance se pierde al insertar el nmero 3 en el rbol, por lo que es necesario restaurar de alguna
forma dicha condicin. Esto siempre es posible de hacer a travs de una modificacin simple en el rbol, conocida como rotacin.
Suponga que despus de la insercin de un elemento X el nodo desbalanceado ms profundo en el rbol es N. Esto quiere decir que la
diferencia de altura entre los dos hijos de N tiene que ser 2, puesto que antes de la insercin el rbol estaba balanceado. La violacin del
balance pudo ser ocasionada por alguno de los siguientes casos:
El elemento X fue insertado en el subrbol izquierdo del hijo izquierdo de N.
El elemento X fue insertado en el subrbol derecho del hijo izquierdo de N.
El elemento X fue insertado en el subrbol izquierdo del hijo derecho de N.
El elemento X fue insertado en el subrbol derecho del hijo derecho de N.
Dado que el primer y ltimo caso son simtricos, asi como el segundo y el tercero, slo hay que preocuparse de dos casos principales: una
insercin "hacia afuera" con respecto a N (primer y ltimo caso) o una insercin "hacia adentro" con respecto a N (segundo y tercer caso).
Rotacin simple
El desbalance por insercin "hacia afuera" con respecto a N se soluciona con una rotacin simple.
converted by W eb2PDFConvert.com
La figura muestra la situacin antes y despus de la rotacin simple, y ejemplifica el cuarto caso anteriormente descrito, es decir, el
elemento X fue insertado en E, y b corresponde al nodo N. Antes de la insercin, la altura de N es la altura de C+1. Idealmente, para
recuperar la condicin de balance se necesitaria bajar A en un nivel y subir E en un nivel, lo cual se logra cambiando las referencias
derecha de b e izquierda de d, quedando este ltimo como nueva raz del rbol, N'. Ntese que ahora el nuevo rbol tiene la misma altura
que antes de insertar el elemento, C+1, lo cual implica que no puede haber nodos desbalanceados ms arriba en el rbol, por lo que es
necesaria una sola rotacin simple para devolver la condicin de balance al rbol. Ntese tambin que el rbol sigue cumpliendo con la
propiedad de ser ABB.
Rotacin doble
Claramente un desbalance producido por una insercin "hacia adentro" con respecto a N no es solucionado con una rotacin simple, dado
que ahora es C quien produce el desbalance y como se vio anteriormente este subrbol mantiene su posicin relativa con una rotacin
simple.
Para el caso de la figura (tercer caso), la altura de N antes de la insercin era G+1. Para recuperar el balance del rbol es necesario subir
C y E y bajar A, lo cual se logra realizando dos rotaciones simples: la primera entre d y f, y la segunda entre d, ya rotado, y b,
obtenindose el resultado de la figura. A este proceso de dos rotaciones simples se le conoce como rotacin doble, y como la altura del
nuevo rbol N' es la misma que antes de la insercin del elemento, ningn elemento hacia arriba del rbol queda desbalanceado, por lo que
solo es necesaria una rotacin doble para recuperar el balance del rbol despus de la insercin. Ntese que el nuevo rbol cumple con la
propiedad de ser ABB despus de la rotacin doble.
Eliminacin en un AVL
La eliminacin en rbol AVL se realiza de manera anloga a un ABB, pero tambin es necesario verificar que la condicin de balance se
mantenga una vez eliminado el elemento. En caso que dicha condicin se pierda, ser necesario realizar una rotacin simple o doble
dependiendo del caso, pero es posible que se requiera ms de una rotacin para reestablecer el balance del rbol.
Arboles 2-3
Los rboles 2-3 son rboles cuyos nodos internos pueden contener hasta 2 elementos (todos los rboles vistos con anterioridad pueden
contener slo un elemento por nodo), y por lo tanto un nodo interno puede tener 2 o 3 hijos, dependiendo de cuntos elementos posea el
nodo. De este modo, un nodo de un rbol 2-3 puede tener una de las siguientes formas:
converted by W eb2PDFConvert.com
Una propiedad de los rboles 2-3 es que todas las hojas estn a la misma profundidad, es decir, los rboles 2-3 son rboles
perfectamente balanceados. La siguiente figura muestra un ejemplo de un rbol 2-3:
Ntese que se sigue cumpliendo la propiedad de los rboles binarios: nodos internos + 1 = nodos externos. Dado que el rbol 2-3 es
perfectamente balanceado, la altura de ste esta acotada por:
Si el nodo donde se inserta X tena dos llaves (tres hijos), queda transitoriamente con tres llaves (cuatro hijos) y se dice que est
saturado (overflow). En este caso se debe realizar una operacin de split: el nodo saturado se divide en dos nodos con un valor cada
uno (el menor y el mayor de los tres). El valor del medio sube un nivel, al padre del nodo saturado.
El problema se resuelve a nivel de X y Z, pero es posible que el nodo que contiene a Y ahora este saturado. En este caso, se repite el
mismo prodecimiento anterior un nivel ms arriba. Finalmente, si la raz es el nodo saturado, ste se divide y se crea una nueva raz un
nivel ms arriba. Esto implica que los rboles 2-3 crecen "hacia arriba".
converted by W eb2PDFConvert.com
El nodo donde se encuentra Z contiene un solo elemento. En este caso al eliminar el elemento Z el nodo queda sin elementos
(underflow). Si el nodo hermano posee dos elementos, se le quita uno y se inserta en el nodo con underflow.
Si el nodo hermano contiene solo una llave, se le quita un elemento al padre y se inserta en el nodo con underflow.
Si esta operacin produce underflow en el nodo padre, se repite el procedimiento anterior un nivel ms arriba. Finalmente, si la raz
queda vaca, sta se elimina.
converted by W eb2PDFConvert.com
Arboles B
La idea de los rboles 2-3 se puede generalizar a rboles t - (2t-1), donde t>=2 es un parmetro fijo, es decir, cada nodo del rbol posee
entre t y 2t-1 hijos, excepto por la raz que puede tener entre 2 y 2t-1 hijos. En la prctica, t puede ser bastante grande, por ejemplo t = 100 o
ms. Estos rboles son conocidos como rboles B (Bayer).
Insercin en un rbol B
Se agrega una nueva llave al nivel inferior.
Si el nodo rebalsa (overflow), es decir, si queda con 2t hijos, se divide en 2 nodos con t hijos cada uno y sube un elemento al nodo
padre. Si el padre rebalsa, se repite el procedimiento un nivel ms arriba.
Finalmente, si la raiz rebalsa, se divide en 2 y se crea una nueva raiz un nivel ms arriba.
Eliminacin en un rbol B
Se elimina la llave, y su hoja respectiva, del nivel inferior.
Si el nodo queda con menos de t hijos (underflow) se le piden prestados algunos al hermano, por ejemplo mitad y mitad.
Si el hermano no tiene para prestar, entonces se fusionan los 2 nodos y se repite el procedimiento con el padre.
Si la raz queda vaca, se elimina.
Arboles digitales
Suponga que los elementos de un conjunto se pueden representar como una secuencia de bits:
X = b0b1b2...bk
Adems, suponga que ninguna representacin de un elemento en particular es prefijo de otra. Un rbol digital es un rbol binario en donde
la posicin de insercin de un elemento ya no depende de su valor, sino de su representacin binaria. Los elementos en un rbol digital se
almacenan slo en sus hojas, pero no necesariamente todas las hojas contienen elementos (ver ejemplo ms abajo). Esta estructura de
datos tambin es conocida como trie.
El siguiente ejemplo muestra un rbol digital con 5 elementos.
converted by W eb2PDFConvert.com
utilizando el siguiente bit del elemento, bk+1, y se repite el procedimiento, si es necesario, hasta que quede slo un elemento por hoja.
Con este proceso de insercin, la forma que obtiene el rbol digital es insensible al orden de insercin de los elementos.
Hn son los nmeros armnicos y P(n) es una funcin peridica de muy baja amplitud (O(10-6))
Los ABD poseen un mejor costo promedio de bsqueda que los ABB, pero tambien es O(log(n)).
Skip lists
Al principio del captulo se vio que una de las maneras simples de implementar el TDA diccionario es utilizando una lista enlazada, pero
tambin se vio que el tiempo de bsqueda promedio es O(n) cuando el diccionario posee n elementos. La figura muestra un ejemplo de
lista enlazada simple con cabecera, donde los elementos estn ordenados ascendentemente:
Sin embargo, es posible modificar la estructura de datos de la lista y as mejorar el tiempo de bsqueda promedio. La siguiente figura
muestra una lista enlazada en donde a cada nodo par se le agrega una referencia al nodo ubicado dos posiciones ms adelante en la lista.
Modificando ligeramente el algoritmo de bsqueda, a lo ms
nodos son examinados en el peor caso.
Esta idea se puede extender agregando una referencia cada cuatro nodos. En este caso, a lo ms
El caso lmite para este argumento se muestra en la siguiente figura. Cada 2i nodo posee una referencia al nodo 2i posiciones ms
adelante en la lista. El nmero total de referencias solo ha sido doblado, pero ahora a lo ms
nodos son examinados durante la
bsqueda. Note que la bsqueda en esta estructura de datos es bsicamente una bsqueda binaria, por lo que el tiempo de bsqueda en el
peor caso es O(log n).
converted by W eb2PDFConvert.com
El problema que tiene esta estructura de datos es que es demasiado rgida para permitir inserciones de manera eficiente. Por lo tanto, es
necesario relajar levemente las condiciones descritas anteriormente para permitir inserciones eficientes.
Se define un nodo de nivel k como aquel nodo que posee k referencias. Se observa de la figura anterior que, aproximadamente, la mitad
de los nodos son de nivel 1, que un cuarto de los nodos son de nivel 2, etc. En general, aproximadamente n/2i nodos son de nivel i. Cada
vez que se inserta un nodo, se elige el nivel que tendr aleatoriamente en concordancia con la distribucin de probabilidad descrita. Por
ejemplo, se puede lanzar una moneda al aire, y mientras salga cara se aumenta el nivel del nodo a insertar en 1 (partiendo desde 1). Esta
estructura de datos es denominada skip list. La siguiente figura muestra un ejemplo de una skip list:
ABB ptimos
ABB con probabilidades de acceso no uniforme
Problema: dados n elementos X1 < X2 < ... < Xn, con probabilidades de acceso conocidas p1, p2, ..., pn, y con probabilidades de bsqueda
infructuosa conocidas q0, q1, ..., qn, se desea encontrar el ABB que minimice el costo esperado de bsqueda.
Por ejemplo, para el siguiente ABB con 6 elementos:
=(q0+p1+q1+p2+q2+p3+q3+p4+q4+p5+q5+p6+q6)+
(1q0+1p1+3q1+3p2+3q2+2p3+3q3+3p4+3q4)+
(1q5+1p6+1q6)
=W(q0,q6)+C(q0,q4)+C(q5,q6)
Esto es, el costo del rbol completo es igual al "peso" del rbol ms los costos de los subrboles. Si la raz es k:
Splay Trees
Esta estructura garantiza que para cualquier secuencia de M operaciones en un rbol, empezando desde un rbol vaco, toma a lo ms
tiempo O(M log(N)). A pesar que esto no garantiza que alguna operacin en particular tome tiempo O(N), si asegura que no existe ninguna
secuencia de operaciones que sea mala. En general, cuando una secuencia de M operaciones toma tiempo O(M f(N)), se dice que el costo
amortizado en tiempo de cada operacin es O(f(N)). Por lo tanto, en un splay tree los costos amortizados por operacin son de O(log(N)).
La idea bsica de un splay tree es que despus que un valor es insertado o buscado en el rbol, el nodo correspondiente a este valor se
"sube" hasta la raz del rbol a travs de rotaciones (similares a las rotaciones en los rboles AVL). Una manera de realizar esto, que NO
funciona, es realizar rotaciones simples entre el nodo accesado y su padre hasta dejar al nodo accesado como raz del rbol. El problema
que tiene este enfoque es que puede dejar otros nodos muy abajo en el rbol, y se puede probar que existe una secuencia de M
operaciones que toma tiempo O(MN), por lo que esta idea no es muy buena.
La estrategia de "splaying" es similar a la idea de las rotaciones simples. Si el nodo k es accesado, se realizarn rotaciones para llevarlo
converted by W eb2PDFConvert.com
hasta la raz del rbol. Sea k un nodo distinto a la raz del rbol. Si el padre de k es la raz del rbol, entonces se realiza una rotacin simple
entre estos dos nodos. En caso contrario, el nodo k posee un nodo padre p y un nodo "abuelo" a. Para realizar las rotaciones se deben
considerar dos casos posibles (ms los casos simtricos).
El primer caso es una insercin zig-zag, en donde k es un hijo derecho y p es un hijo izquierdo (o viceversa). En este caso, se realiza una
rotacin doble estilo AVL:
El otro caso es una insecin zig-zig, en donde k y p son ambos hijos izquierdo o derecho. En este caso, se realiza la transformacin
indicada en la figura siguiente:
El efecto del splaying es no slo de mover el nodo accesado a la raz, sino que sube todos los nodos del camino desde la raz hasta el nodo
accesado aproximadamente a la mitad de su profundidad anterior, a costa que algunos pocos nodos bajen a lo ms dos niveles en el rbol.
El siguiente ejemplo muestra como queda el splay tree luego de accesar al nodo d.
converted by W eb2PDFConvert.com
Para borrar un elemento de un splay tree se puede proceder de la siguiente forma: se realiza una bsqueda del nodo a eliminar, lo cual lo
converted by W eb2PDFConvert.com
deja en la raz del rbol. Si sta es eliminada, se obtienen dos subrboles Sizq y Sder. Se busca el elemento mayor en Sizq, con lo cual dicho
elemento pasa a ser su nueva raz, y como era el elemento mayor Sizq ya no tiene hijo derecho, por lo que se cuelga Sder como subrbol
derecho de Sizq, con lo que termina la operacin de eliminacin.
El anlisis de los splay trees es complejo, porque debe considerar la estructura cambiante del rbol en cada acceso realizado. Por otra
parte, los splay trees son ms fciles de implementar que un AVL dado que no hay que verificar una condicin de balance.
Hashing
Suponga que desea almacenar n nmeros enteros, sabiendo de antemano que dichos nmeros se encuentran en un rango conocido 0, ...,
k-1. Para resolver este problema, basta con crear un arreglo de valores booleanos de tamao k y marcar con valor true los casilleros del
arreglo cuyo ndice sea igual al valor de los elementos a almacenar. Es fcil ver que con esta estructura de datos el costo de bsqueda,
insercin y eliminacin es O(1).
La funcin h debe ser de tipo pseudoaleatorio para distribuir las llaves uniformemente dentro de la tabla, es decir, Pr( h(X) = z) = 1/m para
todo z en [0, ..., m-1]. La llave X se puede interpretar como un nmero entero, y las funciones h(X) tpicas son de la forma:
donde c es una constante, p es un nmero primo y m es el tamao de la tabla de hashing. Distintos valores para estos parmetros producen
distintas funciones de hash.
Problema de este nuevo enfoque: colisiones.
La paradoja de los cumpleaos
Cul es el nmero n mnimo de personas que es necesario reunir en una sala para que la probabilidad que dos de ella tengan su
cumpleaos en el mismo da sea mayor que 1/2?
Sea dn la probabilidad que no haya coincidencia en dos fechas de cumpleaos. Se tiene que:
Encadenamiento
La idea de este mtodo es que todos los elementos que caen en el mismo casillero se enlacen en una lista, en la cual se realiza una
bsqueda secuencial.
Esto implica que el costo esperado de bsqueda slo depende del factor de carga
Direccionamiento abierto
En general, esto puede ser visto como una sucesin de funciones de hash {h0(X), h1(X), ...}. Primero se intenta con tabla[h0(X)], si el
casillero est ocupado se prueba con tabla[h1(X)], y as sucesivamente.
Linear probing
converted by W eb2PDFConvert.com
Es el mtodo ms simple de direccionamiento abierto, en donde las funciones de hash se definen como:
Cuando la tabla de hashing est muy llena, este mtodo resulta ser muy lento.
Cn
Cn'
.6 1.75 3.63
.7 2.17 6.06
.8 3
13
.9 5.50 50.50
.99 50.50 5,000.50
.999 500.50 500,000.50
A medida que la tabla se va llenando, se observa que empiezan a aparecer clusters de casilleros ocupados consecutivos:
Si la funcin de hash distribuye los elementos uniformemente dentro de la tabla, la probabilidad que un cluster crezca es proporcional a su
tamao. Esto implica que una mala situacin se vuelve peor cada vez con mayor probabilidad. Sin embargo, este no es todo el problema,
puesto que lo mismo sucede en hashing con encadenamiento y no es tan malo. El verdadero problema ocurre cuando 2 clusters estn
separados solo por un casillero libre y ese casillero es ocupado por algn elemento: ambos clusters se unen en uno mucho ms grande.
Otro problema que surge con linear probing es conocido como clustering secundario: si al realizar la bsqueda de dos elementos en la
tabla se encuentran con el mismo casillero ocupado, entonces toda la bsqueda subsiguiente es la misma para ambos elementos.
Eliminacin de elementos: no se puede eliminar un elemento y simplemente dejar su casillero vaco, puesto que las bsquedas terminaran
en dicho casillero. Existen dos maneras para eliminar elementos de la tabla:
Marcar el casillero como "eliminado", pero sin liberar el espacio. Esto produce que las bsquedas puedan ser lentas incluso si el
factor de carga de la tabla es pequeo.
Eliminar el elemento, liberar el casillero y mover elementos dentro de la tabla hasta que un casillero "verdaderamente" libre sea
encontrado. Implementar esta operacin es complejo y costoso.
Hashing doble
En esta estrategia se usan dos funciones de hash: una funcin
conocida como paso. Por lo tanto:
Elegir m primo asegura que se va a visitar toda la tabla antes que se empiecen a repetir los casilleros. Nota: slo basta que m y s(X) sean
primos relativos (ejercicio: demostralo por contradiccin).
El anlisis de eficiencia de esta estrategia es muy complicado, y se estudian modelos idealizados: muestreo sin reemplazo (uniform
probing) y muestreo con reemplazo (random probing), de los cuales se obtiene que los costos de bsqueda esperado son:
converted by W eb2PDFConvert.com
Si bien las secuencias de casilleros obtenidas con hashing doble no son aleatorias, en la prctica su rendimiento es parecido a los valores
obtenidos con los muestreos con y sin reemplazo.
Cn Cn'
.6 1.53 2.5
.7 1.72 3.33
.8 2.01 5
.9 2.56 10
.99 4.65 100
.999 6.91 1,000
Existen heursticas para resolver el problema de las colisiones en hashing con direccionamiento abierto, como por ejemplo last-come-firstserved hashing (el elemento que se mueve de casillero no es el que se inserta sino el que ya lo ocupaba) y Robin Hood hashing (el
elemento que se queda en el casillero es aquel que se encuentre ms lejos de su posicin original), que si bien mantienen el promedio de
bsqueda con respecto al mtodo original (first-come-first-served) disminuyen dramticamente su varianza.
Este mtodo es eficiente para un factor de carga pequeo, ya que con factor de carga alto la bsqueda toma tiempo O(n). Para resolver
esto puede ser necesario incrementar el tamao de la tabla, y as reducir el factor de carga. En general esto implica reconstruir toda la
tabla, pero existe un mtodo que permite hacer crecer la tabla paulatinamente en el tiempo denominado hashing extendible.
Hashing extendible
Suponga que las pginas de disco son de tamao b y una funcin de hash h(X)>=0 (sin lmite superior). Sea la descomposicin en binario
de la funcin de hash h(X)=(... b2(X) b1(X) b0(X))2.
Inicialmente, todas las llaves se encuentran en una nica pgina. Cuando dicha pgina se rebalsa se divide en dos, usando b0(X) para
discriminar entre ambas pginas:
Cada vez que una pgina rebalsa, se usa el siguiente bit en la sequencia para dividirla en dos. Ejemplo:
converted by W eb2PDFConvert.com
El ndice (directorio) puede ser almacenado en un rbol, pero el hashing extensible utiliza una idea diferente. El rbol es extendido de
manera que todos los caminos tengan el mismo largo:
Cuando se inserta un elemento y existe un rebalse, si cae en la pgina (D,E) esta se divide y las referencias de los casilleros 2 y 3 apuntan
a las nuevas pginas creadas. Si la pgina (B,C) es la que se divide, entonces el rbol crece un nivel, lo cual implica duplicar el tamao del
directorio. Sin embargo, esto no sucede muy a menudo.
El tamao esperado del directorio es ligeramente superlinear:
converted by W eb2PDFConvert.com
Ordenacin
1.
2.
3.
4.
5.
Cota inferior
Quicksort
Heapsort
Radix sort
Mergesort y Ordenamiento Externo
El problema de ordenar un conjunto de datos (por ejemplo, en orden ascendente) tiene gran importancia tanto terica como prctica. En
esta seccin veremos principalmente algoritmos que ordenan mediante comparaciones entre llaves, para los cuales se puede demostrar
una cota inferior que coincide con la cota superior provista por varios algoritmos. Tambin veremos un algoritmo de otro tipo, que al no
hacer comparaciones, no est sujeto a esa cota inferior.
Cota inferior
Supongamos que deseamos ordenar tres datos A, B y C. La siguiente figura muestra un rbol de decisin posible para resolver este
problema. Los nodos internos del rbol representan comparaciones y los nodos externos representan salidas emitidas por el programa.
Como se vio en el captulo de bsqueda, todo rbol de decisin con H hojas tiene al menos altura log2 H, y la altura del rbol de decisin es
igual al nmero de comparaciones que se efectan en el peor caso.
En un rbol de decisin para ordenar n datos se tiene que H=n!, y por lo tanto se tiene que todo algoritmo que ordene n datos mediante
comparaciones entre llaves debe hacer al menos log2 n! comparaciones en el peor caso.
Usando la aproximacin de Stirling, se puede demostrar que log2 n! = n log2 n + O(n), por lo cual la cota inferior es de O(n log n).
Si suponemos que todas las posibles permutaciones resultantes son equiprobables, es posible demostrar que el nmero promedio de
comparaciones que cualquier algoritmo debe hacer es tambin de O(n log n).
Quicksort
Este mtodo fue inventado por C.A.R. Hoare a comienzos de los '60s, y sigue siendo el mtodo ms eficiente para uso general.
Quicksort es un ejemplo clsico de la aplicacin del principio de dividir para reinar. Su estructura es la siguiente:
Primero se elige un elemento al azar, que se denomina el pivote.
El arreglo a ordenar se reordena dejando a la izquierda a los elementos menores que el pivote, el pivote al medio, y a la derecha los
elementos mayores que el pivote:
Costo promedio
Si suponemos, como una primera aproximacin, que el pivote siempre resulta ser la mediana del conjunto, entonces el costo de ordenar
est dado (aproximadamente) por la ecuacin de recurrencia
T(n) = n + 2 T(n/2)
converted by W eb2PDFConvert.com
Esto tiene solucin T(n) = n log2 n y es, en realidad, el mejor caso de Quicksort.
Para analizar el tiempo promedio que demora la ordenacin mediante Quicksort, observemos que el funcionamiento de Quicksort puede
graficarse mediante un rbol de particin:
Por la forma en que se construye, es fcil ver que el rbol de particin es un rbol de bsqueda binaria, y como el pivote es escogido al
azar, entonces la raz de cada subrbol puede ser cualquiera de los elementos del conjunto en forma equiprobable. En consecuencia, los
rboles de particin y los rboles de bsqueda binaria tienen exactamente la misma distribucin.
En el proceso de particin, cada elemento de los subrboles ha sido comparado contra la raz (el pivote). Al terminar el proceso, cada
elemento ha sido comparado contra todos sus ancestros. Si sumamos todas estas comparaciones, el resultado total es igual al largo de
caminos internos.
Usando todas estas correspondencias, tenemos que, usando los resultados ya conocidos para rboles, el nmero promedio de
comparaciones que realiza Quicksort es de:
1.38 n log2 n + O(n)
Por lo tanto, Quicksort es del mismo orden que la cota inferior (en el caso esperado).
Peor caso
El peor caso de Quicksort se produce cuando el pivote resulta ser siempre el mnimo o el mximo del conjunto. En este caso la ecuacin de
recurrencia es
T(n) = n - 1 + T(n-1)
lo que tiene solucin T(n) = O(n2). Desde el punto de vista del rbol de particin, esto corresponde a un rbol en "zig-zag".
Si bien este peor caso es extremadamente improbable si el pivote se escoge al azar, algunas implementaciones de Quicksort toman como
pivote al primer elemento del arreglo (suponiendo que, al venir el arreglo al azar, entonces el primer elemento es tan aleatorio como
cualquier otro). El problema es que si el conjunto viene en realidad ordenado, entonces caemos justo en el peor caso cuadrtico.
Lo anterior refuerza la importancia de que el pivote se escoja al azar. Esto no aumenta significativamente el costo total, porque el nmero
total de elecciones de pivote es O(n).
Mejoras a Quicksort
Quicksort puede ser optimizado de varias maneras, pero hay que ser muy cuidadoso con estas mejoras, porque es fcil que terminen
empeorando el desempeo del algoritmo.
En primer lugar, es desaconsejable hacer cosas que aumenten la cantidad de trabajo que se hace dentro del "loop" de particin, porque
este es el lugar en donde se concentra el costo O(n log n).
Algunas de las mejoras que han dado buen resultado son las siguientes:
Quicksort con "mediana de 3"
En esta variante, el pivote no se escoge como un elemento tomado al azar, sino que primero se extrae una muestra de 3 elementos, y entre
ellos se escoge a la mediana de esa muestra como pivote.
Si la muestra se escoge tomando al primer elemento del arreglo, al del medio y al ltimo, entonces lo que era el peor caso (arreglo
ordenado) se transforma de inmediato en mejor caso.
De todas formas, es aconsejable que la muestra se escoja al azar, y en ese caso el anlisis muestra que el costo esperado para ordenar n
elementos es
(12/7) n ln n = 1.19 n log2 n
converted by W eb2PDFConvert.com
Esta reduccin en el costo se debe a que el pivote es ahora una mejor aproximacin a la mediana. De hecho, si en lugar de escoger una
muestra de tamao 3, lo hiciramos con tamaos como 7, 9, etc., se lograra una reduccin an mayor, acercndonos cada vez ms al
ptimo, pero con rendimientos rpidamente decrecientes.
Uso de Ordenacin por Insercin para ordenar sub-arreglos pequeos
Tal como se dijo antes, no es eficiente ordenar recursivamente sub-arreglos demasiado pequeos.
En lugar de esto, se puede establecer un tamao mnimo M, de modo que los sub-arreglos de tamao menor que esto se ordenan por
insercin en lugar de por Quicksort.
Claramente debe haber un valor ptimo para M, porque si creciera indefinidamente se llegara a un algoritmo cuadrtico. Esto se puede
analizar, y el ptimo es cercano a 10.
Como mtodo de implementacin, al detectarse un sub-arreglo de tamao menor que M, se lo puede dejar simplemente sin ordenar,
retornando de inmediato de la recursividad. Al final del proceso, se tiene un arreglo cuyos pivotes estn en orden creciente, y encierran
entre ellos a bloques de elementos desordenados, pero que ya estn en el grupo correcto. Para completar la ordenacin, entonces, basta
con hacer una sola gran pasada de Ordenacin por Insercin, la cual ahora no tiene costo O(n2), sino O(nM), porque ningn elemento esta a
distancia mayor que M de su ubicacin definitiva.
Ordenar recursivamente slo el sub-arreglo ms pequeo
Un problema potencial con Quicksort es la profundidad que puede llegar a tener el arreglo de recursividad. En el peor caso, sta puede
llegar a ser O(n).
Para evitar esto, vemos primero cmo se puede programar Quicksort en forma no recursiva, usando un stack. El esquema del algoritmo
sera el siguiente (en seudo-Java):
void Quicksort(Object a[])
{
Pila S = new Pila();
S.apilar(1,N); // lmites iniciales del arreglo
while(!S.estaVacia())
{
(i,j) = S.desapilar(); // sacar lmites
if(j-i>0) // al menos dos elementos para ordenar
{
p = particionar(a,i,j); // pivote queda en a[p]
S.apilar(i,p-1);
S.apilar(p+1,j);
}
}
}
Con este enfoque se corre el riesgo de que la pila llegue a tener profundidad O(n). Para evitar esto, podemos colocar en la pila slo los
lmites del sub-arreglo ms pequeo, dejando el ms grande para ordenarlo de inmediato, sin pasar por la pila:
void Quicksort(Object a[])
{
Pila S = new Pila();
S.apilar(1,N); // lmites iniciales del arreglo
while(!S.estaVacia())
{
(i,j) = S.desapilar(); // sacar lmites
while(j-i>0) // al menos dos elementos para ordenar
{
p = particionar(a,i,j); // pivote queda en a[p]
if(p-i>j-p) // mitad izquierda es mayor
{
S.apilar(p+1,j);
j=p-1;
}
else
{
S.apilar(i,p-1);
i=p+1;
}
}
}
}
Con este enfoque, cada intervalo apilado es a lo ms de la mitad del tamao del arreglo, de modo que si llamamos S(n) a la profundidad
de la pila, tenemos:
S(n) <= 1 + S(n/2)
lo cual tiene solucin log2 n, de modo que la profundidad de la pila nunca es ms que logartmica.
Es posible modificar el algoritmo de Quicksort para seleccionar el k-simo elemento de un arreglo. Bsicamente, la idea es ejecutar
Quicksort, pero en lugar de ordenar las dos mitades, hacerlo solo con aquella mitad en donde se encontrara el elemento buscado.
Suponemos que los elementos estn en a[1],...,a[n] y que k est entre 1 y n. Cuando el algoritmo termina, el k-simo elemento se
encuentra en a[k]. El resultado es el siguiente algoritmo, conocido como Quickselect, el cual se llama inicialmente como
Quickselect(a,k,1,N).
void Quickselect(Object a[], int k, int i, int j)
{
if(j-i>0) // an quedan al menos 2 elementos
{
p = particionar(a,i,j);
if(p==k) // bingo!
return;
if(k<p) // seguimos buscando a la izquierda
Quickselect(a,k,i,p-1);
else
Quickselect(a,k,p+1,j);
}
}
Dado que en realidad se hace slo una llamada recursiva y que sta es del tipo "tail recursion", es fcil transformar esto en un algoritmo
iterativo (hacerlo como ejercicio).
El anlisis de Quickselect es difcil, pero se puede demostrar que el costo esperado es O(n). Sin embargo, el peor caso es O(n2).
Heapsort
A partir de cualquier implementacin de una cola de prioridad es posible obtener un algoritmo de ordenacin. El esquema del algoritmo es:
Comenzar con una cola de prioridad vaca.
Fase de ordenacin:
Sucesivamente extraer el mximo n veces. Los elementos van apareciendo en orden decreciente y se van almacenando en el
conjunto de salida.
Si aplicamos esta idea a las dos implementaciones simples de colas de prioridad, utilizando lista enlazada ordenada y lista enlazada
desordenada, se obtienen los algoritmos de ordenacin por Insercin y por Seleccin, respectivamente. Ambos son algoritmos cuadrticos,
pero es posible que una mejor implementacin lleve a un algoritmo ms rpido. En el captulo de Tipos de Datos Abstractos se vi que una
forma de obtener una implementacin eficiente de colas de prioridad es utilizando una estructura de datos llamada heap.
Implementacin de Heapsort
Al aplicar heaps en la implementacin de cola de prioridad para construir un algoritmo de ordenacin, se obtiene un algoritmo llamado
Heapsort, para el cual resulta que tanto la fase de construccin de la cola de prioridad, como la fase de ordenacin, tienen ambas costo O(n
log n), de modo que el algoritmo completo tiene ese mismo costo.
Por lo tanto, Heapsort tiene un orden de magnitud que coincide con la cota inferior, esto es, es ptimo incluso en el peor caso. Ntese que
esto no era as para Quicksort, el cual era ptimo en promedio, pero no en el peor caso.
De acuerdo a la descripcin de esta familia de algoritmos, dara la impresin de que en la fase de construccin del heap se requerira un
arreglo aparte para el heap, distinto del arreglo de entrada. De la misma manera, se requerira un arreglo de salida aparte, distinto del
heap, para recibir los elementos a medida que van siendo extrados en la fase de ordenacin.
En la prctica, esto no es necesario y basta con un slo arreglo: todas las operaciones pueden efectuarse directamente sobre el arreglo de
entrada.
En primer lugar, en cualquier momento de la ejecucin del algoritmo, los elementos se encuentran particionados entre aquellos que estn ya
o an formando parte del heap, y aquellos que se encuentran an en el conjunto de entrada, o ya se encuentran en el conjunto de salida,
segn sea la fase. Como ningn elemento puede estar en ms de un conjunto a la vez, es claro que, en todo momento, en total nunca se
necesita ms de n casilleros de memoria, si la implementacin se realiza bien.
En el caso de Heapsort, durante la fase de construccin del heap, podemos utilizar las celdas de la izquierda del arreglo para ir "armando"
el heap. Las celdas necesarias para ello se las vamos "quitando" al conjunto de entrada, el cual va perdiendo elementos a medida que se
insertan en el heap. Al concluir esta fase, todos los elementos han sido insertados, y el arreglo completo es un solo gran heap.
converted by W eb2PDFConvert.com
En la fase de ordenacin, se van extrayendo elementos del heap, con lo cual este se contrae de tamao y deja espacio libre al final, el cual
puede ser justamente ocupado para ir almacenando los elementos a medida que van saliendo del heap (recordemos que van apareciendo
en orden decreciente).
converted by W eb2PDFConvert.com
lo cual es igual a n.
Radix sort
Los mtodos anteriores operan mediante comparaciones de llaves, y estn sujetos, por lo tanto, a la cota inferior O(n log n). Veremos a
continuacin un mtodo que opera de una manera distinta, y logra ordenar el conjunto en tiempo lineal.
Supongamos que queremos ordenar n nmeros, cada uno de ellos compuesto de k dgitos decimales. El siguiente es un ejemplo con n=10,
k=5.
73895
93754
82149
99046
04853
94171
54963
70471
80564
66496
Imaginando que estos dgitos forman parte de una matriz, podemos decir que a[i,j] es el j-simo del i-simo elemento del conjunto.
Es fcil, en una pasada, ordenar el conjunto si la llave de ordenacin es un solo dgito, por ejemplo el tercero de izquierda a derecha:
99046
82149
94171
70471
66496
80564
93754
73895
04853
54963
Llamemos j a la posicin del dgito mediante el cual se ordena. La ordenacin se puede hacer utilizando una cola de entrada, que contiene
al conjunto a ordenar, y un arreglo de 10 colas de salida, subindicadas de 0 a 9. Los elementos se van sacando de la cola de entrada y se
van encolando en la cola de salida Q[dj], donde dj es el j-simo dgito del elemento que se est transfiriendo.
Al terminar este proceso, los elementos estn separados por dgito. Para completar la ordenacin, basta concatenar las k colas de salida y
formar nuevamente una sola cola con todos los elementos.
Este proceso se hace en una pasada sobre los datos, y toma tiempo O(n).
Para ordenar el conjunto por las llaves completas, repetimos el proceso dgito por dgito, en cada pasada separando los elementos segn el
converted by W eb2PDFConvert.com
valor del dgito respectivo, luego recolectndolos para formar una sola cola, y realimentando el proceso con esos mismos datos. El conjunto
completo queda finalmente ordenado si los dgitos se van tomando de derecha a izquierda (esto no es obvio!).
Como hay que realizar k pasadas y cada una de ellas toma tiempo O(n), el tiempo total es O(k n), que es el tamao del archivo de entrada
(en bytes). Por lo tanto, la ordenacin toma tiempo lineal en el tamao de los datos.
El proceso anterior se puede generalizar para cualquier alfabeto, no slo dgitos (por ejemplo, ASCII). Esto aumenta el nmero de colas de
salida, pero no cambia sustancialmente el tiempo que demora el programa.
la cual tiene solucin O(n log n), de modo que el algoritmo resulta ser ptimo.
Esto mismo se puede implementar en forma no recursiva, agrupando los elementos de a dos y mezclndolos para formar pares ordenados.
Luego mezclamos pares para formar cudruplas ordenadas, y as sucesivamente hasta mezclar las ltimas dos mitades y formar el conjunto
completo ordenado. Como cada "ronda" tiene costo lineal y se realizan log n rondas, el costo total es O(n log n).
La idea de Mergesort es la base de la mayora de los mtodos de ordenamiento externo, esto es, mtodos que ordenan conjuntos
almacenados en archivos muy grandes, en donde no es posible copiar todo el contenido del archivo a memoria para aplicar alguno de los
mtodos estudiados anteriormente.
En las implementaciones prcticas de estos mtodos, se utiliza el enfoque no recursivo, optimizado usando las siguientes ideas:
No se comienza con elementos individuales para formar pares ordenados, sino que se generan archivos ordenados lo ms grandes
posibles. Para esto, el archivo de entrada se va leyendo por trozos a memoria y se ordena mediante Quicksort, por ejemplo.
En lugar de mezclar slo dos archivos se hace una mezcla mltiple (con k archivos de entrada. Como en cada iteracin hay k
candidatos a ser el siguiente elemento en salir, y siempre hay que extrar al mnimo de ellos y sustituirlo en la lista de candidatos por su
sucesor, la estructura de datos apropiada para ello es un heap.
En caso que no baste con una pasada de mezcla mltiple para ordenar todo el archivo, el proceso se repite las veces que sea
necesario.
Al igual que en los casos anteriores, el costo total es O(n log n).
converted by W eb2PDFConvert.com
Grafos
1.
2.
3.
4.
Definiciones Bsicas
Recorridos de Grafos
rbol Cobertor Mnimo
Distancias Mnimas en un Grafo Dirigido
Definiciones Bsicas
Un grafo consiste de un conjunto V de vrtices (o nodos) y un conjunto E de arcos que conectan a esos vrtices.
Ejemplos:
V = {v1,v2,v3,v4,v5}
E = { {v1,v2}, {v1,v3}, {v1,v5}, {v2,v3},
{v3,v4}, {v4,v5} }
V = {v1,v2,v3,v4}
E = { (v1,v2), (v2,v2), (v2,v3), (v3,v1),
(v3,v4), (v4,v3) }
Adems de esto, los grafos pueden ser extendidos mediante la adicin de rtulos (labels) a los arcos. Estos rtulos pueden representar
costos, longitudes, distancias, pesos, etc.
v2
v2, v3
v1, v4
v3
Esto utiliza espacio O(|E|) y permite acceso eficiente a los vecinos, pero no hay acceso aleatorio a los arcos.
converted by W eb2PDFConvert.com
Un grafo es conexo si desde cualquier vrtice existe un camino hasta cualquier otro vrtice del grafo.
Se dice que un grafo no dirigido es un rbol si es conexo y acclico.
Recorridos de Grafos
En muchas aplicaciones es necesario visitar todos los vrtices del grafo a partir de un nodo dado. Algunas aplicaciones son:
Encontrar ciclos
Encontrar componentes conexas
Encontrar rboles cobertores
Hay dos enfoque bsicos:
Recorrido (o bsqueda) en profundidad (depth-first search):
La idea es alejarse lo ms posible del nodo inicial (sin repetir nodos), luego devolverse un paso e intentar lo mismo por otro camino.
Recorrido (o bsqueda) en amplitud (breadth-first search):
Se visita a todos los vecinos directos del nodo inicial, luego a los vecinos de los vecinos, etc.
Para hacer un recorrido en profundidad a partir del nodo v, utilizamos el siguiente programa principal:
n=0;
for(todo w)
DFN[w]=0;
DFS(v);
Si hubiera ms de una componente conexa, esto no llegara a todos los nodos. Para esto podemos hacer:
n=0;
ncc=0; // nmero de componentes conexas
converted by W eb2PDFConvert.com
for(todo w)
DFN[w]=0;
while(existe v en V con DFN[v]==0)
{
++ncc;
DFS(v);
}
rboles cobertores
Dado un grafo G no dirigido, conexo, se dice que un subgrafo T de G es un rbol cobertor si es un rbol y contiene el mismo conjunto de
nodos que G.
Todo recorrido de un grafo conexo genera un rbol cobertor, consistente del conjunto de los arcos utilizados para llegar por primera vez a
cada nodo.
Para un grafo dado pueden existir muchos rboles cobertores. Si introducimos un concepto de "peso" (o "costo") sobre los arcos, es
interesante tratar de encontrar un rbol cobertor que tenga costo mnimo.
Algoritmo de Kruskal
Este es un algoritmo del tipo "avaro" ("greedy"). Comienza inicialmente con un grafo que contiene slo los nodos del grafo original, sin
arcos. Luego, en cada iteracin, se agrega al grafo el arco ms barato que no introduzca un ciclo. El proceso termina cuando el grafo est
completamente conectado.
En general, la estrategia "avara" no garantiza que se encuentre un ptimo global, porque es un mtodo "miope", que slo optimiza las
decisiones de corto plazo. Por otra parte, a menudo este tipo de mtodos proveen buenas heursticas, que se acercan al ptimo global.
En este caso, afortunadamente, se puede demostrar que el mtodo "avaro" logra siempre encontrar el ptimo global, por lo cual un rbol
cobertor encontrado por esta va est garantizado que es un rbol cobertor mnimo.
Una forma de ver este algoritmo es diciendo que al principio cada nodo constituye su propia componente conexa, aislado de todos los
converted by W eb2PDFConvert.com
dems nodos. Durante el proceso de construccin del rbol, se agrega un arco slo si sus dos extremos se encuentran en componentes
conexas distintas, y luego de agregarlo esas dos componentes conexas se fusionan en una sola.
Para la operatoria con componentes conexas supondremos que cada componente conexa se identifica mediante un representante
cannico (el "lider" del conjunto), y que se dispone de las siguientes operaciones:
Union(a,b): Se fusionan las componentes cannicas representadas por a y b, respectivamente.
Find(x): Encuentra al representante cannico de la componente conexa a la cual pertenece x.
Con estas operaciones, el algoritmo de Kruskal se puede escribir as:
Ordenar los arcos de E en orden creciente de costo;
C = { {v} | v est en V }; // El conjunto de las componentes conexas
while( |C| > 1 )
{
Sea e={v,w} el siguiente arco en orden de costo creciente;
if(Find(v)!=Find(w))
{
Agregar e al rbol;
Union(Find(v), Find(w));
}
}
El tiempo que demora este algoritmo est dominado por lo que demora la ordenacin de los arcos. Si |V|=n y |E|=m, el tiempo es O(m
log m) ms lo que demora realizar m operaciones Find ms n operaciones Union.
Es posible implementar Union-Find de modo que las operaciones Union demoran tiempo constante, y las operaciones Find un tiempo casi
constante. Ms precisamente, el costo amortizado de un Find est acotado por log* n, donde log* n es una funcin definida como el
nmero de veces que es necesario tomar el logaritmo de un nmero para que el resultado sea menor que 1.
Por lo tanto, el costo total es O(m log m) o, lo que es lo mismo, O(m log n) (por qu?).
Ejemplo:
Lema
Sea V' subconjunto propio de V, y sea e={v,w} un arco de costo mnimo tal que v est en V' y w est en V-V'. Entonces
existe un rbol cobertor mnimo que incluye a e.
Este lema permite muchas estrategias distintas para escoger los arcos del rbol. Veamos por ejemplo la siguiente:
Algoritmo de Prim
Comenzamos con el arco ms barato, y marcamos sus dos extremos como "alcanzables". Luego, a cada paso, intentamos extender
nuestro conjunto de nodos alcanzables agregando el arco ms barato que tenga uno de sus extremos dentro del conjunto alcanzable y el
otro fuera de l.
De esta manera, el conjunto alcanzable se va extendiendo como una "mancha de aceite".
Sea e={v,w} un arco de costo mnimo en E;
Agregar e al rbol;
A={v,w}; // conjunto alcanzable
while(A!=V)
{
Encontrar el arco e={v,w} ms barato con v en A y w en V-A;
Agregar e al rbol;
Agregar w al conjunto alcanzable A;
}
Para implementar este algoritmo eficientemente, podemos mantener una tabla donde, para cada nodo de V-A, almacenamos el costo del
arco ms barato que lo conecta al conjunto A. Estos costos pueden cambiar en cada iteracin.
converted by W eb2PDFConvert.com
Si se organiza la tabla como una cola de prioridad, el tiempo total es O(m log n). Si se deja la tabla desordenada y se busca
linealmente en cada iteracin, el costo es O(n2). Esto ltimo es mejor que lo anterior si el grafo es denso.
Implementaciones:
Usando una cola de prioridad para la tabla D el tiempo es O(m log n).
Usando un arreglo con bsqueda secuencial del mnimo el tiempo es O(n2).
Ejemplo:
converted by W eb2PDFConvert.com
Al terminar esta iteracin, las distancias calculadas ahora incluyen la posibilidad de pasar por nodos intermedios de numeracin <=k, con
lo cual estamos listos para ir a la iteracin siguiente.
Para inicializar la matriz de distancias, se utilizan las distancias obtenidas a travs de un arco directo entre los pares de nodos (o infinito si
no existe tal arco). La distancia inicial entre un nodo y s mismo es cero.
for(1<=i,j<=n)
D[i,j]=cost(i,j); // infinito si no hay arco entre i y j
for(1<=i<=n)
D[i,i]=0;
for(k=1,...,n)
for(1<=i,j<=n)
D[i,j]=min(D[i,j], D[i,k]+D[k,j]);
min
infinito <infinito
infinito infinito <infinito
<infinito <infinito <infinito
or 0 1
0 01
1 11
+
infinito <infinito
infinito infinito infinito
<infinito infinito <infinito
and 0 1
0 00
1 01
converted by W eb2PDFConvert.com
Bsqueda en texto
1. Algoritmo de fuerza bruta.
2. Algoritmo Knuth-Morris-Pratt (KMP).
3. Algoritmo Boyer-Moore.
Boyer-Moore-Horspool (BMH).
Boyer-Moore-Sunday (BMS).
La bsqueda de patrones en un texto es un problema muy importante en la prctica. Sus aplicaciones en computacin son variadas, como
por ejemplo la bsqueda de una palabra en un archivo de texto o problemas relacionados con biologa computacional, en donde se requiere
buscar patrones dentro de una secuencia de ADN, la cual puede ser modelada como una secuencia de caracteres (el problema es ms
complejo que lo descrito, puesto que se requiere buscar patrones en donde ocurren alteraciones con cierta probabilidad, esto es, la
bsqueda no es exacta).
En este captulo se considerar el problema de buscar la ocurrencia de un patrn dentro de un texto. Se utilizarn las siguientes
convenciones:
n denotar el largo del texto en donde se buscar el patrn, es decir, texto = a1 a2 ... an.
m denotar el largo del patrn a buscar, es decir, patrn = b1 b2 ... bm.
Por ejemplo:
Texto = "analisis de algoritmos".
Patrn = "algo".
Si se detiene la bsqueda por una discrepancia, se desliza el patrn en una posicin hacia la derecha y se intenta calzar el patrn
nuevamente.
comparaciones de caracteres.
Sea X la parte del patrn que calza con el texto, e Y la correspondiente parte del texto, y suponga que el largo de X es j. El algoritmo de
fuerza bruta mueve el patrn una posicin hacia la derecha, sin embargo, esto puede o no puede ser lo correcto en el sentido que los
primeros j-1 caracteres de X pueden o no pueden calzar los ltimos j-1 caracteres de Y.
converted by W eb2PDFConvert.com
La observacin clave que realiza el algoritmo Knuth-Morris-Pratt (en adelante KMP) es que X es igual a Y, por lo que la pregunta planteada
en el prrafo anterior puede ser respondida mirando solamente el patrn de bsqueda, lo cual permite precalcular la respuesta y
almacenarla en una tabla.
Por lo tanto, si deslizar el patrn en una posicin no funciona, se puede intentar deslizarlo en 2, 3, ..., hasta j posiciones.
Se define la funcin de fracaso (failure function) del patrn como:
Intuitivamente, f(j) es el largo del mayor prefijo de X que adems es sufijo de X. Note que j = 1 es un caso especial, puesto que si hay una
discrepancia en b1 el patrn se desliza en una posicin.
Si se detecta una discrepancia entre el patrn y el texto cuando se trata de calzar bj+1, se desliza el patrn de manera que bf(j) se encuentre
donde bj se encontraba, y se intenta calzar nuevamente.
Suponiendo que se tiene f(j) precalculado, la implementacin del algoritmo KMP es la siguiente:
// n = largo del texto
// m = largo del patron
// Los indices comienzan desde 1
int k=0;
int j=0;
while (k<n && j<m)
{
while (j>0 && texto[k+1]!=patron[j+1])
{
j=f[j];
}
if (texto[k+1])==patron[j+1]))
{
j++;
}
k++;
}
// j==m => calce, j el patron estaba en el texto
Ejemplo:
El tiempo de ejecucin de este algoritmo no es difcil de analizar, pero es necesario ser cuidadoso al hacerlo. Dado que se tienen dos
converted by W eb2PDFConvert.com
ciclos anidados, se puede acotar el tiempo de ejecucin por el nmero de veces que se ejecuta el ciclo externo (menor o igual a n) por el
nmero de veces que se ejecuta el ciclo interno (menor o igual a m), por lo que la cota es igual a
, que es igual a lo que demora el
algoritmo de fuerza bruta!.
El anlisis descrito es pesimista. Note que el nmero total de veces que el ciclo interior es ejecutado es menor o igual al nmero de veces
que se puede decrementar j, dado que f(j)<j. Pero j comienza desde cero y es siempre mayor o igual que cero, por lo que dicho nmero es
menor o igual al nmero de veces que j es incrementado, el cual es menor que n. Por lo tanto, el tiempo total de ejecucin es
. Por otra
parte, k nunca es decrementado, lo que implica que el algoritmo nunca se devuelve en el texto.
Queda por resolver el problema de definir la funcin de fracaso, f(j). Esto se puede realizar inductivamente. Para empezar, f(1)=0 por
definicin. Para calcular f(j+1) suponga que ya se tienen almacenados los valores de f(1), f(2), ..., f(j). Se desea encontrar un i+1 tal que el
(i+1)-simo carcter del patrn sea igual al (j+1)-simo carcter del patrn.
Para esto se debe cumplir que i=f(j). Si bi+1=bj+1, entonces f(j+1)=i+1. En caso contrario, se reemplaza i por f(i) y se verifica nuevamente la
condicin.
El algoritmo resultante es el siguiente (note que es similar al algoritmo KMP):
// m es largo del patron
// los indices comienzan desde 1
int[] f=new int[m];
f[1]=0;
int j=1;
int i;
while (j<m)
{
i=f[j];
while (i>0 && patron[i+1]!=patron[j+1])
{
i=f[i];
}
if (patron[i+1]==patron[j+1])
{
f[j+1]=i+1;
}
else
{
f[j+1]=0;
}
j++;
}
El tiempo de ejecucin para calcular la funcin de fracaso puede ser acotado por los incrementos y decrementos de la variable i, que es
.
Por lo tanto, el tiempo total de ejecucin del algoritmo, incluyendo el preprocesamiento del patrn, es
Algoritmo Boyer-Moore
Hasta el momento, los algoritmos de bsqueda en texto siempre comparan el patrn con el texto de izquierda a derecha. Sin embargo,
suponga que la comparacin ahora se realiza de derecha a izquierda: si hay una discrepancia en el ltimo carcter del patrn y el carcter
del texto no aparece en todo el patrn, entonces ste se puede deslizar m posiciones sin realizar ninguna comparacin extra. En particular,
no fue necesario comparar los primeros m-1 caracteres del texto, lo cual indica que podra realizarse una bsqueda en el texto con menos
d e n comparaciones; sin embargo, si el carcter discrepante del texto se encuentra dentro del patrn, ste podra desplazarse en un
nmero menor de espacios.
El mtodo descrito es la base del algoritmo Boyer-Moore, del cual se estudiarn dos variantes: Horspool y Sunday.
Boyer-Moore-Horspool (BMH)
El algoritmo BMH compara el patrn con el texto de derecha a izquierda, y se detiene cuando se encuentra una discrepancia con el texto.
Cuando esto sucede, se desliza el patrn de manera que la letra del texto que estaba alineada con bm, denominada c, ahora se alinie con
algn bj, con j<m, si dicho calce es posible, o con b0, un carcter ficticio a la izquierda de b1, en caso contrario (este es el mejor caso del
algoritmo).
Para determinar el desplazamiento del patrn se define la funcin siguiente como:
converted by W eb2PDFConvert.com
0 si c no pertenece a los primeros m-1 caracteres del patrn (Por qu no se considera el carcter bm?).
j si c pertenece al patrn, donde j<m corresponde al mayor ndice tal que bj==c.
Esta funcin slo depende del patrn y se puede precalcular antes de realizar la bsqueda.
El algoritmo de bsqueda es el siguiente:
// m es el largo del patron
// los indices comienzan desde 1
int k=m;
int j=m;
while(k<=n && j>=1)
{
if (texto[k-(m-j)]==patron[j])
{
j--;
}
else
{
k=k+(m-siguiente(a[k]));
j=m;
}
}
// j==0 => calce!, j>=0 => no hubo calce.
Se puede demostrar que el tiempo promedio que toma el algoritmo BMH es:
donde c es el tamao del alfabeto (c<<n). Para un alfabeto razonablemente grande, el algoritmo es
En el peor caso, BMH tiene el mismo tiempo de ejecucin que el algoritmo de fuerza bruta.
Boyer-Moore-Sunday (BMS)
El algoritmo BMH desliza el patrn basado en el smbolo del texto que corresponde a la posicin del ltimo carcter del patrn. Este
siempre se desliza al menos una posicin si se encuentra una discrepancia con el texto.
Es fcil ver que si se utiliza el carcter una posicin ms adelante en el texto como entrada de la funcin siguiente el algoritmo tambin
funciona, pero en este caso es necesario considerar el patrn completo al momento de calcular los valores de la funcin siguiente. Esta
variante del algoritmo es conocida como Boyer-Moore-Sunday (BMS).
Es posible generalizar el argumento, es decir, se pueden utilizar caracteres ms adelante en el texto como entrada de la funcin
siguiente? La respuesta es no, dado que en ese caso puede ocurrir que se salte un calce en el texto.
converted by W eb2PDFConvert.com
Algoritmos Probabilsticos
En muchos casos, al introducir elecciones aleatorias en un algoritmo se pueden obtener mejores rendimientos que al aplicar el algoritmo
determinstico puro.
Un algoritmo tipo Montecarlo asegura un tiempo fijo de ejecucin, pero no est garantizado que la respuesta sea correcta, aunque lo puede
ser con alta probabilidad.
U n algoritmo tipo Las Vegas siempre entrega la respuesta correcta, pero no garantiza el tiempo total de ejecucin, aunque con alta
probabilidad ste ser bajo.
Sea
. Si
Algoritmo:
while (true)
{
Colorear los elementos aleatoriamente;
if (ningn Ci es homogeneo)
break;
}
Pr(Ci homogeneo) = Pr(todos los elementos de Ci rojos) + Pr(todos los elementos de Ci azules) = 1/2r + 1/2r = 1/2r-1
=> Pr(algn Ci homogeneo) = k/2r-1 <= 1/2 (ya que k<=2r-2).
Esto implica que en promedio el ciclo se ejecuta 2 veces => O(k*r).
converted by W eb2PDFConvert.com
Compresin de datos
En esta seccin veremos la aplicacin de la teora de rboles a la compresin de datos. Por compresin de datos entendemos cualquier algoritmo que reciba una
cadena de datos de entrada y que sea capaz de generar una cadena de datos de salida cuya representacin ocupa menos espacio de almacenamiento, y que
permite -mediante un algoritmo de descompresin- recuperar total o parcialmente el mensaje recibido inicialmente. A nosotros nos interesa particularmente los
algoritmos de compresin sin prdida, es decir, aquellos algoritmos que permiten recuperar completamente la cadena de datos inicial.
Codificacin de mensajes
Supongamos que estamos codificando mensajes en binario con un alfabeto de tamao . Para esto se necesitan
bits
.-...
--..
Podemos ver en el rbol que letras de mayor probabilidad de aparicin (en idioma ingls) estn ms cerca de la raz, y por lo tanto tienen una codificacin ms
corta que letras de baja frecuencia.
Problema: este cdigo no es auto-delimitante
Por ejemplo, SOS y IAMS tienen la misma codificacin
Para eliminar estas ambigedades, en morse se usa un tercer delimitador (espacio) para separar el cdigo de cada letra. Se debe tener en cuenta que este
problema se produce slo cuando el cdigo es de largo variable (como en morse), pues en otros cdigos de largo fijo (por ejemplo el cdigo ASCII, donde
cada caracter se representa por 8 bits) es directo determinar cuales elementos componen cada caracter.
La condicin que debe cumplir una codificacin para no presentar ambigedades, es que la codificacin de ningun caracter sea prefijo de otra. Esto nos lleva a
definir rboles que slo tienen informacin en las hojas, como por ejemplo:
converted by W eb2PDFConvert.com
Como nuestro objetivo es obtener la secuencia codificada ms corta posible, entonces tenemos que encontrar la codificacin que en promedio use el menor
largo promedio del cdigo de cada letra.
Problema: Dado un alfabeto
aparezca en un mensaje es
A
B
C
D
E
F
probabilidad
0.30
0.25
0.08
0.20
0.05
0.12
cdigo
00
10
0110
11
0111
010
Entropa de Shannon
Shannon define la entropa del alfabeto como:
El teorema de Shannon dice que el nmero promedio de bits esperable para un conjunto de letras y probabilidades dadas se aproxima a la entropa del alfabeto.
Podemos comprobar esto en nuestro ejemplo anterior donde la entropia de Shannon es:
converted by W eb2PDFConvert.com
Algoritmo de Huffman
El algoritmo de Huffman permite construir un cdigo libre de prefijos de costo esperado mnimo.
Inicialmente, comenzamos con
hojas desconectadas, cada una rotulada con una letra del alfabeto y con una probabilidad (ponderacion o peso).
Entonces la construccin del rbol de Huffman es (los nmeros en negrita indican los rboles con menor peso):
converted by W eb2PDFConvert.com
Se puede ver que el costo esperado es de 2,53 bits por letra, mientras que una codificacin de largo fijo (igual nmero de bits para cada smbolo) entrega un
costo de 3 bits/letra.
El algoritmo de codificacin de Huffman se basa en dos supuestos que le restan eficiencia:
1. supone que los caracteres son generados por una fuente aleatoria independiente, lo que en la prctica no es cierto. Por ejemplo, la probabilidad de
encontrar una vocal despus de una consonante es mucho mayor que la de encontrarla despus de una vocal; despus de una q es muy probable
encontrar una u, etc
2. Debido a que la codificacin se hace caracter a caracter, se pierde eficiencia al no considerar las secuencias de caracteres ms probables que otras.
Lempel Ziv
Una codificacin que toma en cuenta los problemas de los supuestos enunciados anteriormente para la codificacin de Huffman sera una donde no solo se
consideraran caracteres uno a uno, sino que donde adems se consideraran aquellas secuencias de alta probabilidad en el texto. Por ejemplo, en el texto:
converted by W eb2PDFConvert.com
aaabbaabaa
Obtendramos un mayor grado de eficiencia si adems de considerar caracteres como a y b, tambin considersemos la secuencia aa al momento de codificar.
Una generalizacin de esta idea es el algoritmo de Lempel-Ziv. Este algoritmo consiste en separar la secuencia de caracteres de entrada en bloques o secuencias
de caracteres de distintos largos, manteniendo una diccionario de bloques ya vistos. Aplicando el algoritmo de Huffman para estos bloques y sus probabilidades,
se puede sacar provecho de las secuencias que se repitan con ms probabilidad en el texto. El algoritmo de codificacin es el siguiente:
1.- Inicializar el diccionario con todos
los bloques de largo 1
2.- Seleccionar el prefijo ms largo del
mensaje que calce con alguna secuencia W
del diccionario y eliminar W del mensaje
3.- Codificar W con su ndice en el diccionario
4.- Agregar W seguido del primer smbolo del
prximo bloque al diccionario.
5.- Repetir desde el paso 2.
Ejemplo:
Si el mensaje es
la codificacin y los bloques agregados al diccionario seran (donde los bloques reconocidos son mostrados entre parntesis y la secuencia agregada al
diccionario en cada etapa es mostrada como un subndice):
Tericamente, el diccionario puede crecer indefinidamente, pero en la prctica se opta por tener un diccionario de tamao limitado. Cuando se llega al lmite del
diccionario, no se agregan ms bloques.
Lempel-Ziv es una de las alternativas a Huffman. Existen varias otras derivadas de estas dos primeras, como LZW (Lempel-Ziv-Welch), que es usado en
programas de compresin como el compress de UNIX.
converted by W eb2PDFConvert.com