Está en la página 1de 235

Algoritmos

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;

int euclides(int m,int 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

Modelo Random Access Machine


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

Depende del problema

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

Ejemplo 1 Ordenacin por Insercin


public static void insertionSort(int array[]) { for (int i=1; i<array.length; i++) { int tmp = array[i]; int j; for (j = i; j>0 && tmp<array[j-1]; j--) { array[j] = array[j-1]; } array[j] = tmp; } }

Ejecucin
Original 81 94 11 96 12 35 19 95
Movimientos y comparaciones

Pase 1 81 94 11 96 12 35 19 95 Pase 2 11 81 94 96 12 35 19 95 Pase 3 11 81 94 96 12 35 19 95 Pase 4 11 12 81 94 96 35 19 95 Pase 5 11 12 35 81 94 96 19 95 Pase 6 11 12 19 35 81 94 96 95 Pase 7 11 12 19 35 81 94 95 96

0,1 2,2 0,3 3,4 3,5 4,6 1,7

Ejemplo 1 Ordenacin por Insercin


public static void insertionSort(int a[]) { int i = 1; while (i < a.length) { int tmp = a[i]; int j=i; while (j>0 && tmp<a[j-1]) { a[j] = a[j-1]; j--; } a[j] = tmp; i++; } } c1 c2 c3 c4 c5 c6 c7 c8 c9
n 1 j=1

1 n n-1 n-1
n 1 j=1

t j t j 1
n-1 n-1

Ordenacin por insercin

El tiempo de ejecucin es:

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

En el mejor caso (ya ordenado): n 1 n 1 n 1 t j =1 t j = 1= n 1 t j 1 =0


j=1 j =1 j=1

En el peor caso (orden inverso):


n 1 n 1 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:

f(n)=O(f(n)) cO(f(n))=O(f(n)) O(f(n))+O(f(n))=O(f(n)) O(O(f(n))=O(f(n)) O(f(n))O(g(n))=O(f(n)g(n))=f(n)O(g(n)) f(n)+g(n)=max(O(f(n)),O(g(n)))

Notacin O

O(g(n)) implica una cota asinttica superior. Ejemplo:


1+2+...+n=n(n+1)/2=n2/2+n/2

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

Algunos ejemplos de funciones en 2 O(n ):


n2 n2+n 1000n2+1000n n n/1000 n1,999 n2/lg(n)

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

Algunos ejemplos de funciones en 2 (n ):


n2 n2+n 1000n2+1000n 1000n2-1000n n3 n2,0001 n2lg(n) 2n

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

f n =0 Equivale a decir: lim n g n

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

f n = Equivale a decir: lim n g n

Notaciones o y

Algunos ejemplos de funciones en 2 (n ):


n1,999 =o(n2) n2/lg(n)=o(n2) n2o(n2) n2/1000o(n2) n2,001 =(n2) n2lg(n)=(n2) n2(n2) n2/1000(n2)

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

El tiempo de ejecucin se puede calcular en varios casos:


Peor caso Mejor caso Caso promedio Anlisis probabilstico Anlisis amortizado

Anlisis de Algoritmos

Generalmente, lo ms importante es analizar el peor caso por las siguientes razones:

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

Algunos tiempos de ejecucin tpicos en notacin asinttica


1: constante log(n): logartmico n: lineal nlog(n) n2: cuadrtico n3: cbico nk: polinomial 2n, kn: exponencial

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

Anlisis usando barmetros

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)

Anlisis de mejor caso

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)

Anlisis probabilstico caso promedio

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

Anlisis probabilstico caso promedio

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

Anlisis de caso promedio

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

Anlisis de caso promedio

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

Como i vara de 1 a n-1, el nmero promedio de veces que se ejecuta el barmetro es n1

i =1

i 2 n 1 n 4 = = n2 = n2 2 4

Reglas generales de anlisis

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; }

Algoritmo 3 O(n log(n))


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;

Algoritmo 3 O(n log(n))


