Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Definicin
Es un conjunto finito de reglas que establecen una secuencia de operaciones destinadas a resolver un problema particular
Ejemplo
Algoritmo de Euclides:
do { int r = m % n; if (r0) { m=n n=r } } while (r0) return n;
Caractersticas
Finitud
El algoritmo debe terminar en un nmero finito de pasos Un procedimiento que posea todas las caractersticas de un algoritmo, excepto, posiblemente, la finitud, recibe el nombre de mtodo computacional En la prctica, se requiere que el nmero no slo sea finito, sino muy finito
Caractersticas
Definibilidad
Cada paso de un algoritmo debe definirse de un modo preciso; las acciones a realizar deben estar especificadas rigurosamente y sin ambigedad Para salvar esta dificultad, se han creado lenguajes de programacin. En estos lenguajes cada frase tiene un significado muy definido La expresin de un mtodo computacional en uno de estos lenguajes se denomina programa
Caractersticas
Entrada
Un algoritmo tiene cero o ms entradas
Salida
Un algoritmo tiene cero o ms salidas
Caractersticas
Efectividad Un algoritmo debe ser efectivo. Todas las operaciones deben ser lo bastante bsicas para ser efectuadas de modo exacto y en un tiempo finito por un humano utilizando lpiz y papel Un ejemplo de paso no efectivo sera si m es mayor que la cantidad de nmeros primos gemelos entonces m=m+1
Algoritmos
Con frecuencia se utilizan recetas de cocina como ejemplos de algoritmos Una receta tiene
Finitud Entrada (huevos, harina, etc.) Salida (torta, etc.) No hay definibilidad (una pizca de sal, agtese suavemente, etc.)
Modelo Computacional
Ejecuta las instrucciones secuencialmente Tiene un repertorio de instrucciones simples como suma, resta, multiplicacin, divisin, comparacin, asignacin) que requieren un tiempo constante para ejecutarse Tiene memoria ilimitada No hay operaciones sofisticadas como inversin de matrices u ordenamiento Utiliza datos enteros y de punto flotante Hay un lmite en el tamao de la palabra para los datos enteros y de punto flotante
Anlisis de Algoritmos
Analizar un algoritmo significa estimar los recursos que ste necesita para ejecutarse El recurso ms importante que se suele analizar es el tiempo de ejecucin Tambin se suele analizar la cantidad de memoria y el ancho de banda requeridos
Anlisis de Algoritmos
Diversos factores afectan el tiempo de ejecucin de un algoritmo. Algunos, como el compilador y la computadora utilizada, escapan del modelo terico El factor ms importante es el tamao de los datos de entrada
Tamao de la entrada
El nmero de elementos en caso de un arreglo El valor de un nmero (e.g. euclides) Puede ser ms de un nmero. Por ejemplo, los tiempos de ejecucin de los algoritmos de grafos se suelen expresar en funcin del nmero de vrtices y aristas o arcos
Ejecucin
Original 81 94 11 96 12 35 19 95
Movimientos y comparaciones
1 n n-1 n-1
n 1 j=1
t j t j 1
n-1 n-1
n 1 j= 1
n 1 j=1
T n = c 1 c 3 c 4 c 8 c 9 n c 2 c 3 c 4 c 8 c 9 c 5 t j c 6 c7 t j 1
t j= j
t j =
j=1
n n 1 j= 2
n 1 j =1
n 1 j =1
t j1 = j 1 =
n1 n 2 2
En promedio:
j t j= 2 j n n 1 t = j 2= 4 j =1 j=1
n 1 n 1 n 1
n 1 n 4 j t 1 = 1 = j 2 4 j=1 j=1
n 1
Notaciones asintticas
Las notaciones asintticas permiten expresar el comportamiento de funciones en el lmite, es decir, para valores suficientemente grandes de su parmetro Permiten expresar de manera simplificada el tiempo de ejecucin de un algoritmo en funcin del tamao de los datos de entrada
Notacin O
El smbolo O(g(n)) representa el conjunto de todas las funciones f tales que existen constantes c y n0 tales que 0f(n)cg(n) para nn0 Muchas reglas de lgebra se pueden usar con las notaciones O, pero hay ciertas restricciones. La ms importante es la idea de las igualdades en un solo sentido
Notacin O
f(n)=O(g(n))
Notacin O
Escribimos n2+n=O(n2) pero nunca O(n2)=n2+n porque podramos llegar al disparate n2/2=O(n2)=n2+n O(g(n)) en expresiones representa una funcin annima en el conjunto O(g(n)) Algunas operaciones:
Notacin O
Entonces
1+2+...+n=O(n3) 1+2+...+n=O(n2) 1+2+...+n=n2/2+O(n) Crudo, pero correcto Restrictivo y correcto Ms restrictivo
Notacin O
Notacin
El smbolo (g(n)) representa el conjunto de todas las funciones f tales que existen constantes positivas c y n0 tales que 0cg(n)f(n) para n suficientemente grande (nn0) Decimos que g(n) es una cota asinttica inferior
Notacin
f(n)=(g(n))
Notacin
Notacin
El smbolo (g(n)) representa el conjunto de todas las funciones f tales que existen constantes positivas n0, c1 y c2 tales que 0c1g(n)f(n)c2g(n) para nn0 Decimos que f(n) es una cota asinttica estrecha h(n)=(g(n)) si y slo si h(n)=O(g(n) y h(n)=(g(n))
Notacin
Notacin o
El smbolo o(g(n)) representa el conjunto de todas las funciones f(n) tales que existen constantes positivas n0 y c tales que 0(n)f(n)<cg(n) para nn0 Decimos que g(n) es una cota asinttica superior estricta
Notacin
El smbolo (g(n)) representa el conjunto de todas las funciones f(n) tales que existen constantes positivas n0 y c tales que 0(n)cg(n)<f(n) para nn0 Decimos que g(n) es una cota asinttica inferior estricta
Notaciones o y
Notaciones o y
O: : : = o: < : >
transitiva, refleja, sim.trasp. transitiva, refleja, sim.trasp. transitiva, refleja, simtrica transitiva, sim. trasp. transitiva, sim. trasp.
Simetra traspuesta:
f(n)=O(g(n)) si y slo si g(n)=(f(n)) f(n)=o(g(n)) si y slo si g(n)=(f(n))
Anlisis de Algoritmos
Peor caso Mejor caso Caso promedio Anlisis probabilstico Anlisis amortizado
Anlisis de Algoritmos
Proporciona una cota superior para todas las entradas, incluyendo entradas perversas Las cotas para el caso promedio usualmente son mucho ms difciles de calcular y tienden a ser asintticamente iguales a peor caso Pueden haber mltiples definiciones de promedio
Anlisis Amortizado
En ocasiones el peor caso de una operacin puede considerarse lento Es posible que sea matemticamente imposible que en una secuencia sucesiva de operaciones siempre se presente el peor caso Por ejemplo, una operacin individual en los rboles tipo splay tiene un tiempo (n) para el peor caso, sin embargo, se puede demostrar que una secuencia de m operaciones tiene un tiempo O(mlog(n)). Esto es una cota amortizada.
Anlisis de Algoritmos
Barmetros
El anlisis de muchos algoritmos se facilita cuando es posible aislar una instruccin que se ejecuta por lo menos con tanta frecuencia como cualquier otra instruccin del algoritmo Esa instruccin recibe el nombre de barmetro No pasa nada si algunas instrucciones se ejecutan un nmero constante de veces ms que el barmetro pues su contribucin ser absorbida por la notacin asinttica
Para analizar el ordenamiento por insercin podemos utilizar la comprobacin j>0 && tmp<array[j-1] como barmetro Como vimos antes, en el peor caso esta operacin se ejecuta (n-1)(n-2)/2 = (n2)=O(n2)
El mejor caso es cuando el barmetro slo se tiene que ejecutar una vez (el arreglo ya viene ordenado) en cada iteracin En este caso el nmero de veces que se ejecutara el barmetro sera n-1=(n)=(n)
Rigurosamente, el anlisis del caso promedio implica calcular el valor esperado del tiempo de ejecucin para todas las posibles entradas. Esto implica hacer los clculos para todas las entradas y luego promediar o hacer los clculos en funcin de la distribucin de probabilidad de entrada
Para facilitar el anlisis del algoritmo de ordenamiento por insercin asumamos que todos los elementos son distintos y cada uno de los n! rdenes iniciales es igualmente probable
Anlisis probabilstico
Para todo i entre 0 y n-1 considrese el subarreglo A[0..i]. El rango parcial de A[i] se define como la posicin que ocupara si estuviera ordenado el subarreglo Por ejemplo, el rango parcial de A[3] en [3,6,2,5,1,7,4] es 2 porque T[0..3] despus de ordenado es [2,3,5,6] Claramente, el rango parcial no depende del orden inicial de los elementos
Si las n! permutaciones son igualmente probables, es fcil ver que el rango parcial de A[i] tiene las mismas probabilidades de tomar cualquier valor entre 0 e i Sea k este rango parcial Por definicin de rango parcial y por el hecho de que A[0..i-1] est ordenado, el barmetro se ejecuta i-k+1 veces
Todo valor de k entre 0 e i tiene una probabilidad de ocurrir de 1/(i+1), entonces el nmero medio de veces que se ejecuta para cualquier valor i es
1 i 2 i k1 = i1 k =0 2
i
i =1
i 2 n 1 n 4 = = n2 = n2 2 4
Lazo: multiplicar el tiempo de ejecucin de las instrucciones dentro del lazo por el nmero de iteraciones Lazos anidados: multiplicar tiempo del lazo interno por el nmero de iteraciones de los lazo de adentro hacia afuera Instrucciones consecutivas: se suman los tiempos de las instrucciones IF/ELSE: Se toma el tiempo de la rama que toma ms tiempo y se suma con el tiempo de la evaluacin de la condicin Llamadas recursivas: se resuelve la relacin de recurrencia resultante
Ejemplo de Algoritmo
Dados los enteros (tal vez negativos) A0, A1, A2 ... An -1 , encontrar el valor mximo j de Ak
k= i
Por conveniencia la suma mxima es 0 si todos los enteros son negativos Ejemplo: para los enteros -2, 11, -4, 13, -5 la solucin es 20 (desde A1 hasta A3)
Algoritmo 1 O(n )
3
public static int maxSubSum1( int [ ] a ) { /* 1*/ int maxSum = 0; /* 2*/ for( int i = 0; i < a.length; i++ ) /* 3*/ for( int j = i; j < a.length; j++ ) { /* 4*/ int thisSum = 0; /* 5*/ for( int k = i; k <= j; k++ ) /* 6*/ thisSum += a[ k ]; /* 7*/ if( thisSum > maxSum ) /* 8*/ maxSum = thisSum; } /* 9*/ return maxSum; }
Algoritmo 2 O(n )
2
public static int maxSubSum2( int [ ] a ) { /* 1*/ int maxSum = 0; /* 2*/ for( int i = 0; i < a.length; i++ ) { /* 3*/ int thisSum = 0; /* 4*/ for( int j = i; j < a.length; j++ ) { /* 5*/ thisSum += a[ j ]; /* 6*/ if( thisSum > maxSum ) /* 7*/ maxSum = thisSum; } } /* 8*/ return maxSum; }
T(n)=2T(n/2)+kn=O(n log(n))
Algoritmo 4 O(n)
public static int maxSubSum4( int [ ] a ) { /* 1*/ int maxSum = 0, thisSum = 0; /* 2*/ /* 3*/ /* /* /* /* 4*/ 5*/ 6*/ 7*/ } /* 8*/ } return maxSum; for( int j = 0; j < a.length; j++ ) { thisSum += a[j]; if( thisSum > maxSum ) maxSum = thisSum; else if( thisSum < 0 ) thisSum = 0;
Introduccin
Guido Urdaneta Enero, 2010
Esta gua es simplemente una referencia informal en la que se muestra el anlisis del tiempo de ejecucin de los algoritmos de la presentacin introductoria. Estos algoritmos son tomados del libro Data Structures & Algorithm Anlisis in Java, de Mark Allen Weiss.
Algoritmo 1
public static int maxSubSum1( int [ ] a ) { /* 1*/ int maxSum = 0; /* 2*/ for( int i = 0; i < a.length; i++ ) /* 3*/ for( int j = i; j < a.length; j++ ) { /* 4*/ int thisSum = 0; /* 5*/ for( int k = i; k <= j; k++ ) /* 6*/ thisSum += a[ k ]; /* 7*/ if( thisSum > maxSum ) /* 8*/ maxSum = thisSum; } /* 9*/ return maxSum; }
El tiempo de ejecucin del barmetro es:
n1 n1 j n1 n1 i=0 j=i n1
1 =
=
( j i + 1)
(n i)(n i + 1) 2 i=0
[ 1 = 2 [
n1 i=0
(2n + 1) i + (n + n ) 1
2 i=0 i=0
n1
n1
Algoritmo 2
public static int maxSubSum2( int [ ] a ) { /* 1*/ int maxSum = 0; /* 2*/ for( int i = 0; i < a.length; i++ ) { /* 3*/ int thisSum = 0; /* 4*/ for( int j = i; j < a.length; j++ ) { /* 5*/ thisSum += a[ j ]; /* 6*/ if( thisSum > maxSum ) /* 7*/ maxSum = thisSum; } } /* 8*/ return maxSum; }
Tiempo de ejecucin del barmetro:
n1 n n1 i=0
i=0 j=i
1 =
(n i + 1)
n1 i=0 n1 i=0 n1 i=0
= n 1+ i+ 1 n(n 1) +n 2 = O(n2 ) = n2 +
Algoritmo 3
public static int maxSubSum3( int [ ] a ) { return maxSumRec( a, 0, a.length - 1 ); } private static int maxSumRec( int [ ] a, int left, int right) { /*1*/ if( left == right ) // Base case /*2*/ if( a[ left ] > 0 ) /*3*/ return a[ left ]; else /*4*/ return 0; /*5*/ int center = ( left + right ) / 2; /*6*/ int maxLeftSum = maxSumRec(a, left, center ); /*7*/ int maxRightSum = maxSumRec(a, center + 1, right ); /*8*/ int maxLeftBorderSum = 0, leftBorderSum = 0; /* 9*/ for( int i = center; i >= left; i-- ) { /*10*/ leftBorderSum += a[ i ]; /*11*/ if( leftBorderSum > maxLeftBorderSum ) /*12*/ maxLeftBorderSum = leftBorderSum; } /*13*/ int maxRightBorderSum = 0, rightBorderSum = 0; /*14*/ for( int i = center + 1; i <= right; i++ ) { /*15*/ rightBorderSum += a[ i ]; /*16*/ if( rightBorderSum > maxRightBorderSum ) /*17*/ maxRightBorderSum = rightBorderSum; } /*18*/ return max3( maxLeftSum, maxRightSum, /*19*/ maxLeftBorderSum + maxRightBorderSum ); }
Este es un algoritmo recursivo. Su tiempo de ejecucin viene dado por una relacin de recurrencia. El problema e tamao n se divide en dos subproblemas de tamao n/2 y la combinacin de los dos subproblemas requiere un recorrido lineal de todo el arreglo, que toma tiempo . Por lo tanto el tiempo de ejecucin es T (n) = 2T (n/2) + (n). Ver gua de arreglos para solucin de esta relacin de recurrencia.
Algoritmo 4
public static int maxSubSum4( int [ ] a ) {
3
/* 8*/
int maxSum = 0, thisSum = 0; for( int j = 0; j < a.length; j++ ) { thisSum += a[j]; if( thisSum > maxSum ) maxSum = thisSum; else if( thisSum < 0 ) thisSum = 0; } return maxSum;
En este caso el barmetro es la evaluacin de la condicin de nalizacin del ciclo for, la cual se ejecuta n + 1 = (n) veces.
Guido Urdaneta
Algoritmos fundamentales
Bsqueda Secuencial
int buscar(int c, int *arr, int n) { int i=0; for (i=0; i<n; i++) { if (arr[i] == c) { return i; } } return -1; }
Bsqueda Secuencial
Bsqueda Binaria
int buscar(int c, int *arr, int n) { int inf=0; int sup=n-1; while (inf <= sup) { int med = (inf + sup) / 2; if (c < arr[med]) { sup = med-1; } else if (c > arr[med]) { inf = med+1; } else { return med; } } return -1; //no se encontr }
Bsqueda Binaria
int buscar(DATO *c, DATO *arr, int n, int (*compare)(DATO *, DATO *)) { int inf=0; int sup=n-1; while (inf <= sup) { int med = (inf + sup) / 2; int cmp = compare(c,&arr[med]); if (cmp < 0) { sup = med-1; } else if (cmp > 0) { inf = med+1; } else { return med; } } return -1; //no se encontr }
Bsqueda Binaria
Cada iteracin opera sobre un subarreglo cuyo tamao es la mitad del arreglo de la iteracin anterior. T(n) = T(n/2)+c = O(lg(n))
Quicksort
1. Si el nmero de elementos es 0 o 1, retornar. 2. Seleccionar un elemento del arreglo. Este elemento recibe el nombre de pivote. 3. Particionar los otros elementos en dos grupos: A1 con los elementos menores que el pivote y A2 con los elementos mayores o iguales que el pivote. 2 4. Retornar quicksort(A1), seguido del pivote y seguido de quicksort(A2).
Quicksort
void quicksort(int *arr, int n) { quicksort_rec(arr,0,n-1); } void quicksort_rec(int *arr, int p, int f) { if (p < f) { int c = particionar(arr, p, f); quicksort_rec(arr, p, c-1); quicksort_rec(arr, c+1, f); } }
Quicksort
int particionar(int *arr, int p, int f) { int r = arr[f]; int i = p-1; int j; for (j=p; j<f; j++) { if (arr[j] < r) { i++; swap(arr, i, j); } } swap(arr, i+1, f); return i+1; }
Quicksort
Quicksort
Merge
void merge(int *arr, int p, int c, int f){ int n1 = c - p + 1; int n2 = f - c; int *izq = (int *)malloc(sizeof(int)*(n1+1)); int *der = (int *)malloc(sizeof(int)*(n2+1)); int i,j,k; for (i=0; i<n1; i++) izq[i] = arr[p+i]; for (i=0;i<n2; i++) der[i] = arr[c+i+1]; izq[n1] = INF; der[n2] = INF; i=0; j=0; for (k=p; k<=f; k++) if (izq[i] <= der[j]) arr[k] = izq[i++]; else arr[k] = der[j++]; free(izq); free(der); }
Merge sort
void mergesort(int *arr, int n) { mergesort_rec(arr, 0, n-1); } void mergesort_rec(int *arr, int p, int f) { if (p<f) { int c = (p+f)/2; mergesort_rec(arr, p, c); mergesort_rec(arr, c+1, f); merge(arr, p, c, f); } }
Merge sort
Merge sort
Merge
void merge(int *arr, int p, int c, int f){ int n1 = c - p + 1; int n2 = f - c; int *izq = (int *)malloc(sizeof(int)*(n1+1)); int *der = (int *)malloc(sizeof(int)*(n2+1)); int i,j,k; for (i=0; i<n1; i++) izq[i] = arr[p+i]; for (i=0;i<n2; i++) der[i] = arr[c+i+1]; izq[n1] = INF; der[n2] = INF; i=0; j=0; for (k=p; k<=f; k++) if (izq[i] <= der[j]) arr[k] = izq[i++]; else arr[k] = der[j++]; free(izq); free(der); }
Merge sort
Esta gua es simplemente una referencia informal y toma elementos de diversas fuentes, especialmente del libro Introduction to Algorithms de Cormen et al.
1 Bsqueda Secuencial
Este algoritmo permite encontrar un elemento en un arreglo.
int buscar(Comparable c, Comparable[] arr) { for (int i=0; i<arr.length; i++) { if (arr[i].equals(c)) { return i; } } }
Se denomina bsqueda secuencial porque examina secuencialmente cada uno de los elementos del arreglo. Si el nmero de elementos en el arreglo es n, este algoritmo realiza n = (n) comparaciones en el peor caso. En el caso promedio realizara n/2 = (n) comparaciones. Como puede verse, este algoritmo tiene un tiempo de ejecucin lineal y por eso tambin se le conoce como bsqueda lineal.
2 Bsqueda binaria
La bsqueda secuencial es rpida para arreglos pequeos, pero en el caso de arreglos grandes es ms eciente utilizar bsqueda binaria.
int buscar(Comparable c, Comparable[] arr) { int inf=0; int sup=arr.length-1; while (inf <= sup) { int med = (inf + sup) / 2; int cmp = arr[med].compareTo(c); if (cmp < 0) { sup = med-1; } else if (cmp > 0) { inf = med+1; } else { return med; } } return -1; //no se encontr }
Este algoritmo, por supuesto, slo funciona si el arreglo est ordenado previamente. El anlisis de este algoritmo para el peor caso es ms sencillo si lo interpretamos como si fuera recursivo ya que en cada iteracin se repite el mismo proceso pero considerando la mitad de los elementos a analizar. El tiempo T (d) requerido para resolver el problema cuando quedan pendientes por analizar d elementos es T (d) = T (d/2) + b, siendo b un tiempo constante para realizar las operaciones dentro del lazo y T (d/2) el tiempo requerido para analizar d/2 elementos ya que en la siguiente iteracin se realiza sobre la mitad de los elementos. Para n elementos, esto da lugar a una recurrencia T (n) = T (n/2) + b con T (1) = k, que sera el tiempo requerido para terminar el lazo. Para resolver esta relacin de recurrencia realizamos un cambio de variable y establecemos que n = 2m , por lo tanto, T (n) = T (2m ) = T (2m1 ) + b. Si hacemos tm = T (2m ) nos queda que t m = t m 1 + b (1) y tambien t m 1 = t m 2 + b restando 2 de 1 y agrupando trminos nos queda t m 2 t m 1 + t m 2 = 0 (3) (2)
la cual es una relacin de recurrencia lineal homognea cuyo polinomio caracterstico es x2 2x + 1 = ( x 1)2 que tiene la raz x = 1 con multiplicidad 2. Entonces la solucin de esta recurrencia es tm = cm 1m + c2 m1m = c1 + c2 m. Como n = 2m entonces m = lg n y T (n) = tlg n = c1 + c2 lg n = (log n) = O(log n) 2
3 Quicksort
Quicksort es uno de los mejores algoritmos que existe para ordenar arreglos. La idea general es la siguiente: 1. Si el nmero de elementos es 0 o 1, retornar. 2. Seleccionar un elemento del arreglo. Este elemento recibe el nombre de pivote. 3. Particionar los otros elementos en dos grupos disjuntos: A1 con los elementos menores que el pivote y A2 con los elementos mayores o iguales que el pivote. 4. Retornar quicksort( A1 ), seguido del pivote y seguido de quicksort( A2 ). Como puede verse, quicksort es un algoritmo recursivo y, por lo tanto, su anlisis involucra la resolucin de una relacin de recurrencia. Es evidente que siempre la seleccin del pivote es (1) y una vez seleccionado el pivote, se realizan n 1 = (n) comparaciones para determinar los grupos A1 y A2 .
restando 7 menos 2 veces 8 nos queda tm 4tm1 + 4tm2 = 0, que es una relacin de recurrencia lineal homognea cuyo polinomio caracterstico es x2 4x + 4 = ( x 2)2 , que tiene la raz doble 2. Entonces la solucin de la recurrencia tm es t m = c 1 2m + c2 m 2m como n = 2m entonces m = lg n y como tm = T (2m ) entonces tlg n = T (2lg n ) = T (n) = c1 2lg n + c2 lg n2lg n = c1 n + c2 n lg n = O(n lg n) (9)
i =0
T (i) + kn2
(12)
( n 1) T ( n 1) = 2
Si restamos 13 de 12 obtenemos
n 2 i =0
T ( i ) + k ( n 1)2
(13)
(14)
( n + 1) k T ( n 1) + 2k n n
(15)
Demostraremos por induccin que T (n) (n + 1) lg(n + 1) = O(n lg n), o lo que es lo mismo, T (n 1) cn lg n. Fcilmente podemos encontrar valores de c y n sucientemente grandes tales que (n0 + 1) lg(n0 + 1) T (n0 ), as que lo que resta es demostrar que T (n) c(n + 1) lg(n + 1) asumiendo que T (n 1) cn lg n. Multipliquemos la hiptesis por
n +1 n
y summosle 2k
k n
y nos queda
n+1 k k T (n 1) + 2k = T (n) c(n + 1) lg n + 2k n n n Entonces, slo resta demostrar que c(n + 1) lg n + 2k lo cual es equivalente a k 2k c(n + 1) lg(n + 1) c(n + 1) lg n + n ( ) n+1 k = c(n + 1) lg + n n ) ( ) ( 1 k 1 + c lg 1 + + = cn lg 1 + n n n ( ) 1 Cuando n es grande, los ltimos dos trminos tienden a cero, pero cn lg 1 + n tiende a c (calcular lmite cuando n para vericar), la cual arbitrariamente podemos jarla con un valor mayor que 2k. k c(n + 1) lg(n + 1) n
4 Mergesort
La operacin clave del ordenamiento por fusin es la operacin de fusin, la cual consiste en combinar dos arreglos ordenados en un nico arreglo ordenado ms grande. Si A es un arreglo y p, q, r son ndices de A, tales que el subarreglo 1 est denido entre los ndices p y q y el subarreglo 2 est denido entre los ndices q + 1 y r, entonces denimos la operacin de fusion como:
merge(A,p,q,r) { n1=q-p+1 n2=r-q IZQ es un nuevo arreglo de tamao n1+1 DER es un nuevo arreglo de tamao n2+1 for (i=0;i<n1;i++) IZQ[i]=A[p+i]; for (j=0;j<n2;j++) DER[j]=A[q+1+j]; IZQ[n1]=INFINITO; DER[n2]=INFINITO; i=1; j=1; for (k=p;k<=r;k++) if (IZQ[i]<=DER[j]) A[k] = IZQ[i++]; else A[k] = DER[j++]; }
Si n = r p, entonces el tiempo de ejecucin es (n). El centinela sera la evaluacin de la condicin en el ciclo (k<=r), que se ejecuta n + 1 = (n) veces. El procedimiento de ordenamiento por fusin es entonces:
Listas
Guido Urdaneta
Instituto de C alculo Aplicado - LUZ
8 de febrero de 2010
Listas
1 / 38
Table of Contents
ADT Lista
Listas Enlazadas
ADT Pila
Listas
2 / 38
Table of Contents
ADT Lista
Listas Enlazadas
ADT Pila
Listas
ADT Lista
3 / 38
ADT Lista
El ADT lista es uno de los m as utilizados y consiste en una lista lineal ordenada de elementos. El orden se reere a que el orden de los elementos cuenta. La lista (4, 7, 2) es distinta a la lista (4, 2, 7). Operaciones: A nadir en un extremo A nadir en posici on arbitraria Buscar dado el valor Buscar dada la posici on Borrar en un extremo Borrar dada la posici on Borrar dado el valor Reemplazar dada la posici on Recorrer la lista Las implementaciones m as frecuentes de las listas son los arreglos de tama no variable y las listas enlazadas.
Listas ADT Lista 4 / 38
Table of Contents
ADT Lista
Listas Enlazadas
ADT Pila
Listas
5 / 38
Listas
6 / 38
1 2 3 4 5 6 7
if ( arr . length == tam ) { int [] tmp = arr ; arr = new int [ tmp . length * FACTOR ]; // arr = new int [ tmp . length + INC ]; for ( int i =0; i < tam ; i ++) arr [ i ]= tmp [ i ]; }
Listas
7 / 38
A nadir al nal
1 2
Tiempo de ejecuci on peor caso: O (n) Tiempo de ejecuci on promedio de una secuencia de adiciones: O (1) Ejercicio: Cu al es el tiempo de ejecuci on promedio si la correcci on de tama no es aritm etica?
Listas
8 / 38
A nadir en posici on x
1 2 3 4 5 6
co rr eg i r_ an ad i r (); for ( int i = tam ; i >= x ;i - -) { arr [ i ] = arr [i -1]; } arr [ x ] = valor ; tam ++;
Tiempo de ejecuci on peor caso: O (n) Tiempo de ejecuci on caso promedio: O (n)
Listas
9 / 38
Buscar posici on x
return arr [ x ];
Listas
10 / 38
Buscar valor
Listas
11 / 38
Borrar al nal
1 2
tam - -; co rr eg i r_ bo rr a r ();
Tiempo de ejecuci on peor caso: O (n) Tiempo de ejecuci on promedio de una secuencia de borrados: O (1) Ejercicio: Cu al es el tiempo de ejecuci on promedio si la correcci on de tama no es aritm etica?
Listas
12 / 38
1 2 3 4 5 6 7
ret = arr [ x ]; for ( i = x ;i < tam ; i ++) { arr [ i ] = arr [ i +1]; } tam - -; co rr eg i r_ bo rr a r (); return ret ;
Listas
13 / 38
1 2 3 4 5 6
Listas
14 / 38
arr [ x ] = valor ;
Listas
15 / 38
Recorrido
1 2 3
Listas
16 / 38
Table of Contents
ADT Lista
Listas Enlazadas
ADT Pila
Listas
Listas Enlazadas
17 / 38
Listas enlazadas
Las listas enlazadas permiten un mayor control de la memoria. Su uso es particularmente com un en lenguajes funcionales.
1 2 3 4 5 6 7 8 class Nodo { int dato ; Nodo siguiente ; } class ListaEnlazada { Nodo cab ; }
Listas
Listas Enlazadas
18 / 38
A nadir al principio
1 2 3 4
Nodo nodo = new Nodo (); nodo . dato = valor ; nodo . siguiente = cab ; cab = nodo ;
Listas
Listas Enlazadas
19 / 38
A nadir en posici on x
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Nodo nodo = new Nodo (); nodo . dato = valor ; if ( x ==0) { nodo . siguiente = cab ; cab = nodo ; } else { prev = cab ; tmp = cab . siguiente ; for ( int i =1; i < x ; i ++) { prev = tmp ; tmp = tmp . siguiente ; } prev . siguiente = nodo ; nodo . siguiente = tmp ; }
Tiempo de ejecuci on peor caso: O (n) Tiempo de ejecuci on caso promedio: O (n) Ejercicio: modicar para soportar el caso en que la posici on x es inv alida
Listas Listas Enlazadas 20 / 38
Buscar posici on x
1 2 3 4 5
tmp = cab ; for ( int i =0; i < x ; i ++) { if ( tmp ) tmp = tmp . siguiente ; } return tmp ;
Listas
Listas Enlazadas
21 / 38
Buscar valor
1 2 3 4 5 6 7 8
tmp = cab ; while ( tmp ) { if ( tmp . dato == valor ) return tmp ; tmp = tmp . siguiente ; } return null ;
Tiempo de ejecuci on: O (n) Ejercicio: C omo modicarlo para que se retorne la posici on?
Listas
Listas Enlazadas
22 / 38
Borrar al principio
Listas
Listas Enlazadas
23 / 38
1 2 3 4 5 6 7 8 9 10 11
if ( x ==0) { cab = cab . siguiente ; } else { prev = cab ; tmp = cab . siguiente ; for ( int i =1; i < x ; i ++) { prev = tmp ; tmp = tmp . siguiente ; } prev . siguiente = tmp . siguiente ; }
Tiempo de ejecuci on peor caso: O (n) Tiempo de ejecuci on promedio: O (n) Ejercicio: modicar para que funcione cuando x es una posici on inv alida
Listas
Listas Enlazadas
24 / 38
1 2
Listas
Listas Enlazadas
26 / 38
Recorrido
1 2 3 4 5
Listas
Listas Enlazadas
27 / 38
Table of Contents
ADT Lista
Listas Enlazadas
ADT Pila
Listas
ADT Pila
28 / 38
ADT Pila
El ADT pila es utilizado para implementar una estructura LIFO (last in, rst out). Normalmente se implementa mediante una lista debido a que el orden de inserci on de los elementos importa. Operaciones: Meter (push) Sacar (pop) Consultar (peek)
Listas
ADT Pila
29 / 38
Meter (push)
Se puede implementar con una operaci on de a nadir al nal (o principio) en cualquier implementaci on de lista. Tiempo de ejecuci on: O (1)
Listas
ADT Pila
30 / 38
Sacar (pop)
Se puede implementar con una operaci on de borrar al nal (o principio) en cualquier implementaci on de lista. Tiempo de ejecuci on: O (1)
Listas
ADT Pila
31 / 38
Consultar (peek)
Se puede implementar con una operaci on de obtener (sin borrar) el u ltimo (o primer) elemento en cualquier implementaci on de lista. Tiempo de ejecuci on: O (1)
Listas
ADT Pila
32 / 38
ADT Cola
El ADT cola es utilizado para implementar una estructura FIFO (rst in, rst out). Normalmente se implementa mediante una lista debido a que el orden de inserci on de los elementos importa. Operaciones: Meter (enqueue) Sacar (dequeue)
Listas
ADT Pila
33 / 38
Meter (enqueue)
Se puede implementar con una operaci on de a nadir al nal (o principio) en cualquier implementaci on de lista. Tiempo de ejecuci on: O (1)
Listas
ADT Pila
34 / 38
Sacar (dequeue)
Se debe implementar con una operaci on de borrar en el extremo opuesto de donde se insert o. Tiempo de ejecuci on con arreglos de tama no variable: O (n) Tiempo de ejecuci on con listas enlazadas: O (n) Tiempo de ejecuci on con listas doblemente enlazadas: O (1)
Listas
ADT Pila
35 / 38
1 2 3 4 5 6 7 8 9 10
class Nodo { int dato ; Nodo sig ; Nodo prev ; } class ListaDoble { Node cab ; Node fin ; }
Listas
ADT Pila
36 / 38
1 2 3 4 5 6 7 8 9 10
Nodo tmp = new Nodo (); tmp . dato = valor ; tmp . sig = cab ; tmp . prev = null ; if ( cab == null ) cab = fin = tmp ; else { cab . prev = tmp ; cab = tmp ; }
Listas
ADT Pila
37 / 38
1 2 3 4 5 6 7
tmp = fin . dato ; fin = fin . prev ; if ( fin == null ) cab = null ; else fin . sig = null ; return tmp ;
Listas
ADT Pila
38 / 38
Heaps
Guido Urdaneta
Instituto de C alculo Aplicado - LUZ
Febrero 2010
Heaps
1 / 18
Table of Contents
Heapsort
Heaps
2 / 18
Table of Contents
Heapsort
Heaps
3 / 18
El ADT cola de prioridad es utilizado cuando se deben procesar solicitudes por orden de prioridad en lugar de por orden de llegada. Operaciones: Insertar Sacar m aximo (o m nimo) Ver m aximo (o m nimo) Una cola de prioridad tiene asociada una relaci on de orden, pero no tiene como operaci on el recorrido ordenado de los elementos.
Heaps
4 / 18
Implementaciones Simples
Una posibilidad es una lista ordenada con tiempo O (n) para insertar y tiempo O (1) para sacar. Otra posibilidad es una lista no ordenada que requiere tiempo O (1) para insertar y tiempo O (n) para sacar. Ejercicio: describir estos algoritmos usando arreglos de tama no variable y listas simple y doblemente enlazadas.
Heaps
5 / 18
Table of Contents
Heapsort
Heaps
Heaps Binarios
6 / 18
Heaps Binarios
Un heap binario es un arbol binario con las siguientes propiedades: El arbol est a lleno en todos los niveles excepto el u ltimo, que se llena de izquierda a derecha (propiedad estructural). Esto resulta en que la altura del arbol es O (log n) El nodo padre siempre es menor que sus dos hijos (propiedad de orden)
Heaps
Heaps Binarios
7 / 18
Heaps Binarios
La propiedad estructural permite que los datos se almacenen en un arreglo tal que: El nodo mayor est a en la ra z del arbol Los hijos del nodo i est an en las posiciones 2i + 1 y 2i + 2
Heaps
Heaps Binarios
8 / 18
Clase Heap
1 2 3 4 5 6 7 8 9 10
class Heap { int [] arr ; int tam ; } padre ( i ) { return (i -1)/2; } hijo_izq ( i ) { return 2* i +1; } hijo_der ( i ) { return 2* i +2; }
Heaps
Heaps Binarios
9 / 18
1 2 3 4 5 6 7 8 9 10 11 12 13
void mantener_heap ( int [] arr , int i , int tam ) { int max = i ; int izq = hijo_izq ( i ); int der = hijo_der ( i ); if ( izq < tam && arr [ izq ] > arr [ max ]) max = izq ; if ( der < tam && arr [ der ] > arr [ max ]) max = der ; if ( max != i ) { swap (i , max ); mantener_heap ( arr , max , tam ); } }
Se asume que ambos hijos del nodo i son heaps v alidos Tiempo de ejecuci on: T (n) = T (2n/3) + (1) = O (log n)
Heaps
Heaps Binarios
10 / 18
Sacar m aximo
1 2 3 4 5 6 7 8 9 10
int sacar_max () { if ( tam <= 0) throw new Exception (); int ret = arr [0]; tam - -; arr [0] = arr [ tam ]; co rr e gi r_ bo r ra r ( arr , 0 , tam ); // si se desea de tam . v a r i a b l e mantener_or den ( arr , 0 , tam ); return ret ; }
Heaps
Heaps Binarios
11 / 18
Heaps
Heaps Binarios
12 / 18
Ver m aximo
1 2 3 4
Heaps
Heaps Binarios
13 / 18
Insertar x
1 2 3 4 5 6 7 8 9 10 11
co rr eg i r_ an ad i r (); int hueco = tam ; tam ++; while ( hueco > 0) { int p = padre ( hueco ); if ( x < arr [ p ]) break ; arr [ hueco ] = arr [ p ]; hueco = p ; } arr [ hueco ] = x ;
Heaps
Heaps Binarios
14 / 18
Table of Contents
Heapsort
Heaps
Heapsort
15 / 18
Heapsort
La estructura heapsort se puede usar para ordenar arreglos: La idea es convertir el arreglo en heap y encontrar el mayor elemento. Intercambiar el mayor elemento con el nal del arreglo y corregir el orden del heap, considerando que el mayor ya no forma parte del heap. Al conseguir el 2do. mayor elemento, intercambiarlo con el pen ultimo del arreglo y corregir el orden del heap, considerando que los dos mayores ya no forman parte del heap. Repetir hasta que el tama no del heap sea 1.
Heaps
Heapsort
16 / 18
1 2 3 4 5
void c o n v e r t i r _ e n _ h e a p ( int [] arr ) { for ( int i = arr . length /2 -1; i >=0; i - -) mantener_heap ( arr , i , arr . length ); }
Heaps
Heapsort
17 / 18
Heapsort
1 2 3 4 5 6 7
void heapsort ( int [] arr ) { c o n v e r t i r _ e n _ h e a p ( arr ); for ( int i = arr . length -1; i >0; i - -) swap ( arr , 0 , i ); mantener_heap ( arr , 0 , i ); }
Heaps
Heapsort
18 / 18
Heaps*
Guido Urdaneta 8 de febrero de 2010
1.
El ADT Cola de prioridad es utilizado cuando se quieren procesar elementos por orden de prioridad y no por orden de llegada u otro orden. Las operaciones fundamentales de una cola de prioridad son: Insertar Sacar mnimo (o mximo) Ver mnimo (o mximo) La operacin de insertar agrega un elemento a la cola de prioridad, mientras que la operacin de sacar permite sacar de la lista el elemento de mxima prioridad, estando la mxima prioridad denida mediante una relacin de orden. Es indiferente si se dene como mxima prioridad el mnimo elemento o el mximo elemento del conjunto segn la relacin de orden, ya que para cada relacin de orden R hay una relacin de orden R tal que si a b segn R, entonces b a segn R . Cuando un heap implementa la operacin de sacar mnimo se le llama min-heap, mientras que si la operacin de sacar remueve el mximo se le llama max-heap. Una manera simple (y en general poco eciente) de implementar una cola de prioridad es utilizar una lista (enlazada o basada en arreglos). En este caso una de las operaciones requerir tiempo de ejecucin O(1) y la otra O(n). Por ejemplo, se pueden hacer siempre las inserciones en un extremo en tiempo O(1) y hacer la bsqueda (y remocin) del mnimo mediante una bsqueda lineal en tiempo O(n). La otra opcin es mantener ordenada la lista y realizar inserciones en tiempo O(n) y entonces se puede sacar el mnimo de un extremo de la lista en tiempo O(1).
* Tomado
Algorithm 1 Estructura y algoritmos utilitarios para heaps en (algo parecido a) Java class Heap { int[] arr; //puede ser de cualquier tipo
int tam; } int padre(int i) { return (i-1)/2; } int hijo_izq(int i) { return 2*i+1; } int hijo_der(int i) { return 2*i + 2; }
2.
Heaps Binarios
Una opcin simple y eciente para implementar colas de prioridad es el uso de heaps binarios. Un heap binario es un arreglo (posiblemente de tamao variable) que puede verse como un rbol binario casi completo. Cada posicin del arreglo coresponde a un nodo del rbol. Si el primer elemento del arreglo est en la posicin 1, entonces establecemos que para el nodo en la posicin i del arreglo, su hijo izquierdo est en la posicin 2i, su hijo derecho en la posicin 2i + 1 y su padre en la posicin i/2 . Si el primer elemento del arreglo est en la posicin 0 (como en C, C++, C# y Java), el hijo izquierdo de i est en la posicin 2i + 1, el derecho en 2i + 2 y el padre en (i 1)/2 . En lenguajes como C/Java/etc., se puede tambin ignorar el primero elemento del arreglo (posicin 0) y asumir que el primer elemento del heap est en la posicin 1. En esta gua asumiremos que los arreglos comienzan en la posicin 0 y que el primer elemento del heap est en la posicin 0. Por lo tanto podemos establecer las siguientes estructuras y algoritmos bsicos: En lo sucesivo al referirnos a un heap, nos estaremos reriendo a un min-heap. Todas las operaciones se pueden convertir en operaciones de max-heap modicando el sentido de las desigualdades que apliquen. En todo heap se debe cumplir que el padre de un nodo x debe ser siempre menor o igual que x. La Figura 1 muestra un heap. 2
Figura 1: Heap
2.1.
Teorema 1. La altura de un heap con n elementos es O(log n). Demostracin. Un rbol completo con n elementos y altura h siempre tiene 2h 1 elementos. Para ver esto, en la primera la est la raz que tiene 1 = 20 elementos, la segunda la contiene 2 = 21 elementos, la tercera la contiene 4 = 22 elementos y as sucesivamente hasta la h-sima la, que contiene 2h1 elementos. Entonces debemos demostrar que 2h 1 = 2i1
i=1 h
21 = 2 1 = 1
i=1 0
211
i1 y queremos demostrar que 2h+1 Ahora el paso inductivo. Asumimos que 2h 1 = h i=1 2 +1 i1 +1 i1 i1 + 2h , y sustituimos el primer sumando por la 1 = h . Sabemos que h = h i=1 2 i=1 2 i=1 2 +1 i1 igualdad de la hiptesis y nos queda h = 2h 1 + 2h = 2 2h 1 = 2h+1 1, tal y como i=1 2 queramos demostrar. Entonces sabemos que un rbol completo de n elementos tiene altura h y 2h 1 elementos. Entonces n = 2h 1, de donde 2h = n 1 y h = log(n 1) = (log n).
Esto demuestra que si el heap est completo y tiene n elementos, su altura es (log n). 3
Algorithm 2 Algoritmo para mantener propiedad de orden en un heap void mantener_heap(int[] arr, int i, int tam) {
int min=i; int izq = hijo_izq(i); int der = hijo_der(i); if (izq < tam && arr[izq]<arr[min]) min = izq; if (der < tam && arr[der]<arr[min]) min = der; if (min != i) { swap(i, min); mantener_heap(min); } } void swap(int[] arr, int i, int j) { int tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; }
Qu pasa si el rbol no est completo?. Ya sabemos que si est completo, la altura es (log n), pero este es el caso en que n es mximo para una altura h dada. Debemos demostrar que esto sigue cumplindose para cuando n es mnimo para la misma altura h. En este caso, el rbol est completa hasta la la h 1 y tiene solo elemento en la la h y el nmero total de elementos es n = 1 + 2h1 1 = 2h1 , de donde h 1 = log n, y h = 1 + log n = (log n).
2.2.
El Listado 2 muestra un procedimiento para mantener la propiedad de orden en un heap (arr[padre(i)]<=arr[i] Este procedimiento recibe una posicin i del heap y asume que los subrboles correpondientes a ambos hijos de i son heaps vlidos. Ntese que este algoritmo no convierte un arreglo arbitrario en heap ya que asume que los dos hijos son heaps vlidos, lo cual no es necesariamente cierto en todos los arreglos. El tiempo de ejecucin para el peor caso de este algoritmo corresponde al caso en que el rbol est lo ms desbalanceado posible, que es cuando la ltima la est llena hasta la mitad. En este caso el 4
corregir_anadir(); //si se desea de tamao variable int hueco = tam; tam++; while (hueco > 0) { int p = padre(hueco); if (x > arr[p]) break; arr[hueco] = arr[p]; hueco = p; } arr[hueco] = x; }
subrbol izquierdo de i tiene 2/3 de los elementos1 y por lo tanto el tiempo de ejecucin T (n) sera T (2n/3) + (1), donde (1) es el tiempo constante que toma determinar el mnimo elemento.
2 n) + k. Si hacemos n = Entonces debemos resolver la relacin de recurrencia T (n) = T ( 3
x1
3 x 2
entonces px1 = T
3 x 2 , 3 x1 y 2
La solucin de la versin homognea de la recurrencia nos da la solucin x = 1 y por lo tanto pH x = 1 . Una posible solucin particular sera pP x = 2 x, ya que 2 x = 2 (x 1)+ k, de donde k = 2 , lo cual x log n = T (n) = 1 + k2 x = 1 + k log es vlido. Entonces la solucin sera px = T 3 2 (3/2) = O(log n).
2.3.
Operacin de insercin
El Listado 3 muestra el algoritmo de insercin en un heap. El tiempo de ejecucin de este algoritmo es $O(log n) ya que cada iteracin aumenta la altura del nodo apuntado por la variable hueco y sabemos que la altura total es $O(log n) .
un rbol completo, la ltima la tiene 2h1 elementos y el nmero total de elementos excluyendo la ltima la es 2h1 1. En el heap ms desbalanceado el subrbol derecho es completo y tiene m elementos y el subrbol izquierdo exceptuando la ltima la tambin tiene m elementos, y la ltima la tendra m + 1 elementos. El nmero total de hijos es t = 3m + 1, de los cuales 2m + 1 estn en el subrbol izquierdo, lo cual representa (2m + 1)/(3m + 1), que es aproximadamente 2/3 a medida que m crece.
1 En
if (tam <= 0) throw new Exception(); int ret = arr[0]; tam--; arr[0] = arr[tam]; corregir_borrar(); //si se desea de tamao variable mantener_orden(arr, 0, tam); return ret; } int ver_min() { if (tam <= 0) throw new Exception(); return arr[0]; }
2.4.
Operacin de sacar
El Listado 4 muestra los algoritmo para sacar y ver el mnimo elemento de un heap. El tiempo de ejecucin es dominado por mantener_orden y por lo tanto es O(log n).
2.5.
Heapsort
La estructura de datos heap se puede utilizar para ordenar arreglos. Si se utiliza un min-heap bsicamente hay que reorganizar el arreglo para que se convierta en un heap y entonces el menor elemento quedar en la primera posicin. Luego hay que convertir en minheap el subarreglo desde la segunda hasta la ltima posicin y luego la segunda posicin contendr el segundo menor elemento. Seguir repitiendo hasta que se llegue al ltimo elemento. 2.5.1. Reorganizacin de un arreglo en heap
El algoritmo del Listado 5 reorganiza los elementos de un arreglo de manera tal que cumpla con las propiedades de un heap. Para demostrar que este algoritmo es correcto, demostraremos que en cada iteracin los elementos en las posiciones i + 1, i + 2, , arr.length 1 son heaps vlidos. 6
buildHeap(a); for (int i=0; i<a.length; i++) { swap(a, 0, i); mantener_heap(a, 0, i); } }
Antes de que se ejecute el algoritmo, los elementos en la segunda mitad del arreglo son hojas y por lo tanto son heaps vlidos trivialmente. En cada iteracin los hijos del nodo i estn en posiciones mayores que i y por lo tanto son heaps vlidos, lo cual es la condicin requerida por el algoritmo mantener_heap. Por lo tanto, el nodo i se convierte en un heap vlido tambin y se cumple para el siguiente valor de i (que es i 1) que todos los nodos a la derecha son heaps vlidos. Sabemos que cada llamada a mantener_heap requiere tiempo O(log n) y que se hace un nmero O(n) de llamadas, dando lugar a un tiempo de ejecucin O(n log n). Esta cota es correcta, pero no estrecha. Se puede demostrar que el tiempo es O(n). El listado 6 muestra el algoritmo heapsort. Como puede verse, la funcin downheap, de tiempo O(log n), se ejecuta n 1 veces. Por lo tanto heapsort tiene un tiempo de ejecucin de O(n log n).
Funciones Hash
Guido Urdaneta
Instituto de C alculo Aplicado - LUZ
Febrero 2010
Funciones Hash
1 / 31
Table of Contents
1
Introducci on Funciones hash sobre n umeros enteros Funciones hash sobre arreglos Sumas de chequeo Funciones hash criptogr acamente seguras Tablas Hash ADT Diccionario y Conjunto Encadenamiento Direccionamiento abierto Otras Aplicaciones Filtros de Bloom Hashing consistente C odigos de autenticaci on de mensajes
Funciones Hash
2 / 31
Table of Contents
1
Introducci on Funciones hash sobre n umeros enteros Funciones hash sobre arreglos Sumas de chequeo Funciones hash criptogr acamente seguras Tablas Hash ADT Diccionario y Conjunto Encadenamiento Direccionamiento abierto Otras Aplicaciones Filtros de Bloom Hashing consistente C odigos de autenticaci on de mensajes
Funciones Hash
Introducci on
3 / 31
Introducci on
Una funci on hash mapea un dato (posiblemente grande y de tama no arbitrario) del conjunto U a un conjunto peque no, como el conjunto de enteros entre 0 y M 1. h : U {0, 1, 2, , M 1} Observaciones: Muchas aplicaciones, especialmente b usqueda |U | > M implica colisiones Propiedades deseadas: Bajo costo computacional Uniformidad (minimizar la probabilidad de colisiones)
Funciones Hash
Introducci on
4 / 31
M etodo de divisi on
Para mapear un n umero entero K de tama no no mayor a una palabra de CPU a un entero entre 0 y M 1 se puede hacer h(K ) = K modM Recomendaciones: Hacer que M sea un n umero primo que no divida a 2k a donde k y a son enteros peque nos. Si M es potencia de 2, el resultado es los bits menos signicativos de K . Se recomienda que el valor hash dependa de toda la clave y no s olo de una porci on.
Funciones Hash
Introducci on
5 / 31
M etodo de multiplicaci on
Tambi en se puede hacer h(K ) = M ((cK ) mod1) donde 0 < c < 1 Recomendaciones: Hacer que on aurea c sea la proporci 5 1 /2 0, 6180339887 1 = En este caso valores sucesivos dividen el intervalo [0, 1) seg un la proporci on a urea. La principal ventaja de este m etodo es que el valor de M no es importante Ejemplo: Si c = 0, 618 y M = 1000, h(3) = 1000 ((0, 618 3) mod1) = 1000 (1, 854mod1) = 854 h(1) = 618, h(2) = 236, h(3) = 854, h(4) = 472, h(5) = 90
Funciones Hash Introducci on 6 / 31
Cuando la clave es una cadena de caracteres (u otra secuencia de bytes t pica de una estructura de programaci on en general) se recomienda usar una funci on hash que dependa de todos los caracteres de la cadena, y que dependan de cada car acter de manera diferente El procedimiento usual es combinar todos los caracteres en un entero y luego aplicar el m etodo de divisi on o multiplicaci on para obtener un entero comprendido entre 0 y M 1 Las operaciones de suma y XOR sirven como base para combinar, pero como son conmutativas no pueden usarse solas, ya que diferentes permutaciones del mismo arreglo producir an el mismo resultado
Funciones Hash
Introducci on
7 / 31
1 2 3 4
rot(s[i]) es una rotaci on de bits a la izquierda o la derecha Puede usarse XOR en lugar de suma
Funciones Hash
Introducci on
8 / 31
Funciones Hash
Introducci on
9 / 31
Java hashCode()
Funciones Hash
Introducci on
10 / 31
1 2 3 4 5 6
hash = offset ; for i =0 to n -1 { hash = hash * FNV_primo ; hash = hash XOR s [ i ]; } return hash ;
Sirve para calcular valores hash con diferentes tama nos de palabra. Para valores hash de 32 bits recomiendan oset=2166136261 y FNV primo=16777619. Para valores hash de 64 bits recomiendan oset=14695981039346656037 y FNV primo=1099511628211.
Funciones Hash
Introducci on
11 / 31
Sumas de chequeo
El prop osito de estas funciones hash es detectar cambios accidentales en una secuencia de datos que ha sido transmitida o almacenada. Se transmite la secuencia y el valor hash. El receptor compara calcula el valor hash de la secuencia recibida y lo compara con el valor hash recibido. Si hay discrepancia signica que hubo un error. Estos algoritmos no protegen contra modicaciones intencionales introducidas por un atacante capaz de enviar un mensaje falso que se mapee al mismo valor hash del mensaje original.
Funciones Hash
Introducci on
12 / 31
Fletcher-32
Calcula un valor hash de 32 bits sobre una secuencia de bytes d . Se puede expresar como:
1 2 3 4 5 6 sumA = sumB = 0; for i =0 to n -1 { sumA = ( sumA + d [ i ]) mod 65535; sumB = ( sumB + sumA ) mod 65535; } return sumB < <16 + sumA ;
Problema: para mensajes cortos la suma de bytes suele ser mucho menor que 65535 y los valores hash no se distribuyen uniformemente sobre el espacio de 32 bits. Alternativa: usar Fletcher-16, que usa operaciones m odulo 255 en lugar de 65535 y un desplazamiento de 8 bits en lugar de 16.
Funciones Hash
Introducci on
13 / 31
Sirve para calcular un valor hash de una secuencia de bits B = bn1 bn2 b1 b0 Se interpreta como el polinomio B (x ) = bn1 x n1 + bn2 x n2 b1 x + b0 Se mapea a un entero entre 0 y M 1, M = 2m h(B ) = B (x )modP (x ) P (x ) es un polinomio de grado m: P (x ) = x m + pm1 x m1 + + p0 Es f acil de implementar en hardware
Funciones Hash
Introducci on
14 / 31
Deben tener las siguientes caracter sticas: Resistencia preimagen: dado un valor hash h, debe ser dif cil encontrar un mensaje m tal que hash(m) = h. Segunda resistencia preimagen: Dado un mensaje m1 debe ser dif cil encontrar un mensaje m2 tal que hash(m1 ) = hash(m2 ). Resistencia a colisiones : Debe ser dif cil encontrar dos mensajes m1 y m2 tales que hash(m1 ) = hash(m2 ). Una modicaci on min uscula (e.g., un bit) en el mensaje original ocasiona cambios en el valor hash comparables a cualquier otro cambio. Ejemplos de funciones hash seguras: MD5, SHA-1, SHA-256, Tiger
Funciones Hash
Introducci on
15 / 31
Table of Contents
1
Introducci on Funciones hash sobre n umeros enteros Funciones hash sobre arreglos Sumas de chequeo Funciones hash criptogr acamente seguras Tablas Hash ADT Diccionario y Conjunto Encadenamiento Direccionamiento abierto Otras Aplicaciones Filtros de Bloom Hashing consistente C odigos de autenticaci on de mensajes
Funciones Hash
Tablas Hash
16 / 31
Operaciones fundamentales: insertar(clave, dato) o insertar(clave): inserta una clave en el conjunto o un par (clave,dato) en el diccionario borrar(clave): retorna un valor booleano que indica si la clave se encontr o o no. En un diccionario puede alternativamente retornar el dato borrado buscar(clave): Devuelve un valor booleano que indica si la clave se encontr o o no. En un diccionario puede retornar el dato asociado a la clave o un valor especial si la clave no existe. Al ADT diccionario tambi en se le llama arreglo asociativo.
Funciones Hash
Tablas Hash
17 / 31
Tablas Hash
Se utiliza un arreglo de tama no M Para almacenar una clave, se determina la posici on en el arreglo con una funci on hash Hay dos m etodos para resolver las colisiones: ecadenamiento y direccionamiento abierto.
Funciones Hash
Tablas Hash
18 / 31
Encadenamiento
En cada posici on del arreglo se utiliza una lista Todas las claves que tengan el mismo valor hash se agregan a la lista correspondiente Las inserciones, eliminaciones y b usquedas se resuelven con las operaciones de lista
Funciones Hash
Tablas Hash
19 / 31
An alisis
Los an alisis se hacen sobre el factor de carga = n/M , donde n es el n umero de claves insertadas en la tabla y M el tama no del arreglo. Si la funci on hash distribuye uniformemente el tama no esperado de las listas es Si se usan listas enlazadas, las operaciones sobre la lista ser an O (1) u O () Sumando el tiempo de c alculo de la funci on hash, estas operaciones quedan O (1) y O (1 + ) Si n = O (M ), entonces = n/M = O (M )/M = O (1) y las operaciones son todas O (1)
Funciones Hash
Tablas Hash
20 / 31
Direccionamiento abierto
Examinar (sondear) la posici on h(K ) Si h(K ) contiene la clave K , la b usqueda termina exitosamente. Si contiene NULO , la b usqueda falla Si h(K ) tiene otra clave, se calcula otra posici on a partir de K y del n umero de sondeo actual (los sondeos se enumeran (0, 1, $) Continuar sondeando hasta encontrar K o NULO
Funciones Hash
Tablas Hash
21 / 31
M etodos de sondeo
Sea K la clave e i es el n umero de sondeo Para buscar una clave K Sondeo lineal: h(k , i ) = (h (k ) + i ) mod M Sondeo cuadr atico: h(k , i ) = h (k ) + c1 i + c2 i 2 mod M donde c1 y c2 deben producir permutaciones de 0, 1, . . . , m 1 Doble hashing: h(k , i ) = (h1 (k ) + ih2 (k )) mod M
Funciones Hash
Tablas Hash
22 / 31
Tiempo de ejecuci on
Si asumimos Dispersi on uniforme Cada clave tiene la misma probabilidad de ser buscada No se borran claves N umero esperado de sondeos en inserciones y b usquedas fallidas: 1/(1 ) 1 1 N umero esperado de sondeos en b usqueda exitosa: como ln 1 aximo m
Funciones Hash
Tablas Hash
23 / 31
Table of Contents
1
Introducci on Funciones hash sobre n umeros enteros Funciones hash sobre arreglos Sumas de chequeo Funciones hash criptogr acamente seguras Tablas Hash ADT Diccionario y Conjunto Encadenamiento Direccionamiento abierto Otras Aplicaciones Filtros de Bloom Hashing consistente C odigos de autenticaci on de mensajes
Funciones Hash
Otras Aplicaciones
24 / 31
Filtros de Bloom
Implementaci on alternativa de conjuntos: No se permite borrar Falsos positivos posibles (la clave no est a, pero se reporta que s est a) Falsos negativos imposibles Ventaja: Ahorro masivo de espacio Muchas aplicaciones pueden tolerar esta sem antica
Funciones Hash
Otras Aplicaciones
25 / 31
Filtros de Bloom
Se tiene una tabla de M bits b0 b1 bM 1 con M razonablemente grande Para insertar la clave K :
1
Calcular l funciones hash h1 (K ), h2 (K ), , hl (K ) diferentes que producen valores hash comprendidos entre 0 y M 1 (los valores hash no son necesariamente diferentes) Para todo i comprendido entre 1 y l , hacer bhi (K ) = 1 Calcular las l funciones hash h1 (K ), h2 (K ), , hl (K ) Si para todo i comprendido entre 1 y l , se cumple que bhi (K ) = 1, entonces se es probable que K est e en el conjunto Si alguno de los bhi (K ) es 0, entonces se tiene la certeza de que K no est a en el conjunto
Funciones Hash
Otras Aplicaciones
26 / 31
Filtros de Bloom
1 e kN /M
Crece a medida que se agregan m as claves (N crece) Decrece a medida que el arreglo es m as grande (M crece) El valor de l que minimiza la probabilidad de falsos positivos es M M N ln 2 0,7 N Asumiendo l optimo, si se desea probabilidad de falsos positivos p entonces n ln p M= (ln 2)2
Funciones Hash
Otras Aplicaciones
27 / 31
Hashing consistente
Si se desea cambiar el tama no de una tabla hash, el procedimiento suele ser recalcular la posici on de todas claves tomando en cuenta el nuevo valor de M Una alternativa es tener un M invariable y sucientemente grande y asignar a cada entrada (bucket) de la tabla un identicador aleatorio entre 0 y M 1 Al insertar una clave K , guardarla en la entrada con identicador m as cercano a K Al agregar un bucket, reasignar al nuevo bucket aquellas claves cuyo valor hash sea m as cercano al identicador del nuevo bucket que al del bucket donde estaban guardadas anteriormente Al eliminar un bucket, reasignar cada claves de dicho bucket al bucket con identicador m as cercano entre los buckets restantes
Funciones Hash
Otras Aplicaciones
28 / 31
Hashing consistente
El n umero esperado de claves a reasignar cuando se a nade o elimina un bucket es N /B , donde N es el n umero total de claves y B es el n umero de buckets. El hashing consistente se utiliza principalmente en sistemas distribuidos donde cada bucket es una computadora separada y la reasignaci on de claves puede ser costosa ya que depende del tama no de las claves (y sus posibles datos asociados) Muchos sistemas P2P para compartir archivos utilizan sistemas similares a este
Funciones Hash
Otras Aplicaciones
29 / 31
Los usuarios A y B comparten una clave secreta A desea enviar un mensaje m a B y desea asegurarse de que el mensaje llegue ntegramente Para esto agrega un c odigo c que depende del mensaje y la clave compartida Si un atacante M adultera el mensaje, no podr a generar un c odigo correcto para el mensaje adulterado, ya que este depende de la clave secreta la cual M no conoce
Funciones Hash
Otras Aplicaciones
30 / 31
Los MACs se pueden implementar utilizando una funci on hash criptogr acamente segura Posibilidades: c = h(m||clave , c = h(clave ||m) o c = h(clave ||m||clave ) En la pr actica se ha demostrado que es m as seguro derivar dos claves a partir de la clave original y utilizar c = h(clave1 ||h(clave2 ||m)) Una manera com un de hacerlo es:
1 2 3 clave1 = ( byte 0 x5c repetido B veces ) XOR clave ; clave2 = ( byte 0 x36 repetido B veces ) XOR clave ; hmac = hash ( clave1 || hash ( clave2 || mensaje ));
donde se asume que h act ua de manera iterativa sobre bloques de B bytes (ver RFC 2104).
Funciones Hash
Otras Aplicaciones
31 / 31
Funciones Hash*
Guido Urdaneta 11 de febrero de 2010
1. Introduccin
Una funcin hash es una funcin que mapea un dato (posiblemente grande y de tamao arbitrario) de un conjunto U a un dato pequeo, usualmente un nmero entero comprendido entre 0 y M 1, el cual puede utilizarse como ndice en un arreglo, por ejemplo. Al dato a mapear suele llamrsele clave, mensaje u otro nombre dependiendo de la aplicacin, mientras que al dato mapeado se le puede llamar valor hash, cdigo hash, o simplemente hash. h : U {0, 1, 2, , M 1} Las funciones hash tienen muchsimas aplicaciones en computacin, siendo la bsqueda una de las ms importantes. La estructura de datos fundamental para realizar bsquedas usando funciones hash es la tabla hash, en la cual el valor hash se interpreta como la posicin de un arreglo en donde se almacena el valor buscado. Puesto que las funciones hash pueden mapear elementos de un conjunto ms grande a uno ms pequeo, es posible que dos o ms claves sean mapeadas al mismo valor hash. En este caso se habla de una colisin y se desea que la probabilidad de stas sea muy baja. Los algoritmos de tabla hash bsicamente se enfocan en cmo resolver las colisiones. El enfoque ms simple es tener una lista para cada posible valor hash, guardar todas las claves con el mismo valor hash en la lista correspondiente y resolver las colisiones con una bsqueda lineal.
Se desea que el costo computacional de una funcin hash sea bajo. Por ejemplo, un rbol balanceado permite hacer bsqueda binaria realizando aproximadamente log n comparaciones. Si el calcular la funcin hash toma un tiempo mayor que realizar log n comparaciones, entonces puede que no tenga sentido usar una tabla hash.
* Tomado
1.1.2.
Uniformidad
Una funcin hash debe mapear los datos esperados de entrada de la manera ms uniforme posible. La razn es que esta es la manera de minimizar el nmero de colisiones, ya que mientras ms colisiones haya, mayor ser el tiempo de ejecucin de los algoritmos basados en funciones hash. Por ejemplo, si hay muchas claves mapeadas al mismo valor hash en una tabla hash, las bsquedas degenerarn en bsquedas lineales.
En la prctica, si bien con frecuencia las claves suelen ser ms grandes que la palabra del computador, se suele utilizar una funcin hash que produzca un valor hash del tamao de la palabra, y luego se aplica uno de estos dos mtodos para transformar dicho valor a un valor comprendido entre 0 y M 1.
2.3. Funciones hash para arreglos y otras estructuras de propsito general en programacin
Cuando la clave es una cadenas de caracteres (o bytes), la distribucin de los datos de entrada suele ser poco uniforme. Por ejemplo, si se trata de cadenas con texto en lenguaje natural (e.g., en espaol) habr letras y slabas que se usan con ms frecuencia que otras. En estos casos, se recomienda utilizar funciones hash que dependan de todos los caracteres de la cadena, y que dependan de cada carcter de manera diferente. El procedimiento usual es combinar todos los caracteres en una sola palabra, y posteriormente usar el mtodo de divisin o multiplicacin para obtener un hash comprendido entre 0 y M -1. Una manera de combinar los caracteres es sumarlos o aplicar la funcin XOR, sin embargo, como estas operaciones son conmutativas, el valor hash producido es el mismo para diferentes permutaciones de los mismos caracteres. A continuacin se presentan varios procedimientos propuestos para combinar los caracteres. 2.3.1. Knott
Knott sugiere que antes de sumar o aplicar XOR, se haga un desplazamiento circular de los datos.
Carter y Wegman proponen calcular el hash de una secuencia s de n caracteres o palabras mediante el uso de n funciones hash independientes: h(s) = (h0 (s0 ) + h1 (s1 ) + + hn1 (sn1 )) modM donde las diferentes hi pueden ser implementadas mediante arreglos precalculados.
El mtodo hashCode en Java se supone que debe retornar una funcin hash apropiada para los objetos de la clase en la cual se dene. Por defecto, se suele retornar la direccin de memoria en la que reside el objeto. En el caso de las cadenas de caracteres inmutables (tipo String), se utiliza la siguiente funcin: h(s) = s0 31n1 + s1 31n2 + sn2 31 + sn1
hash = 0; for i=0 to n-1 { hash = hash * 31; hash = hash + s[i]; } return hash;
2.3.4. Fowler-Noll-Vo (FNV) Hash
La funcin hash FNV funciona para encontrar un entero de n bits a partir de una secuencia s de octetos (bytes de 8 bits). La combinacin involucra una multiplicacin por un nmero primo (similar al Java hashCode) y luego el uso de la funcin XOR (en lugar de la suma de Java hashCode). El hash en lugar de comenzar en cero, comienza con un valor especial, y el nmero primo a utilizar debe tener ciertas propiedades.
hash = offset_basis; for i=0 to n-1 { hash = hash * FNV_prime; hash = hash XOR s[i]; } return hash;
Alternativamente,
hash = offset_basis for each octet_of_data to be hashed hash = hash xor octet_of_data hash = hash * FNV_prime return hash
Las multiplicaciones son mdulo 2n donde n es el nmero de bits del hash. Las operaciones XOR son entre el byte a considerar y los ocho bits ms bajos del hash. Parmetros de hash FNV para calcular hashes de 32 y 64 bits: bits (sin signo) 32 64
Esta funcin hash ha sido muy usada en una gran variedad de aplicaciones prcticas. Para ms informacin consultar http://www.isthe.com/chongo/tech/comp/fnv/index.html .
Estas funciones permiten calcular una suma de chequeo de 32 bits dada una secuencia de bytes. Sea D = d0 , d1 , d2 , , dn1 una secuencia de bytes a la que se quiere calcular una suma de chequeo. La funcin Fletcher-32 se puede expresar como: A = d0 + d1 + + dn1 mod65535 B = (d0 ) + (d0 + d1 ) + + (d0 + d1 + + dn1 )mod65535 Fletcher 32(D) = B 65536 + A o, equivalentemente, con el siguiente algoritmo:
sumA = (sumA + d[i]) mod 65535 ; sumB = (sumB + sumA) mod 65535; } return sumB*65536+sumA;
La funcin Adler-32 se puede expresar como: A = 1 + d0 + d1 + + dn mod65521 B = (1 + d0 ) + (1 + d0 + d1 ) + + (1 + d0 + d1 + + dn )mod65521 Adler 32(D) = B 65536 + A o, equivalentemente, con el siguiente algoritmo:
sumA=sumB=1; for (i=0 to n-1) { sumA = (sumA + d[i]) mod 65521 ; sumB = (sumB + sumA) mod 65521; } return sumB*65536+sumA;
Estas funciones tienen la ventaja de que son fciles de implementar en software. Como desventaja tienen que para mensajes cortos la suma de bytes suele ser mucho menor que 65521, razn por la cual los valores hash no se distribuyen uniformemente sobre el el espacio de 32 bits. Una alternativa es usar Fletcher-16, que utiliza operaciones mdulo 28 1 = 255 en lugar de mdulo 216 1 = 65535. 2.4.2. Cyclic Redundancy Check (CRC)
En este mtodo la clave es una secuencia de bits B = bn1 bn2 b1 b0 cuyos elementos se interpretan como los coecientes de un polinomio B(x) = bn1 xn1 + bn2 xn2 b1 x + b0 . Esta secuencia se mapea a un valor entre 0 y M 1, donde M es una potencia de 2, por ejemplo M = 2m , tomando el residuo de la divisin del polinomio B(x) entre un polinomio P(x) = xm + pm1 xm1 + + p0 de grado m. Entonces h(s) = hm1 hm2 h1 h0 se construye con los coecientes del polinomio resultante de la operacin B(x)modP(x). Este tipo de funcin suele ser ms apropiado para una implementacin en hardware o microprograma, que en software y ha sido utilizado con mucha frecuencia en la deteccin de errores de transmisin y almacenamiento de datos. Existen versiones estandarizadas del algoritmo en las que el polinomio P(x) est especicado. Por ejemplo: CRC-16-ANSI: x16 + x15 + x2 + 1, utilizado por USB, Modbus y muchos otros protocolos. CRC-32-IEEE 802.3: x32 + x29 + x21 + x20 + x15 + x13 + x12 + x11 + x8 + x7 + x6 + x2 + x + 1, utilizado por MPEG-2, PNG y el programa cksum 6
3. Tablas Hash
Las tablas hash permiten implementar diccionarios y conjuntos. Las operaciones fundamentales son: insertar(clave, dato) o insertar(clave) borrar(clave)
1 Difcil
buscar(clave) La diferencia entre un diccionario y un conjunto es que en el diccionario, a la clave se le anexa un dato adicional que no es utilizado por los algoritmos de bsqueda, similar al signicado de una palabra en un diccionario tradicional. La operacin de buscar en un conjunto devuelve verdadero o falso y un un diccionario devuelve el dato asociado o un valor especial que indica que la clave no existe. El tiempo esperado de bsqueda en una tabla hash es O(1) si la funcin hash distribuye las claves uniformemente. El peor caso es (n) y se presenta cuando la funcin hash produce el mismo valor hash para todas las claves. Una tabla hash puede verse como una generalizacin de un arreglo. Con un arreglo se almacena el elemento cuya clave es k en la posicin k. El elemento cuya clave es k se puede conseguir accediendo a la k esima posicin del arreglo. Esto recibe el nombre de direccionamiento directo y es aplicable si podemos asignar un arreglo con una posicin para cada posible clave. Las tablas hash se utilizan cuando no se puede asignar un arreglo con una posicin para cada posible clave, lo cual ocurre con frecuencia. En lugar de almacenar un elemento con clave k en la posicin k, se utiliza una funcin hash h y se almacenar el elemento en la posicin h(k). Como hemos visto, normalmente el conjunto de claves tiene una cardinalidad mayor que la del rango de la funcin hash y por lo tanto es posible que dos claves diferentes tengan asociado el mismo valor hash. Existen dos mtodos para el manejo de colisiones: encadenamiento y direccionamiento abierto. El encadenamiento es con frecuencia la opcin ms simple y efectiva.
El anlisis del tiempo de ejecucin se hace en funcin del factor de carga = n/m, donde n es el nmero de claves en la tabla y m es el tamao del arreglo (nmero de listas enlazadas). El factor de carga es el nmero promedio de elementos por cada lista enlazada.
El peor caso es cuando las n claves estn en la misma posicin del arreglo y se tiene el equivalente a una lista enlazada de tamao n. El peor caso del tiempo de bsqueda es (n) ms el tiempo requerido para calcular la funcin hash. El caso promedio depende de cuan bien la funcin hash distribuye las claves entre las posiciones del arreglo. Para realizar el anlisis de caso promedio realizaremos las siguientes consideraciones: 1. Se asume dispersin uniforme simple: todo elemento tiene la misma probabilidad de caer en cualquiera de las m posiciones. 2. Para j = 0, 1, , M 1, denotamos la longitud de la lista T [ j] como n j . Entonces n = n0 + n1 + + nM 1 . 3. El valor promedio de n j es = n/M . 4. Se asume que la funcin hash se puede calcular en tiempo O(1), de manera tal que el tiempo requerido para buscar el elemento con clave k depende de la longitud nh(k) de la lista T [h(k)]. Consideramos dos casos: 1. Si la tabla hash no contiene elementos con la clave k, entonces la bsqueda falla. 2. Si la tabla contiene un elemento con la clave k entonces la bsqueda es exitosa. Bsqueda fallida Demostracin: Una bsqueda fallida de la clave k implica recorrer hasta el nal la lista T [h(k)]. Esta lista tiene una longitud esperada E [nh(k) ] = . Por lo tanto, el nmero esperado de elementos examinados en una bsqueda fallida es . Si sumamos el tiempo requerido para calcular la funcin hash, el tiempo total es (1 + ). Bsqueda exitosa Demostracin: Asumamos que el elemento x buscado es con igual probabilidad cualquiera de los n elementos de la tabla. El nmero de elementos examinados en una bsqueda exitosa de x es uno ms que el nmero de elementos que aparecen antes de x en la lista enlazada correspondiente. Estos son los elementos despus de x, asumiendo que hacemos las inserciones al principio de la lista. Debemos encontrar el promedio, para los n elementos de la tabla, de cuntos elementos fueron insertados en la lista de x antes de que x fuera insertado. Para i = 1, 2, , n, sea xi el i-simo elemento insertado en la tabla y sea ki la clave de xi . 9 Teorema: Una bsqueda exitosa requiere tiempo O(1 + ) Teorema: Una bsqueda fallida requiere tiempo O(1 + )
Para todo i y j, denamos Xi j como la variable aleatoria que vale 1 si h(ki ) = h(k j ) y 0 de otro modo. Como asumimos dispersin uniforme, tenemos que la probabilidad de que h(ki ) = h(k j ) para valores cualesquiera de i, j es 1/M y por lo tanto E [Xi j ] = 1/M . El nmero esperado de elementos examinados en una bsqueda exitosa es: ( )] ( ) i1 i1 [ ] 1 n 1 n E 1 + Xi j = 1 + E Xi j n i n i =1 j=1 =1 j=1 ( ) i1 1 1 1+ = n j=1 M ( ) 1 n 1 = 1 + (i 1) n i M =1 = 1+ 1 n (i 1) Mn i =1 ( ) 1 n2 + n = 1+ n Mn 2 n1 = 1+ 2M = 1+ 2 2n [
Sumando el tiempo para calcular la funcin hash tendramos que el tiempo total es (2 + 2 2n ) = (1 + ).
Si n = O(M ), es decir, si el nmero de elementos en la tabla es proporcional al nmero de posiciones entonces el tiempo promedio entonces = n/M = O(M )/M = O(1). Entonces la bsqueda requiere en promedio tiempo constante.
10
3. Si la posicin h(k) contiene una clave que no es k, se calcula el ndice de otra posicin a partir de k y del sondeo en el cual se est (los sondeos se enumeran 0, 1, 2, ...). 4. Continuar sondeando hasta que se consiga la clave k o NULO. Se necesita que la secuencia de posiciones sondeadas sea una permutacin de los nmeros de las posiciones 0, 1, . . . , m 1 , de manera tal que se examinen todas las posiciones si es necesario y que no se examine ninguna posicin ms de una vez. As, la funcin hash es h : U {0, 1, . . . , m 1} {0, 1, . . . , m 1} El requerimiento de que la secuencia de posiciones sea una permutacin de 0, 1, . . . , m 1 es equivalente a requerir que la secuencia de sondeos h(k, 0), h(k, 1), . . . , h(k, m 1) sea una permutacin de 0, 1, . . . , m 1 . Para insertar, actuar como si se estuviera haciendo una bsqueda e insertar al encontrar la primera posicin con NULO. 3.2.1. Mtodos para calcular secuencias de sondeo
Lo ideal es dispersin uniforme, que consiste en que cada clave tiene la misma probabilidad de tener las m! permutaciones de 0, 1, . . . , m 1 como su secuencia de sondeos. Es difcil implementar la dispersin uniforme, as que en la prctica se aproxima con tcnicas que al menos garanticen que la secuencia de sondeos es una permutacin de 0, 1, . . . , m 1 . Ninguna de las tcnicas mencionadas a continuacin puede generar todas las m! secuencias de sondeo y utilizarn funciones hash auxiliares, las cuales mapean h : U {0, 1, . . . , m 1} Sondeo lineal Dada una funcin hash auxiliar , la secuencia de sondeos comienza en la posicin h (k) y contina secuencialmente a lo largo de la tabla (regresando a 0 despus de la posicin m 1). Dada la clave k y el nmero de sondeo i (0 i < m), h(k, i) = (h (k) + i) mod m El sondeo lineal tiene la desventaja de que tiende a que se formen secuencias largas de posiciones ocupadas y las secuencias ocupadas tienden a crecer, ya que la probabilidad de que se ocupe una posicin vaca precedida por i posiciones ocupadas es (i + 1)/m. El resultado es que los tiempos promedios de bsqueda e insercin se incrementan.
11
Sondeo cuadrtico Al igual que en el sondeo lineal, la secuencia de sondeos comienza en h (k). A diferencia del sondeo lineal, el sondeo cuadrtico salta por la tabla de acuerdo a una funcin cuadrtico sobre nmero de sondeo: h(k, i) = (h (k) + c1 i + c2 i2 ) mod M donde c1 , c2 = 0 son constantes que deben ser restringidas a valores que produzcan una permutacin de 0, 1, . . . , m 1 . Si dos claves distintas tienen el mismo valor h (k) entonces tienen la misma secuencia de sondeos. Doble hashing Se usan dos funciones hash auxiliares, h1 y h2 . h1 da el sondeo inicial y h2 da los sondeos restantes. h(k, i) = (h1 (k) + ih2 (k)) mod M Se debe asegurar que h2 (k) siempre sea coprimo de M (sin factores comunes excepto 1) para garantizar que la secuencia es una permutacin de 0, 1, . . . , m 1 . Opciones: m puede ser una potencia de 2 y que h2 siempre produzca un impar mayor que 1 m puede ser primo y que h2 siempre produzca un nmero entre 2 y m 1 3.2.2. Anlisis
Asumiendo dispersin uniforme, que cada clave tiene la misma probabilidad de ser buscada, y que no se borran claves, el nmero de sondeos de una bsqueda fallida es 1/(1 ), al igual que para insertar. El nmero esperado de sondeos en una bsqueda exitosa es como mximo
1 1 ln 1
4. Otras aplicaciones
4.1. Filtros de Bloom
Un ltro de Bloom es una estructura de datos probabilstica que permite aadir elementos a un conjunto y luego determinar si un elemento pertenece al conjunto. Es posible que se produzcan falsos positivos (que la estructura reporte que un elemento est en el conjunto cuando en realidad no lo est), pero los falsos negativos son imposibles. Un ltro de Bloom consiste en una tabla de bits b0 b1 bM 1 , donde M es razonablemente grande. ( ) ( ) ( ) Para cada clave K j a incluir en el ltro, calcular k funciones hash independientes h1 K j , h2 K j , , hk K j que producen valores hash comprendidos entre 0 y M 1 y establecer los correspondientes k bits en(1 (estos k valores no tienen necesariamente que ser distintos entre s). As, bi = 1 si y slo si ) hl K j = i para algn j y l . 12
Para determinar si una clave K est en el conjunto, evaluar primero si bhl (K ) = 1 para todos los l tal que 1 l k; si esto es cierto, entonces es probable que K est en el conjunto (los bits son todos 1 bien porque K haba sido aadida al conjunto o porque los bits se establecieron en 1 debido a las adiciones de otras claves), mientras que si algn bi es cero, se tiene la certeza de que K no est en el conjunto. 4.1.1. Probabilidad de falsos positivos
Suponga que se inserta un elemento. Entonces la probabilidad de que un bit dado se ponga en 1 debido a la aplicacin de una de las funciones hash es 1/M y la probabilidad de que no se ponga en 1 es 1 1/M . Como son k funciones hash, entonces la probabilidad de un bit determinado no ( ) 1 k se ponga en 1 es 1 M . Despus de insertar N elementos, la probabilidad de que dicho bit ( ) ( ) 1 kN 1 kN permanezca en 0 es 1 M y la probabilidad de que sea 1 es el complemento 1 1 M . Supongamos que se desea realizar una bsqueda de un elemento que no ha sido aadido al conjunto. La probabilidad de obtener un falso positivo sera la probabilidad de que los k bits producidos por las funciones hash sean todos 1, la cual sera ( ( ) )k ( )k 1 kN kN /M 1 1 1e M . Evidentemente, la probabilidad de falsos positivos crece a medida que N (el nmero de claves insertadas) crece, y decrece a medida que M (el nmero de bits en el arreglo) decrece. El valor de k que minimiza la probabilidad de falsos positivos es M M ln 2 0,7 N N . Si se asume que se usar el valor ptimo de k, entonces el nmero de bits M a utilizar dado el nmero de elementos a insertar N y la probabilidad deseada de falsos positivos p es M= n ln p (ln 2)2
Cuando se desea aadir un bucket deben reasignarse al nuevo bucket las claves cuyo valor hash sea ms cercano al identicador del nuevo bucket que al identicador del bucket donde estn almacenadas previo a la adicin del nuevo bucket. Cuando se desea eliminar un bucket, cada clave del bucket a eliminar debe ser reasignada bucket con identicador ms cercano entre los buckets restantes, similar a una insercin. El nmero esperado de claves a mover si hay N claves en total y B buckets es N /B tanto para la adicin como para la eliminacin de buckets. El hashing consistente no suele usarse como una estructura de datos en la memoria de un computador, sino en sistemas distribuidos, donde cada bucket es una computadora diferente y mover claves de un nodo a otro es costoso. Un tipo de sistemas distribuidos que utiliza hashing consistente con frecuencia se conoce como tabla hash distribuidas, el cual puede verse como una tabla hash gigante en el que las claves estn esparcidas en varias computadoras. Este tipo de sistema es muy usado por aplicaciones P2P para compartir archivos.
clave1 = (byte 0x5c repetido B veces) XOR clave; clave2 = (byte 0x36 repetido B veces) XOR clave; hmac = hash(clave1 || hash(clave2 || mensaje));
donde se asume que la funcin hash acta de manera iterativa sobre bloques de B bytes. Para ms detalles, ver http://www.ietf.org/rfc/rfc2104.txt .
14
Guido Urdaneta
Estructura de un nodo
Class Nodo { Comparable dato; Nodo izq; Nodo der; } Class Nodo { Object dato; Nodo izq; Nodo der; }
Algoritmo de insercin
public Nodo insertar(Comparable dato, Nodo n) { if (n == null) { n = new Nodo(); n.dato = dato; n.izq = n.der = null; } else { int cmp = dato.compareTo(n.dato); if (cmp > 0) { n.der = insertar(dato, n.der); } else if (cmp < 0) { n.izq = insertar(dato, n.izq); } } return n; }
Algoritmo de insercin
public Nodo insertar(Comparable dato, Nodo n) { if (n == null) { n = new Nodo(); n.dato = dato; n.izq = n.der = null; } else { int cmp = dato.compareTo(n.dato); if (cmp > 0) { n.der = insertar(dato, n.der); } else if (cmp < 0) { n.izq = insertar(dato, n.izq); } } return n; }
Algoritmo de bsqueda
public Nodo buscar(Comparable dato, Nodo n) { if (n == null) { return null; } int cmp = dato.compareTo(n.dato); if (cmp > 0) { return buscar(dato, n.der); } else if (cmp < 0) { return buscar(dato, n.izq); } return n; }
Algoritmo de eliminacin
public Nodo borrar(Comparable dato, Nodo n) { if (n == null) { return null; } int cmp = dato.compareTo(n.dato); if (cmp > 0) { n.der = borrar(dato, n.der); } else if (cmp < 0) { n.izq = borrar(dato, n.izq); } else if (t.izq!=null && t.der != null) { n.dato = buscarMin(n.der).dato; n.der = borrar(n.dato, n.der); } else { n = (n.izq != null) ? n.izq : n.der; } return n; }
Algoritmo de eliminacin
public Nodo buscarMin(Nodo n) { if (n == null) { return null; } else if (n.izq == null) { return n; } return buscarMin(n.izq); }
Algoritmo de eliminacin
public Nodo buscarMin(Nodo n) { if (n == null) { return null; } while (n.izq != null) { n = n.izq; } return n; }
Algoritmos de recorrido
public Nodo enorden(Nodo n){ if (n!= null) { inOrder(n.izq); print(n.dato + ); inOrder(n.der); } } public Nodo preorden(Nodo n){ if (n!= null) { print(n.dato + ); inOrder(n.izq); inOrder(n.der); } } public Nodo postorden(Nodo n){ if (n!= null) { inOrder(n.izq); inOrder(n.der); print(n.dato + ); } }
rboles Rojo-Negros
Guido Urdaneta
Imgenes de Cormen et al.
Introduccin
Variante de rboles binarios de bsqueda Balanceados: se garantiza que la altura es O(lg(n)) Las operaciones toman un tiempo O(lg(n))
Introduccin
Cada nodo un atributo color que puede ser rojo o negro Se usa un centinela NULL para todas las hojas del rbol NULL.color es negro El padre de la raz es NULL
Propiedades
1. Todo nodo es rojo o negro 2. La raz es negra 3. Toda hoja (NULL) es negra 4. Si un nodo es rojo, ambos hijos son negros 5. Para todo nodo del rbol, todas las rutas desde el nodo hasta cualquier hoja tienen el mismo nmero de nodos negros
Ejemplo
Altura
La altura h(x) de un nodo x es el nmero de aristas en un camino mximo desde un nodo hasta una hoja. La altura negra bh(x) de un nodo x es el nmero de nodos negros (incluyendo NULL) en el camino desde x hasta una hoja. Por la propiedad 5, la altura negra est bien definida.
Altura
Todo nodo x con h(x)=h tiene bh(x)>=h/2 Esto se deriva directamente de la propiedad 4, ya que nunca hay dos nodos rojos consecutivos y las hojas (NULL) son negras
Altura
Para una hoja bh(x)=0 y tiene 1>=20-1=0 nodos Por hiptesis, cada hijo de x tiene al menos 2bh(x)-1 -1 nodos. Entonces x tiene al menos 2(2bh(x)-1 -1)-1=2bh(x) -1
Altura
Un rbol rojo-negro con n nodos internos tiene altura <= 2lg(n+1). Sean h y b la altura y la altura negra de la raz. Por los lemas anteriores:
Estructura de un nodo
Class Nodo { Comparable clave; Nodo izq; Nodo der; Nodo padre; boolean color; }
Algoritmo de rotacin
public rotarIzq(Nodo x){ Nodo y = x.der; x.der = y.izq; if (y.izq != NULL) y.izq.padre = x; y.padre = x.padre; if (x.padre==NULL) root = y; else if (x==x.padre.izq) x.padre.izq = y; else x.padre.der = y; y.izq = x; x.padre = y; }
x y x
Tiempo=O(1)
Algoritmo de rotacin
public rotarDer(Nodo y){ x = y.izq; y.izq = x.der; if (x.der != NULL) x.der.padre = y; x.padre = y.padre; if (y.padre==NULL) root = x; else if (y==y.padre.izq) y.padre.izq = x; else y.padre.der = x; x.der = y; y.padre = x; }
y x
x y
Tiempo=O(1)
Algoritmo de insercin
void insertar(Nodo z) { Nodo y = NULL; Nodo x = root; while (x!=NULL) { y = x; if (z.clave < x.clave) x = x.izq; else x = x.der; } } z.padre = y; if (y == NULL) root = z; else if (z.clave<y.clave) y.izq = z; else y.der = z; z.izq = z.der = NULL; z.color = ROJO; corregirInsercion(z)
Algoritmo de insercin
void corregirInsercion(Nodo z){ 1 while (z.padre.color == ROJO){ 2 if (z.padre == z.padre.padre.izq) { 3 y = z.padre.padre.der; 4 if (y.color==ROJO) { 5 z.padre.color = NEGRO; 1 6 y.color = NEGRO; 1 7 z.padre.padre.color = ROJO; 1 8 z = z.padre.padre; 1 } else { 9 if (z == z.padre.der) { 10 z = z.padre; 2 11 rotarIzq(z); 2 } 12 z.padre.color = NEGRO; 3 13 z.padre.padre.color = ROJO; 3 14 rotarDer(z.padre.padre); 3 } } else { 15 //lo mismo, pero intercambiado izq y der } } 16 root.color = NEGRO; }
Caso 1
Casos 2 y 3
Algoritmo de eliminacin
public Nodo eliminar(Nodo z) { Nodo x,y; if (z.izq!=NULL && z.der!=NULL) y=buscarMax(z.izq); //buscarMin(z.der) else y=z; if (y.izq != NULL) x = y.izq; else x = y.der; x.padre = y.padre; if (y.padre == NULL) root = x; else if (y == y.padre.izq) y.padre.izq = x; else y.padre.der = x; if (y != z) z.clave = y.clave; //copiar if (y.color == NEGRO) corregirEliminar(x); return y; }
Algoritmo de eliminacin
void corregirEliminar(Nodo x) { while (x!=root && x.color == NEGRO) { if (x == x.padre.izq) { w = x.padre.der; if (w.color == ROJO) { 1 w.color = NEGRO; 1 x.padre.color = ROJO; 1 rotarIzq(x.padre); 1 w = x.padre.der; } if (w.izq.color == NEGRO && w.der.color == NEGRO) { 2 w.color = ROJO; 2 x = x.padre; } else { if (w.der.color == NEGRO) { 3 w.izq.color = NEGRO; 3 w.color = ROJO; 3 rotarDer(w); 3 w = x.padre.der; } 4 4 4 4 4 w.color = x.padre.color; x.padre.color = NEGRO; w.der.color = NEGRO; rotarIzq(x.padre); x = root; } } else { //lo mismo, pero // intercambiando izq y der } x.color = NEGRO; }
Casos 1 y 2
Casos 3 y 4
rboles Rojo-Negros*
Guido Urdaneta 25 de febrero de 2010
1. Introduccin
Un rbol rojo-negro es un rbol binario de bsqueda en el que cada nodo almacena un bit adicional de informacin llamado color, el cual puede ser rojo o negro. Sobre este atributo de color se aplican restricciones que resultan en un rbol en el que ningn camino de la raz a una hoja es ms de dos veces ms largo que cualquier otro, lo cual signica que el rbol es balanceado. Cada nodo de un rbol rojo negro contiene la siguiente informacin: color, clave, hijo izquierdo, hijo derecho y padre. Si un hijo o el padre de un nodo no existe, el apuntador correspondiente contiene el valor NULO, el cual consideraremos como un nodo cuyo color es negro. En lo sucesivo nos referiremos a los nodos distintos a las hojas (NULO) como nodos internos del rbol y a las hojas y al padre de la raz como nodos externos. Todo rbol rojo-negro satisface las siguientes propiedades: 1. Todo nodo es rojo o negro 2. La raz es negra 3. Toda hoja (NULO) es negra. 4. Si un nodo es rojo, entonces sus dos hijos son negros. 5. Para cada nodo, todas las rutas de un nodo a las hojas (NULOs) contienen el mismo nmero de nodos negros. El nmero de nodos negros en esta ruta se conoce como altura-negra del nodo. Una posible representacin de un rbol rojo negro y sus nodos en Java sera: La Figura 1 presenta un ejemplo de rbol rojo-negro.
* Tomado
Algorithm 1 Estructura de un rbol rojo-negro class ArbolRojoNegro { static class Nodo { Comparable clave; Nodo izq; Nodo der; Nodo padre; boolean color; } private static final boolean NEGRO = false; private static final boolean ROJO = true; private static final Nodo NULO; static { NULO = new Node(); NULO.clave = null; NULO.padre = NULO; NULO.izq = NULO; NULO.der = NULO; NULO.color = NEGRO; } void insertar(Nodo z) {...} void eliminar(Nodo z) {...} Nodo buscar(Comparable clave) }
2. Rotaciones
Las operaciones de insercin y eliminacin de un rbol binario de bsqueda, si se aplican a un rbol rojo negro pueden modicar las propiedades enumeradas en la seccin anterior. Para restaurar estas propiedades es necesario cambiar el color de algunos nodos as como tambin la estructura de los apuntadores. La estructura de los apuntadores se cambia mediante rotacin, la cual es una operacin que preserva las propiedades de un rbol binario de bsqueda. Existen dos tipos de rotaciones: a la izquierda y a la derecha. La Figura 2 muestra las operaciones de rotacin sobre rboles binarios de bsqueda. A continuacin se muestra el cdigo Java de las rutinas de rotacin:
Algorithm 2 Rotaciones a la izquierda y a la derecha public rotarIzq(Nodo x) { Nodo y = x.der; x.der = y.izq; y.izq.padre = x; y.padre = x.padre; if (x.padre==NULO) root = y; else if (x==x.padre.izq) x.padre.izq = y; else x.padre.der = y; y.izq = x; x.padre = y; } public rotarDer(Nodo y) { x = y.izq; y.izq = x.der; x.der.padre = y; x.padre = y.padre; if (y.padre==NULO) root = x; else if (y==y.padre.izq) y.padre.izq = x; else y.padre.der = x; x.der = y; y.padre = x; }
Algorithm 3 Insercin de un nodo en un rbol rojo-negro public void insertar(Nodo z){ Nodo y = NULO; Nodo x = root; while (x!=NULO) { y = x; if (z.clave < x.clave) x = x.izq; else x = x.der; } z.padre = y; if (y == NULO) root = z; else if (z.clave<y.clave) y.izq = z; else y.der = z; z.izq = z.der = NULO; z.color = ROJO; corregirInsercion(z) }
3. Insercin
La insercin de un nodo en un rbol rojo-negro con n elementos puede realizarse en un tiempo O(lg n). Utilizamos una versin modicada de la rutina de insercin en un rbol binario de bsqueda ordinario: Puede observarse que el cdigo es casi igual a la rutina de insercin en un rbol binario de bsqueda normal, salvo por tres diferencias fundamentales: 1. En lugar de usar el null ordinario, utilizamos la constante NULO ya que necesitamos que las hojas tengan color NEGRO. 2. Se establece el color del nodo z a insertar como ROJO. 3. Se invoca a la rutina corregirInsercin(z) para restaurar las propiedades de rbol rojonegro. Ntese que una vez insertado y marcado como rojo el nodo z (justo antes de llamar a corregirInsercion) se presenta la siguiente situacin: Las propiedades 1 y 3 indudablemente se cumplen, ya que los hijos del nuevo nodo rojo insertado son NULO y por lo tanto son negros. 5
La propiedad 5 se cumple debido a que no se ha insertado ningn nodo negro y por lo tanto todas las rutas de la raz a las hojas siguen teniendo el mismo nmero de hijos negros. Las nicas propiedades que podran ser violadas son la 2, que requiere que la raz sea negra y la 4, que dice que un nodo rojo no puede tener nign hijo rojo. Ambas violaciones se deben a que el nuevo nodo es rojo. La propiedad 2 se viola si z es la raz y la 4 si el padre de z es rojo. Para estudiar cmo corregirInsercion permite restaurar las propiedades de rbol rojo-negro examinaremos el cdigo en tres partes: inicializacin, terminacin y mantenimiento.
3.1. Inicializacin
Antes de la primera iteracin, comenzamos con un rbol rojo-negro sin violaciones y aadimos un nodo rojo z. Si hay una violacin a la propiedad 2 (raz negra), entonces la raz roja tiene que ser el nodo recin aadido z, el cual sera el nico nodo interno del rbol. Debido a que tanto el padre como los hijos de z son NULO, el cual es negro, noy hay violacin de la propiedad 4. As, esta sera la nica violacin en el rbol. Si hay una violacin de la propiedad 4, entonces, considerando que los hijos del nodo z son NULO negros y que el rbol no tena violaciones antes de la insercin de z, la violacin tene que ser porque tanto z como z. padre son rojos. Es imposible que haya alguna otra violacin de las propiedades.
3.2. Terminacin
Cuando el ciclo termina, lo hace porque z. padre es negro. As, no hay violacin de la propiedad 4 al terminar el ciclo. La nica propiedad que podra fallar es la propiedad 2, la cual es restaurada en la lnea 27.
3.3. Mantenimiento
Hay seis casos a considerar dentro del ciclo while, pero tres de ellos son simtricos; dependiendo de si z. padre es un hijo izquierdo o un hijo derecho del abuelo de z (z. padre. padre), lo cual se determina en la lnea 2. Estudiaremos nicamente la primera posibilidad, correspondiente a las lneas 3-14 de corregirInsercion (ntese que el cdigo entre las lneas 15 y 26 es simtrico). Ntese que z. padre. padre existe, ya que la condicin del ciclo es que z. padre sea rojo y por lo tanto z. padre no puede ser la raz. El primero de los tres casos a considerar se diferencia de los casos 2 y 3 por el color del to de z (z. padre. padre.der). Si el to (y) es rojo entonces se ejecuta el caso 1. De otro modo se transere el control a los casos 2 y 3. En los tres casos el abuelo de z (z. padre. padre) es negro, puesto que el padre z. padre es rojo y la propiedad 4 slo puede ser violada entre z y z. padre. 6
Algorithm 4 Correcin de las propiedades de un rbolo rojo negro inmediatamente despus de la insecin de un nodo. void corregirInsercion(Nodo z){ 1 while (z.padre.color == RED){ 2 if (z.padre == z.padre.padre.izq) { 3 y = z.padre.padre.der; 4 if (y.color==RED) { 5 z.padre.color = NEGRO; 6 y.color = NEGRO; 7 z.padre.padre.color = ROJO; 8 z = z.padre.padre; } else { 9 if (z == z.padre.der) { 10 z = z.padre; 11 rotarIzq(z); } 12 z.padre.color = NEGRO; 13 z.padre.padre.color = ROJO; 14 rotarDer(z.padre.padre); } } else { 15 y = z.padre.padre.izq; 16 if (y.color == ROJO) { 17 z.padre.color = NEGRO; 18 y.color = NEGRO; 19 z.padre.padre.color = ROJO; 20 z = z.padre.padre; } else { 21 if (z == z.padre.izq) { 22 z = z.padre; 23 rotarDer(z); } 24 z.padre.color = NEGRO; 25 z.padre.padre.color = ROJO; 26 rotarIzq(z.padre.padre); } } } 27 root.color = NEGRO; }
Figura 3: Ejemplo de caso 1 de insercin en rbol rojo negro Caso 1: El to de z (y) es rojo El caso 1 se ejecuta cuando tanto z. padre e y son rojos. Puesto que z. padre. padre es negro, se pueden colorear z. padre e y como negros, corrigiendo as el problema de que z y z. padre sean ambos rojos, y colorear z. padre. padre como rojo, manteniendo de esta manera la propiedad 5. Luego se repite el ciclo con z. padra. padre como el nuevo nodo z. La Figura 3 muestra un ejemplo de insercin en rbol rojo-negro donde se presenta el caso 1 dos veces en el ciclo. Ntese que si al nal del ciclo la raz queda roja, la violacin es corregida en la lnea 27 estableciendo que la raz debe ser negra. Caso 3: El to de z (y) es negro y z es el hijo izquierdo de su padre En el caso 3 tanto z como z. padre son rojos y z. padre. padre es negro. La accin a realizar en este caso consiste en hacer negro a z. padre y rojo a z. padre. padre y realizar una rotacin a la derecha de z. padre. padre. Como z y z. padre originalmente eran rojos, la rotacin realizada no introduce una violacin de la propiedad 5 ya que la altura-negra de los nodos no resulta afectada y como ya no quedan nodos rojos consecutivos el procedimiento est terminado. La Figura 4 muestra un ejemplo de insercin en rbol rojo-negro donde se presenta el caso 3. Caso 2: El to de z (y) es negro y z es el hijo derecho de su padre En el caso 2 z es el hijo derecho de z. padre. En este caso se procede simplemente a realizar una rotacin a la izquierda para transformar el problema en el caso 3. Al terminar la rotacin se considerar z al antiguo z. padre que fue rotado. La Figura 4 muestra un ejemplo de insercin en rbol rojo-negro donde se presenta este caso.
4. Eliminacin
La elminacin de un nodo en un rbol rojo-negro con n elementos puede realizarse en un tiempo O(lg n). Utilizamos una versin modicada de la rutina de eliminacin en un rbol binario de bsqueda ordinario: Puede observarse que el cdigo es casi igual a la rutina de eliminacin en un rbol binario de bsqueda normal, salvo por las siguientes diferencias: 1. En lugar de usar el null ordinario, utilizamos la constante NULO ya que necesitamos que las hojas tengan color NEGRO. 2. Se hace una asignacin incondicional en la lnea 10 (en un rbol binario de bsqueda normal slo se puede hacer esta asignacin si x no es nulo). En caso de que x sea la constante NULO, esta asignacin facilitar la codicacin de corregirEliminar(). 3. Se invoca a la rutina corregirEliminacin(z) para restaurar las propiedades de rbol rojo-negro en caso de que el nodo desaparecido sea negro. Ntese que si el nodo desaparecido es negro, pueden presentarse tres problemas: 1. Si y era la raz y un hijo rojo de y se convierte en la nueva raz, se viola la propiedad 2. 2. Si tanto x como y. padre (que tambin es x. padre) eran rojos, entonces se viola la propiedad 4. 3. La remocin de y hace que cualquier ruta que previamente contena a y ahora tenga un nodo negro menos. Por lo tanto se viola la propiedad 5. Para evitar lidiar con este problema, asumiremos que la negrura del nodo desaparecido y se le transere a su hijo x. El problema ahora es que x no es ni negro ni rojo (violando la propiedad 1), sino que es doble negro o rojo-negro y contribuye 2 o 1, respectivamente, al conteo de nodos negros en las rutas que lo contengan. El atributo color de x seguir siendo ROJO (si es rojo-negro) o NEGRO (si es negro-negro). En otras palabras, el atributo negro extra de un nodo se establece por el hecho de que x apunte a l y no en el atributo color. A continuacin examinaremos cmo el procedimiento corregirEliminar restaura las propiedades de rbol rojo-negro. 9
Algorithm 5 Eliminacin de un nodo en un rbol rojo-negro public Nodo eliminar(Nodo z){ 1 Nodo x,y; 2 if (z.izq!=NULO && z.der!=NULO) 3 y=buscarMax(z.izq);//tambin sirve buscarMin(z.der) 4 else 5 y=z; 6 if (y.izq != NULO) 7 x = y.izq; 8 else 9 x = y.right; 10 x.padre = y.padre; 11 if (y.padre == NULO) 12 root = x; 13 else if (y == y.padre.izq) 14 y.padre.izq = x; 15 else 16 y.padre.der = x; 17 if (y != z) 18 z.clave = y.clave; //copiar datos adicionales si aplica 19 if (y.color == NEGRO) { 20 corregirEliminar(x); 21 return y; }
10
Algorithm 6 Restauracin de propiedades de rbol rojo-negro void corregirEliminar(Nodo x) { Nodo w; while (x!=root && x.color == NEGRO) { if (x == x.padre.left) { w = x.padre.right; if (w.color == ROJO) { 1 w.color = NEGRO; 1 x.padre.color = ROJO; 1 rotarIzq(x.padre); 1 w = x.padre.der; } if (w.izq.color == NEGRO && w.der.color == NEGRO) { 2 w.color = ROJO; 2 x = x.padre; } else { if (w.der.color == NEGRO) { 3 w.izq.color = NEGRO; 3 w.color = ROJO; 3 rotarDer(w); 3 w = x.padre.der; } 4 w.color = x.padre.color; 4 x.padre.color = NEGRO; 4 w.der.color = NEGRO; 4 rotarIzq(x.padre); 4 x = root; } } else { //lo mismo, pero intercambiando izq y der } x.color = NEGRO; }
11
Figura 5: Ejemplo del Caso 4 de corregirEliminar() Dentro del ciclo while, x siempre apunta a un nodo doble-negro distinto a la raz. En la segunda lnea determinamos si x es un hijo izquierdo de su padre x. padre. La situacin cuando x es el hijo derecho es simtrica, as que nos concentraremos slo en el primer caso. Ntese que el propsito del ciclo while es mover el negro extra hasta que: 1. x apunte a un nodo rojo-negro, en cuyo caso se colorea como negro (no doble) fuera del ciclo, en la ltima lnea, 2. x apunta a la raz, en cuyo caso la negrura extra puede ser simplemente desaparecida, o 3. se puedan ejecutar rotaciones y cambios de colores apropiados. En primer lugar mantenemos un apuntador w al hermano de x. Puesto que x es doble negro, w no puede ser NULO; de otro modo el nmero de nodos negros en la ruta de x. padre a la hoja w sera menor que el nmero en la ruta de x. padre a x. En el cdigo se indican cuatro casos. La idea clave en cada caso es observar que el nmero de nodos negros (incluyendo el negro extra de x) desde la raz del subrbol a cada uno de sus subrboles es preservado por la transformacin. As, si la propiedad 5 se cumple antes de la transformacin, tambin se cumple despus. Por ejemplo, para el Caso 1, puede verse en la Figura 8 que el nmero de negros desde la raz del subrbol a los subrboles y es 3, la cual es preservada despus de la transformacin. Lo mismo ocurre con los otros subrboles y en los otros casos. Caso 4: El hermano (w) de x es negro y el hijo derecho de w es rojo En este caso intercambiando algunos colores (haciendo que w tenga el color del padre y que w. padre y w.der sean negros) y haciendo una rotacin a la izquierda podemos eliminar la negrura extra de x sin violar ninguna de las propiedades de rbol rojo negro. Ntese que por la manera en que se asignaron los colores es imposible que se viole la propiedad 4 ya que aunque w podra convertirse en rojo, su hijo derecho se convierte en negro. El hijo izquierdo de w es potencialmente rojo, pero su nuevo padre despus de la rotacin ser w. padre el cual previamente haba se haba coloreado de negro. Finalmente, podra ser que w quede rojo y que quede como raz del rbol, pero fuera del ciclo se corrige esta situacin. La Figura 5 muestra un ejemplo de una eliminacin que produce el Caso 4 y cmo el resultado nal es un rbol rojo-negro vlido. 12
Figura 7: Ejemplo del Caso 2 de corregirEliminar() Caso 3: El hermano (w) de x es negro, el hijo izquierdo de w es rojo y el hijo derecho de w es negro En este caso se pueden intercambiar los colores de w y su hijo izquierdo w.izq y luego ejecutar un rotacin a la derecha de w sin violar las propiedades. Esto no nos permite desaparecer la negrura extra de x, pero nos permite transformar el problema al Caso 4, que resolvimos previamente. La Figura 6 muestra un ejemplo de una eliminacin que produce el Caso 4 y cmo el resultado nal es un rbol rojo-negro vlido. Caso 2: El hermano (w) de x es negro, los dos hijos de w son negros En este caso quitamos negrura tanto de x (moviendo el apuntador) como de w (convirtindolo en negro) y movemos la negrura al padre de ambos (haciendo que x apunte ahora x. padre). Despus de esto, el ciclo se repite, a menos que la negrura extra se haya movido a la raz del rbol, de donde se puede desaparecer sin problemas. La Figura 7 muestra un ejemplo de una eliminacin que produce el Caso 4 y cmo el resultado nal es un rbol rojo-negro vlido. Caso 1: El hermano (w) de x es rojo Como los dos hijos de w tienen que ser negros, se pueden intercambiar los colores de w y x. padre (que es negro, ya que tiene un hijo rojo) y luego realizar una rotacin a la izquierda de x. padre. Despus de la rotacin, el hermano de x ser uno de los antiguos hijos negros de w, convirtiendose as el problema en uno de los casos 2, 3 o 4. 13
Figura 8: Ejemplo del Caso 1 de corregirEliminar() La Figura 8 muestra un ejemplo de una eliminacin que produce el Caso 4 y cmo el resultado nal es un rbol rojo-negro vlido.
14