/* 9*/ /*10*/ /*11*/ /*12*/ /*13*/ /*14*/ /*15*/ /*16*/ /*17*/ /*18*/ /*19*/ } for( int i = center; i >= left; i-- ) { leftBorderSum += a[ i ]; if( leftBorderSum > maxLeftBorderSum ) maxLeftBorderSum = leftBorderSum; } int maxRightBorderSum = 0, rightBorderSum = 0; for( int i = center + 1; i <= right; i++ ) { rightBorderSum += a[ i ]; if( rightBorderSum > maxRightBorderSum ) maxRightBorderSum = rightBorderSum; } return max3( maxLeftSum, maxRightSum, maxLeftBorderSum + maxRightBorderSum );

Anlisis de peor caso

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

i=0 j=i k=i

1 =
=

( j i + 1)

(n i)(n i + 1) 2 i=0

) 1 n1 ( 2 = i (2n + 1)i + n + n2 2 i=0 1

[ 1 = 2 [

n1 i=0

] 1 (n 1)n(2n 1) (2n + 1)n(n 1) 2 + (n + n )n = 2 6 2 = O(n3 ) =

(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

/* 1*/ /* 2*/ /* /* /* /* /* 3*/ 4*/ 5*/ 6*/ 7*/ }

/* 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.

Algoritmos Fundamentales sobre Arreglos

Guido Urdaneta

Algoritmos fundamentales

Bsqueda secuencial Bsqueda binaria Insertion sort Quicksort Merge sort

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

Peor caso: O(n) Caso promedio: O(n)

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

Algoritmos Divide y Vencers


Dividir el problema en subproblemas que son versiones ms pequeas del problema original Vencer los subproblemas resolvindolos de manera recursiva. Si el subproblema es lo suficientemente pequeo, resolverlo de manera directa Combinar las soluciones de los subproblemas para generar la solucin del problema original

Algoritmos Divide y Vencers


Las relaciones de recurrencias proporcionan una manera natural de caracterizar el tiempo de ejecucin de los algoritmos Divide y Vencers

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

Peor caso: O(n ) Caso promedio: O(nlg(n)) Mejor caso: O(nlg(n))

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

T(n)=2T(n/2)+cn=(n lg(n)) T(1)=c=(1)

Algoritmos Fundamentales sobre Arreglos


Guido Urdaneta Enero, 2010

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 .

3.1 Anlisis de peor caso


El peor caso de quicksort es cuando el pivote siempre resulta ser el menor de los elementos del arreglo y por lo tanto el tiempo T (n) para ordenar los n elementos es la suma del tiempo requerido para particionar que es (1) + (n) = (n) y la podemos aproximar como kn (k > 0), el tiempo requerido para procesar un grupo de cero elementos que es T (0) = (1) y el tiempo requerido para procesar n 1 elementos, que es T (n 1). Entonces nos queda, despus de eliminar los trminos absorbidos por la notacin asinttica, la expresin T (n) = T (n 1) + kn (4) que es una recurrencia lineal no homognea. El polinomio caracterstico es x 1 = 0 y por lo tanto la nica raz es 1 y la solucin homognea es c1 1n = c1 . Para encontrar una solucin particular podemos hacer T (n) = An2 + Bn + C y sustituyendo en 4 nos queda An2 + Bn + C = A(n 1)2 + B(n 1) + C + kn = An2 2 An + A + Bn B + C + kn = An2 + n( B 2 A + k) + ( A B + C ) de donde nos queda A = k /2, B = k/2, C = 0. La solucin nal es la suma de la solucin homognea y la solucin particular y es T (n) = k 2 k n n + c1 = ( n2 ) 2 2 3 (5)

3.2 Anlisis de mejor caso


El mejor caso ocurre cuando el pivote siempre divide el arreglo en dos partes iguales. En este caso T (n) sera el tiempo requerido para realizar la divisin del arreglo, que ya sabemos que es (n) y que aproximamos como kn (k > 0) y el tiempo requerido para ordenar dos subarreglos cuyo tamao no es superior a la mitad del arreglo original (de hecho los tamaos son n/2 y n/2 1). Entonces la relacin de recurrencia la aproximamos como T (n) = 2T (n/2) + kn (6) lo cual es ligeramente ms que T ( n/2 1) + T ( n/2 ) + kn, pero es aceptable para simplicar el anlisis, ya que estamos interesados en una cota superior. Para resolver esta ecuacin de nuevo hacemos n = 2m y nos queda T (2m ) = 2T (2m1 ) + k2m . Haciendo tm = T (2m ) y reordenando los trminos nos queda t m 2 t m 1 = k 2m y tambin, sustituyendo m por m 1 t m 1 2 t m 2 = k 2m 1 (8) (7)

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)

3.3 Anlisis de caso promedio


En general, para ordenar n elementos, quicksort particiona (a un costo kn) el arreglo en dos subarreglos de tamao m y n m 1. Por lo tanto la relacin de recurrencia general es T (n) = T (m) + T (n m 1) + kn. Si asumimos que todas las posibles particiones son igualmente probables, el tiempo promedio de T (m) y T (n m 1) es el mismo y es 1 n 1 T (i ) n i =0 Entonces el tiempo promedio sera 2 n 1 T (n) = T (i ) + kn n i =0 4 (11) (10)

multiplicando 11 por n tenemos


n 1

nT (n) = 2 sustituyendo n por n 1 nos queda

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)

nT (n) (n 1) T (n 1) = 2T (n 1) + 2kn k Reagrupando nos queda T (n) =

(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:

mergesort(A,p,r) { if (p<r) { q=(p+r)/2; mergesort(A,p,q); mergesort(A,q+1,r); merge(A,p,q,r); } }


El tiempo de ejecucin es T (n) = 2T (n/2) + kn, donde el T (n/2) es el tiempo que toma ordenar un subarreglo de la mitad del tamao y kn = (n) es el tiempo que toma realizar la fusin de los dos subarreglos. Esta relacin de recurrencia es idntica a la correspondiente al mejor caso de quicksort y sabemos que el resultado es (n log(n)). 6

5 Arreglos de tamao variable (listas basadas en arreglos)


Con frecuencia, se desea tener una lista relativamente pequea de elementos, pero de tamao variable y en muchos casos los arreglos son la opcin ms apropiada. Para minimizar el costo de la asignacin de memoria, el arreglo debe crecer en pedazos ms o menos grandes y toda la informacin sobre el arreglo se debe manetener organizada en un solo lugar. Los arreglos son la manera ms simple de agrupar datos. Proporcionan las siguientes operaciones en tiempo O(1): agregar al nal del arreglo, borrar al nal del arreglo y obtener el i-simo elemento. Las siguientes operaciones tienen un tiempo O(n): aadir en posicin i, encontrar elemento e, borrar posicin i, borrar elemento e.

Listas
Guido Urdaneta
Instituto de C alculo Aplicado - LUZ

8 de febrero de 2010

Listas

1 / 38

Table of Contents

ADT Lista

Arreglos de tama no variable

Listas Enlazadas

ADT Pila

Listas

2 / 38

Table of Contents

ADT Lista

Arreglos de tama no variable

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

Arreglos de tama no variable

Listas Enlazadas

ADT Pila

Listas

Arreglos de tama no variable

5 / 38

Arreglos de tama no variable

La implementaci on m as com un de listas es con arreglos:


1 2 3 4 class ListaArr { int [] arr ; int tam ; }

Listas

Arreglos de tama no variable

6 / 38

Corregir tama no para a nadir

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 ]; }

Tiempo de ejecuci on: O (n) Ejercicio: implementar corregir para borrar

Listas

Arreglos de tama no variable

7 / 38

A nadir al nal

1 2

co rr eg i r_ an ad i r (); arr [ tam ++] = valor ;

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

Arreglos de tama no variable

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

Arreglos de tama no variable

9 / 38

Buscar posici on x

return arr [ x ];

Tiempo de ejecuci on: O (1)

Listas

Arreglos de tama no variable

10 / 38

Buscar valor

B usqueda lineal en un arreglo Tiempo de ejecuci on: O (1)

Listas

Arreglos de tama no variable

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

Arreglos de tama no variable

12 / 38

Borrar elemento en posici on x

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 ;

Tiempo de ejecuci on peor caso: O (n) Tiempo de ejecuci on promedio: O (n)

Listas

Arreglos de tama no variable

13 / 38

Borrar elemento dado valor

1 2 3 4 5 6

posicion = bu sq ue d a_ li ne a l ( valor ); if ( posicion >= 0) { bo rr a r_ po si c io n ( posicion ); return 0; } return -1;

Tiempo de ejecuci on peor caso: O (n) Tiempo de ejecuci on promedio: O (n)

Listas

Arreglos de tama no variable

14 / 38

Reemplazar elemento en posici on x

arr [ x ] = valor ;

Tiempo de ejecuci on: O (1)

Listas

Arreglos de tama no variable

15 / 38

Recorrido

1 2 3

for ( int i =0; i < tam ; i ++) { func ( arr [ i ]); }

Tiempo de ejecuci on: O (n)

Listas

Arreglos de tama no variable

16 / 38

Table of Contents

ADT Lista

Arreglos de tama no variable

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 ;

Tiempo de ejecuci on: O (1)

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 ;

Tiempo de ejecuci on: O (n)

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

cab = cab . siguiente ;

Tiempo de ejecuci on: O (1)

Listas

Listas Enlazadas

23 / 38

Borrar elemento en posici on x

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

Borrar elemento dado valor


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 if ( cab == null ) return false ; if ( cab . dato == valor ) { cab = cab . siguiente ; return true ; } else { prev = cab ; tmp = cab . siguiente ; while ( tmp ) { if ( tmp . dato == valor ) { prev . siguiente = tmp . siguiente ; return true ; } prev = tmp ; tmp = tmp . siguiente ; } } return false ;

Tiempo de ejecuci on peor caso: O (n) Tiempo de ejecuci on promedio: O (n)


Listas Listas Enlazadas 25 / 38

Reemplazar elemento en posici on x

1 2

Nodo nodo = b us ca r_ p os ic i on ( x ); if ( nodo ) nodo . dato = valor ;

Tiempo de ejecuci on: O (1)

Listas

Listas Enlazadas

26 / 38

Recorrido

1 2 3 4 5

tmp = cab ; while ( tmp ) { func ( tmp ); tmp = tmp . siguiente ; }

Tiempo de ejecuci on: O (n)

Listas

Listas Enlazadas

27 / 38

Table of Contents

ADT Lista

Arreglos de tama no variable

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

Lista doblemente enlazada

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

Lista doblemente enlazada: meter principio

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 ; }

Tiempo de ejecuci on: O (1) Ejercicio: Meter al nal

Listas

ADT Pila

37 / 38

Lista doblemente enlazada: borrar nal

1 2 3 4 5 6 7

tmp = fin . dato ; fin = fin . prev ; if ( fin == null ) cab = null ; else fin . sig = null ; return tmp ;

Tiempo de ejecuci on: O (1) Ejercicio: borrar al principio

Listas

ADT Pila

38 / 38

Heaps
Guido Urdaneta
Instituto de C alculo Aplicado - LUZ

Febrero 2010

Heaps

1 / 18

Table of Contents

ADT Cola de Prioridad

Heaps Binarios Implementaci on

Heapsort

Heaps

2 / 18

Table of Contents

ADT Cola de Prioridad

Heaps Binarios Implementaci on

Heapsort

Heaps

ADT Cola de Prioridad

3 / 18

ADT Cola de Prioridad

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

ADT Cola de Prioridad

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

ADT Cola de Prioridad

5 / 18

Table of Contents

ADT Cola de Prioridad

Heaps Binarios Implementaci on

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

Mantenimiento de propiedad de orden

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 ; }

Tiempo de ejecuci on: O (log n)

Heaps

Heaps Binarios

11 / 18

Heap despu es de sacar m aximo

Heaps

Heaps Binarios

12 / 18

Ver m aximo

1 2 3 4

if ( tam <= 0) throw new Exception (); return arr [0];

Tiempo de ejecuci on: O (1)

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 ;

Tiempo de ejecuci on: O (log n)

Heaps

Heaps Binarios

14 / 18

Table of Contents

ADT Cola de Prioridad

Heaps Binarios Implementaci on

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

Algoritmo para convertir arreglo en heap

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 ); }

Tiempo de ejecuci on: O (n)

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 ); }

Tiempo de ejecuci on: O (n log n)

Heaps

Heapsort

18 / 18

Heaps*
Guido Urdaneta 8 de febrero de 2010

1.

ADT Cola de prioridad

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

de Introduction to Algorithms de T. Cormen et al.

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.

Propiedades de los Heaps

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

Esto se demuestra fcilmente con induccin. Para h = 1 tenemos 21 1 =

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.

Mantenimiento de propiedad de orden

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

Algorithm 3 Algoritmo de insercin en heaps void insertar(int x) {

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

entonces T 3 =T 3 + k. Si hacemos px = T 2 2 la relacin de recurrencia nos queda px = px1 + k.

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

Algorithm 4 Algoritmo de insercin en heaps int sacar_min() {

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

Algorithm 5 Algoritmo para convertir un arreglo en heap convertir_en_heap(int[] arr) {

for (int i=arr.length/2-1; i>=0; i--) mantener_heap(arr,i,arr.length); }


Algorithm 6 Algoritmo heapsort void heapsort(int[] a) {

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

Funciones hash de arreglos

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

Funci on hash de Knott

1 2 3 4

hash =0; for i =0 to n -1 { hash = hash + rot ( s [ i ]); }

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

Funci on hash de Carter y Wegman


Se propone usar n funciones hash independientes: h(s ) = (h0 (s0 ) + h1 (s1 ) + + hn1 (sn1 )) modM Puede usarse XOR en lugar de suma Las funciones hash hi pueden implementarse mediante arreglos precomputados Est a reportada originalmente con el m etodo de divisi on, pero deber a funcionar con el de multiplicaci on tambi en
1 2 3 4 5 hash =0 for i =0 to n -1 { hash = hash + h [ i ]( s [ i ]); // o h [ i ][ s [ i ]] } return hash % M ;

Funciones Hash

Introducci on

9 / 31

Java hashCode()

La clase String de Java propone h(s ) = s0 31n1 + s1 31n2 + sn2 31 + sn1


1 2 3 4 5 6 hash =0 for i =0 to n -1 { hash = hash * 31; hash = hash + s [ i ]; } return hash ;

Funciones Hash

Introducci on

10 / 31

Funci on hash de Fowler-Noll-Vo (FNV)

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

Cyclic Redundancy Check (CRC)

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

Funciones hash criptogr acamente seguras

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

ADT Diccionario y Conjunto

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

Para buscar una clave K


1 2

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

Para determinar si la clave K est a en el conjunto:


1 2

Funciones Hash

Otras Aplicaciones

26 / 31

Filtros de Bloom

La probabilidad de un falso positivo es: 1 1


k 1 kN M

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

C odigos de autenticaci on de mensajes (MAC)

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

C odigos de autenticaci on de mensajes (MAC)

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.

1.1. Propiedades deseadas


1.1.1. Bajo costo

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

principalmente de TAOCP Volumen 3 (Knuth) y Wikipedia

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.

2. Algoritmos para implementar funciones hash


2.1. Funcin hash trivial
Si la clave es lo sucientemente pequea, es posible que sta pueda usarse como valor hash sin hacer modicaciones.

2.2. Hash de una palabra


Para hacer una funcin hash que mapee de datos cuyo tamao es menor o igual al de la palabra de la computadora (tpicamente n dgitos binarios) al conjunto de los enteros comprendidos entre 0 y M 1 (M sera el nmero de entradas en una tabla hash), existen dos mtodos bsicos: 1. El mtodo de divisin: h(K ) = K modM , donde K es la clave. En este caso se recomienda que M no sea potencia de 2 (ya que el resultado sera tomar los bits menos signicativos de la clave ignorando los ms signicativos) y que tampoco divida a 2k a, donde k y a son enteros pequeos. En la prctica, usar nmeros primos que no dividan a 2k a (k y a pequeos) suele dar buenos resultados. (( A ) ) 2. El mtodo de multiplicacin: h(K ) = M w K mod1 , donde w es 2n y A se recomienda que sea un entero relativamente ( ) primo a w. Knuth sugiere un buen valor para A/w es la proporcin urea 1 = 5 1 /2 0, 6180339887, ya que en este caso valores sucesivos tendern a dividir intervalos en del segmento [0, 1) segn la proporcin urea. La principal ventaja de este mtodo con respecto al de divisin es que el valor M no es muy importante. Nota: la operacin xmod1 cuando x es un nmero con parte fraccionaria, produce como resultado la parte fraccionara. Por ejemplo 5, 43mod 1 = 0, 43. La notacin x se reere a la parte entera de x. Por ejemplo, 7, 93 = 7. Si A/w 0, 6180339887 y M = 1000 entonces h(3) = 1000 ((0, 6180339887 3) mod1) = 1000 (1, 8541019661mod1) = 1000 0, 8541019661 = 854, 1019661 = 854 del mismo modo, h(1) = 618, h(2) = 236, h(4) = 472, h(5) = 90. 2

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.

hash=0; for i=0 to n-1 { hash = hash + rot(s[i]); } return hash;


2.3.2. Carter y Wegman

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.

hash=0 for i=0 to n-1 {


3

hash = hash+h[i](s[i]); //o h[i][s[i]] } return hash % M;


Tambin puede usarse XOR como alternativa a la suma. 2.3.3. Java hashCode()

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

offset_basis FNV_prime 2166136261 16777619 14695981039346656037 1099511628211

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 .

2.4. Sumas de chequeo


Las sumas de chequeo (checksums) son funciones hash que tienen como propsito principal detectar cambios accidentales en una secuencia de datos que ha sido transmitida o almacenada. La idea es que se transmite el dato junto con el valor hash y el receptor calcula el valor hash de la secuencia recibida y la compara con el valor hash recibido. Si hay una discrepancia, signica que hubo un error en la transmisin y se pueden tomar acciones como pedir una retransmisin. Ntese que los algoritmos de sumas de chequeo no sirven para detectar modicaciones intencionales, es decir, modicaciones introducidas por un atacante que conoce el valor hash deseado, pero que es capaz de enviar un mensaje falso que se se mapee al mismo valor hash. 2.4.1. Fletcher-32 y Adler-32

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=sumB=0; for (i=0 to n-1) {


5

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

2.5. Funciones hash criptogrcamente seguras


Las funciones hash criptogrcas tienen las siguientes caractersticas adicionales a las funciones hash en general: 1. Resistencia preimagen: dado un valor hash h, debe ser difcil1 encontrar un mensaje n tal que hash(m) = h. 2. Segunda resistencia preimagen: Dado un mensaje m1 debe ser difcil encontrar un mensaje m2 tal que hash(m1 ) = hash(m2 ). 3. Resistencia a colisiones: Debe ser difcil encontrar dos mensajes m1 y m2 tales que hash(m1 ) = hash(m2 ) 4. Una modicacin minscula (e.g., un bit) en el mensaje original ocasiona cambios en el valor hash comparables a un cambio de cualquier otro tipo. En particular, cualquier cambio en el mensaje original idealmente hace que cada uno de los bits en el valor hash cambie con probabilidad 0.5. Las funciones hash seguras tienen muchas aplicaciones en aplicaciones relacionadas con seguridad de datos (e.g., rmas digitales y cdigos de autenticacin de mensajes). Tambin pueden ser usadas como funciones hash de propsito general o como sumas de chequeo. Algunos ejemplos bien conocidos de algoritmos de funcin hash criptogrcamente seguras son SHA-1, SHA-256, MD5 y Tiger, entre otros.

2.6. Hash perfecto


Se dice que una funcin hash es perfecta cuando es inyectiva, es decir, que cada dato de entrada se mapea a un valor hash diferente. Desafortunadamente las funciones hash perfectas slo son tiles cuando las entradas estn preestablecidas y se conocen de antemano, como por ejemplo, mapear los nombres de los meses a los enteros del 1 al 12. Si la funcin hash perfecta mapea los n posibles datos de entrada a un rango consistente en n enteros consecutivos, se dice que es una funcin hash perfecta y mnima.

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

en este contexto signica "ms all de la capacidad de cualquier adversario"

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.

3.1. Resolucin de colisiones por encadenamiento


La resolucin de colisiones por encadenamiento consiste en utilizar una lista enlazada en cada posicin del arreglo, de manera tal que cuando varias claves se deben almacenar en la misma posicin simplemente se almacenan todas en la lista enlazada de dicha posicin. La insercin y la eliminacin pueden hacerse en tiempo O(1) si la insercin se hace siempre en un extremo de la lista de la lista y para la eliminacin se usa un apuntador al nodo de la lista. La insercin y la eliminacin seran en tiempo proporcional a la longitud de la lista si se utiliza una comparacin de los valores de las entradas para evitar entradas repetidas. La bsqueda siempre es proporcional al tamao de la lista y debe ser lineal. 3.1.1. Anlisis

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.

3.2. Direccionamiento abierto


El direccionamiento abierto es una alternativa al encadenamiento para evitar colisiones. La idea es almacenar directamente en el arreglo las claves. Cada posicin contiene una clave o NULO. Para buscar una clave k el procedimiento es: 1. Calcular h(k) y examinar la posicin h(k). Examinar una posicin se conoce como sondeo (probe). 2. Si la posicin h(k) contiene la clave k, la bsqueda es exitosa. Si contiene NULO, la bsqueda fall.

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

4.2. Hash consistente


Normalmente, si se desea modicar el tamao de una tabla hash (por ejemplo, si se han aadido muchas claves y se desea disminuir el factor de carga, o viceversa) la nica alternativa suele ser crear una tabla nueva con el nuevo tamao deseado y reinsertar todos los elementos en la nueva tabla, lo cual es una operacin costosa. Una alternativa es asignar a cada entrada de la tabla (bucket) un identicador aleatorio entre 0 y M 1 (en el mismo rango de la funcin hash) y asignar cada clave K al bucket con identicador ms cercano a la clave K . 13

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.

4.3. Cdigos de autenticacin de mensajes


Un cdigo de autenticacin de mensajes (MAC) permite autenticar un mensaje. Cuando un usuario A enva un mensaje m a B, A anexa a m un cdigo c que se calcula a partir de m y de una clave secreta compartida con B. Cuando B recibe el mensaje mr , utiliza la clave secreta compartida para calcular el cdigo de autenticacin para el mensaje mr . Si mr ha sido adulterado (posiblemente por un adversario) entonces el cdigo de autenticacin ser diferente al recibido. Esto ocurre porque si el adversario no conoce la clave secreta, ser incapaz de sustituir el cdigo de autenticacin c por el correspondiente al mensaje adulterado. Una manera de implementar MACs es utilizando funciones hash criptogrcas. En este caso se conoce como HMAC. Posibles maneras de calcular un HMAC son h(clave||m), h(m||clave) y h(clave||m||clave) donde || signica concatenacin y h es una funcin hash criptogrcamente segura. Se ha demostrado que estos enfoques puede potencialmente ser susceptibles a ataques, entonces lo que se recomienda actualmente es derivar dos claves diferentes a partir de la clave original y utilizar h(clave1 ||h(clave2 ||m)). Una manera comn de hacerlo es:

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

rboles Binarios de Bsqueda

Guido Urdaneta

Estructura de un nodo
Class Nodo { Comparable dato; Nodo izq; Nodo der; } Class Nodo { Object dato; Nodo izq; Nodo der; }

Estructura del rbol


Class ArbolBinarioBusqueda { private Nodo raiz; }

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

El subrbol cuya raz es x tiene al menos 2bh(x) -1 nodos. Por induccin,

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:

n>=2n-1>=2h/2 -1 sumando 1 y aplicando logaritmos nos queda n <=2lg(n+1)

Estructura de un nodo
Class Nodo { Comparable clave; Nodo izq; Nodo der; Nodo padre; boolean color; }

Estructura del rbol


Class ArbolRojoNegro { private Nodo raiz; private static final boolean NEGRO = false; private static final boolean ROJO = true; private static final Nodo NULL; static { NULL = new Node(); NULL.clave = null; NULL.padre = NULL; NULL.izq = NULL; NULL.der = NULL; NULL.color = NEGRO; } static class Node {...} }

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

fundamentalmente de "Introduction to Algorithms" de Cormen et al., incluidas todas las guras.

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

Figura 1: Ejemplo de rbol rojo-negro

Figura 2: Rotacin de rboles binarios de bsqueda a la izquierda y a la derecha

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.

Figura 4: Ejemplo de casos 2 y 3 de insercin en rbol rojo negro

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 6: Ejemplo del Caso 3 de corregirEliminar()

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

También podría gustarte