Conceptos Básicos de Algorítmica

248
 Conceptos Básicos de Algorítmica Rosa M a Jiménez Conrado Martínez U. Politècnica Catalunya 1 de octubre de 2013

description

Conceptos Básicos de Algorítmica

Transcript of Conceptos Básicos de Algorítmica

  • Conceptos Bsicos de Algortmica

    Rosa Ma JimnezConrado Martnez

    U. Politcnica Catalunya

    1 de octubre de 2013

  • Queda prohibida la reproduccin parcial o la modificacin, porcualesquiera medios, de estas transparencias sin el consentimientoexpreso de los autores. Puede accederse a la copia principal de estedocumento electrnico en la web, enlazarse, realizarse cuntascopias se desee y difundirlas pblicamente a travs de la web u otrosmedios, siempre y cuando no se altere su contenido en absoluto.

    El consentimiento para la reproduccin, modificacin o uso total oparcial de estas transparencias con fines exclusivamente docentesest automticamente garantizado, tanto en la Univ. Politcnica deCatalunya (UPC) como en cualquier otra institucin de enseanza,superior o no, de cualquier pas y siempre y cuando: a) se respete eldebido crdito a los autores; b) los derechos de copia, reproduccin,modificacin y uso total o parcial aplicables a los materialesderivados sean los mismos aqu expresados. Por favor, envie une-mail a alg -at- lsi.upc.edu si est interesado en lareproduccin, modificacin o uso total o parcial de estastransparencias con cualquier fin, includos los fines docentes.

  • 1 Anlisis de Algoritmos

    2 Divide y vencers

    3 rboles binarios de bsqueda

    4 rboles balanceados (AVLs)

    5 Tablas de Hash

    6 Colas de prioridad

    7 Grafos y Recorridos

  • Eficiencia de un algoritmo = consumo de recursos decmputo: tiempo de ejecucin y espacio de memoriaAnlisis de algoritmos Propiedades sobre la eficienciade algoritmos

    Comparar soluciones algortmicas alternativasPredecir los recursos que usar un algoritmo o EDMejorar los algoritmos o EDs existentes y guiar el diseode nuevos algoritmos

  • En general, dado un algoritmo A cuyo conjunto de entradas esA su eficiencia o coste (en tiempo, en espacio, en nmero deoperaciones de E/S, etc.) es una funcin T de A en N (o Q o R,segn el caso):

    T : A N T ()

    Ahora bien, caracterizar la funcin T puede ser muycomplicado y adems proporciona informacin inmanejable,difcilmente utilizable en la prctica.

  • Sea An el conjunto de entradas de tamao n y Tn : An N lafuncin T restringida a An.

    Coste en caso mejor:

    Tmejor(n) = mn{Tn() | An}.

    Coste en caso peor:

    Tpeor(n) = max{Tn() | An}.

    Coste promedio:

    Tavg(n) =An

    Pr()Tn()

    =k0

    k Pr(Tn = k).

  • 1 Para todo n 0 y para cualquier AnTmejor(n) Tn() Tpeor(n).

    2 Para todo n 0

    Tmejor(n) Tavg(n) Tpeor(n).

  • Estudiaremos generalmente slo el coste en caso peor:1 Proporciona garantas sobre la eficiencia del algoritmo, el

    coste nunca exceder el coste en caso peor2 Es ms fcil de calcular que el coste promedio

  • Una caracterstica esencial del coste (en caso peor, en casomejor, promedio) es su tasa de crecimiento

    Ejemplo1 Funciones lineales: f(n) = a n+ b f(2n) 2 f(n)2 Funciones cuadrticas:q(n) = a n2 + b n+ c q(2n) 4 q(n)

    Se dice que las funciones lineales y las cuadrticas tienentasas de crecimiento distintas. Tambin se dice que son derdenes de magnitud distintos.

  • log2 n n n log2 n n2 n3 2n

    1 2 2 4 8 42 4 8 16 64 163 8 24 64 512 2564 16 64 256 4096 2621445 32 160 1024 32768 6,87 10106 64 384 4096 262144 4,72 1021

    ` N L C Q E

    `+ 1 2N 2(L+N) 4C 8Q E2

  • Los factores constantes y los trminos de orden inferior sonirrelevantes desde el punto de vista de la tasa de crecimiento:p.e. 30n2 +

    n tiene la misma tasa de creciemiento que

    2n2 + 10n notacin asintticaDefinicinDada una funcin f : R+ R+ la clase O(f) (O-grande de f )es

    O(f)={g:R+R+ | n0 c nn0:g(n)cf(n)}

    En palabras, una funcin g est en O(f) si existe una constantec tal que g < c f para toda n a partir de un cierto punto (n0).

  • Aunque O(f) es un conjunto de funciones por tradicin seescribe a veces g = O(f) en vez de g O(f). Sin embargo,O(f) = g no tiene sentido.Propiedades bsicas de la notacin O:

    1 Si lmn g(n)/f(n) < + entonces g = O(f)2 Es reflexiva: para toda funcin f : R+ R+, f = O(f)3 Es transitiva: si f = O(g) y g = O(h) entonces f = O(h)4 Para toda constante c > 0, O(f) = O(c f)

  • Los factores constantes no son relevantes en la notacinasinttica y los omitiremos sistemticamente: p.e. hablaremosde O(n) y no de O(4 n); no expresaremos la base delogaritmos (O(log n)), ya que podemos pasar de una base aotra multiplicando por el factor apropiado:

    logc x =logb x

    logb c

  • Otras notaciones asintticas son (omega) y (zita). Laprimera define un conjunto de funciones acotada inferiormentepor una dada:

    (f)={g:R+R+ | n0 c>0 nn0:g(n)cf(n)}

    La notacin es reflexiva y transitiva; si lmn g(n)/f(n) > 0entonces g = (f). Por otra parte, si f = O(g) entoncesg = (f) y viceversa.

  • Se dice que O(f) es la clase de las funciones que crecen noms rpido que f . Anlogamente, (f) es la clase de lasfunciones que crecen no ms despacio que f .Finalmente,

    (f) = (f) O(f)es la clase de la funciones con la misma tasa de crecimientoque f .La notacin es reflexiva y transitiva, como las otras. Esadems simtrica: f = (g) si y slo si g = (f). Silmn g(n)/f(n) = c donde 0 < c

  • Propiedades adicionales de las notaciones asintticas (lasinclusiones son estrictas):

    1 Para cualesquiera constantes < , si f es una funcincreciente entonces O(f) O(f).

    2 Para cualesquiera constantes a y b > 0, si f es creciente,O((log f)a) O(f b).

    3 Para cualquier constante c > 1, si f es creciente,O(f) O(cf ).

  • Los operadores convencionales (sumas, restas, divisiones,etc.) sobre clases de funciones definidas mediante unanotacin asinttica se extienden de la siguiente manera:

    AB = {h | f A g B : h = f g},

    donde A y B son conjuntos de funciones. Expresiones de laforma f A donde f es una funcin se entender como{f} A.Este convenio nos permite escribir de manera cmodaexpresiones como n+O(log n), nO(1), (1) +O(1/n).

  • Regla de las sumas:

    (f) + (g) = (f + g) = (max{f, g}).Regla de los productos:

    (f) (g) = (f g).Reglas similares se cumplen para las notaciones O y .

  • Anlisis de algoritmos iterativos

    1 El coste de una operacin elemental es (1).2 Si el coste de un fragmento S1 es f y el de S2 es g

    entonces el coste de S1;S2 es f + g.3 Si el coste de S1 es f , el de S2 es g y el coste de evaluar B

    es h entonces el coste en caso peor deif B then S1elseS2end if

    es max{f + h, g + h}.

  • 4 Si el coste de S durante la i-sima iteracin es fi, el costede evaluar B es hi y el nmero de iteraciones es gentonces el coste T dewhile B do

    Send while

    es

    T (n) =

    i=g(n)i=1

    fi(n) + hi(n).

    Si f = max{fi + hi} entonces T = O(f g).

  • Anlisis de algoritmos recursivos

    El coste (en caso peor, medio, . . . ) de un algoritmo recursivoT (n) satisface, dada la naturaleza del algoritmo, una ecuacinrecurrente: esto es, T (n) depender del valor de T paratamaos menores. Frecuentemente, la recurrencia adopta unade las dos siguientes formas:

    T (n) = a T (n c) + g(n),T (n) = a T (n/b) + g(n).

    La primera corresponde a algoritmos que tiene una parte norecursiva con coste g(n) y hacen a llamadas recursivas consubproblemas de tamao n c, donde c es una constante.La segunda corresponde a algoritmos que tienen una parte norecursiva con coste g(n) y hacen a llamadas recursivas consubproblemas de tamao (aproximadamente) n/b, donde b > 1.

  • TeoremaSea T (n) el coste (en caso peor, en caso medio, ...) de unalgoritmo recursivo que satisface la recurrencia

    T (n) =

    {f(n) si 0 n < n0a T (n c) + g(n) si n n0,

    donde n0 es una constante, c 1, f(n) es una funcinarbitraria y g(n) = (nk) para una cierta constante k 0.Entonces

    T (n) =

    (nk) si a < 1(nk+1) si a = 1(an/c) si a > 1.

  • TeoremaSea T (n) el coste (en caso peor, en caso medio, . . . ) de unalgoritmo recursivo que satisface la recurrencia

    T (n) =

    {f(n) si 0 n < n0a T (n/b) + g(n) si n n0,

    donde n0 es una constante, b > 1, f(n) es una funcinarbitraria y g(n) = (nk) para una cierta constante k 0.Sea = logb a. Entonces

    T (n) =

    (nk) si < k(nk log n) si = k(n) si > k.

  • 1 Anlisis de Algoritmos

    2 Divide y vencers

    3 rboles binarios de bsqueda

    4 rboles balanceados (AVLs)

    5 Tablas de Hash

    6 Colas de prioridad

    7 Grafos y Recorridos

  • Introduccin

    El principio bsico de divide y vencers (en ingls, divide andconquer; en cataln, dividir per conquerir) es muy simple:

    1 Si el ejemplar (instancia) del problema a resolver essuficientemente simple, se encuentra la solucin mediantealgn mtodo directo.

    2 En caso contrario, se divide o fragmenta el ejemplar dadoen subejemplares x1, . . . , xk y se resuelve, independientey recursivamente, el problema para cada uno de ellos.

    3 Las soluciones obtenidas y1, . . . , yk se combinan paraobtener la solucin al ejemplar original.

  • El esquema de divide y vencers expresado enpseudocdigo tiene el siguiente aspecto:

    procedure DIVIDE_Y_VENCERAS(x)if x es simple then

    return SOLUCION_DIRECTA(x)elsex1, x2, . . . , xk := DIVIDE(x)for :=1 to k do

    yi := DIVIDE_Y_VENCERAS(xi)end forreturn COMBINA(y1, y2, . . . , yk)

    end ifend procedure

    Ocasionalmente, si k = 1, se habla del esquema dereduccin.

  • El esquema de divide y vencers se caracterizaadicionalmente por las siguientes propiedades:

    No se resuelve ms de una vez un mismo subproblemaEl tamao de los subejemplares es, en promedio, unafraccin del tamao del ejemplar original, es decir, si x esde tamao n, el tamao esperado de un subejemplarcualquiera xi es n/ci donde ci > 1. Con frecuencia, estacondicin se cumplir siempre, no slo en promedio.

  • Coste de los algoritmos divide y vencers

    El anlisis de un algoritmo divide y vencers requiere, al igualque ocurre con otros algoritmos recursivos, la resolucin derecurrencias. En su caso ms simple las recurrencias son de laforma

    T (n) =

    {f(n) + a T (n/b) si n > n0,b(n) si n n0.

    Este tipo de recurrencia corresponde al coste T (n) de unalgoritmo donde conocemos la solucin directa siempre que n,el tamao de x, sea menor o igual a n0, y en caso contrario, sedescompone el ejemplar en subejemplares cuyo tamao esaproximadamente n/b, haciendose a llamadas recursivas,siendo f(n) el coste conjunto de las funciones de dividir y decombinar. Si bien es habitual que a = b, existen muchassituaciones en las que esto no sucede.

  • TeoremaSi g(n) = (nk) entonces la solucin de la recurrencia

    T (n) =

    {g(n) + a T (n/b) si n > n0,f(n) si n n0,

    donde a 1 y b > 1 son constantes, satisface

    T (n) =

    (g(n)) si < k,(g(n) log n) si = k(n) si > k,

    siendo = logb a.

  • Demostraci12n: Supondremos que n = n0 bj . Aplicando larecurrencia repetidamente,

    T (n) = gf(n) + a T (n/b)= g(n) + a f(n/b) + a2 T (n/b2)= = g(n) + a g(n/b) + + aj1 g(n/bj1)

    + aj T (n/bj)=

    0i

  • Puesto que f(n0) es una constante y tenemos que el segundotrmino es (n) ya que

    aj = alogb(n/n0) = alogb n a logb n0= (blogb alogb n)

    = (nlogb a) = (n).

    Ahora tenemos tres casos diferentes a considerar: segn quea/bk sea menor, igual o mayor que 1. Alternativamente, ya que = logb a, segn que sea mayor, igual o menor que k. Sik > (equivalentemente, a/bk < 1) entonces la suma queaparece en el primer trmino es una serie geomtrica acotadapor una constante, es decir, el primer trmino es (nk). Comoasumimos que k > , es el primer trmino el que domina yT (n) = (nk) = (g(n)).

  • Si = k, tendremos que a/bk = 1 y la suma vale j; ya quej = logb(n/n0) = (log n), conclumos queT (n) = (nk log n) = (g(n) log n). Finalmente, si k < entonces a/bk > 1 y el valor de la suma es

    0i

  • Bsqueda dicotmica

    Problema: Dado un vector A[1..n] de n elementos, en ordencreciente, y un elemento x, determinar el valor i, 0 i n, talque A[i] x < A[i+ 1] (convendremos que A[0] = yA[n+ 1] = +).Si el vector A fuese vaco la respuesta es sencilla, pues x noest en el vector. Pero para poder obtener una solucinrecursiva efectiva, debemos realizar la siguientegeneralizacin: dado un segmento o subvector A[`+ 1..u 1],0 ` u n+ 1, ordenado crecientemente, y tal queA[`] x < A[u], determinar el valor i, ` i u 1, tal queA[i] x < A[i+ 1]. Este es un ejemplo clsico de inmersin deparmetros.Ahora s, si `+ 1 = u, el segmento en cuestin no contieneelementos y i = u 1 = `.

  • . Llamada inicial: BINSEARCH(A, x, 0, n+ 1)procedure BINSEARCH(A, x, `, u)

    Require: 0 ` < u n+ 1, A[`] x < A[u]Ensure: Retorna i tal que A[i] x < A[i+ 1] y ` i < u

    if `+ 1 = u thenreturn `

    elsem := (`+ u) div 2if x < A[m] then

    return BINSEARCH(A, x, `,m)else

    return BINSEARCH(A, x,m, u)end if

    end ifend procedure

  • La llamada inicial es BINSEARCH(()A, x, 0, A.SIZE() + 1),donde A.SIZE() nos da n, el nmero de elementos del vector A.Prescindiendo de la pequea diferencia entre el tamao real delos subejemplares y el valor n/2, el coste de la bsquedabinaria o dicotmica viene descrito por la recurrencia

    B(n) = (1) +B(n/2), n > 0,

    y B(0) = b0, ya que en cada llamada recursiva slo se haceotra, sea en el subvector a la izquierda, sea el subvector a laderecha del punto central. Empleando el Teorema 1, = log2 1 = 0 y por lo tanto B(n) = (log n).

  • Algoritmo de Karatsuba y Ofmann

    El algoritmo tradicional de multiplicacin tiene coste (n2) paramultiplicar dos enteros de n bits, ya la suma de dos enteros de(n) bits tiene coste (n).

    231 x 659 = 9 x 231 + 5 x 231 x 10+ 6 x 231 x 100= 2079 + 11550 + 138600= 152229

  • Tambi12 es de coste cuadr12 tico el algoritmo de

    multiplicacin a la rusa:

    z := 0. x = X y = Ywhile x 6= 0 do

    . Inv: z + x y = X Yif x es par then

    x := x div 2; y := 2 yelse

    x := x 1; z := z + yend if

    end while. z = X Y

  • Supongamos que x e y son dos nmeros positivos de n = 2k

    bits (si n no es una potencia de 2 x e y no tienen ambos lamisma longitud, podemos aadir 0s por la izquierda para queas sea).Una idea que no funciona:

    x y = (A 2n/2 +B) (C 2n/2 +D)= A C 2n + (A D +B C) 2n/2 +B D.

  • Efectuada la descomposicin x = A,B y y = C,D, secalculan 4 productos (A B, A D, B C y B D) y se combinanlas soluciones mediante sumas y desplazamientos (shifts). Elcoste de estas operaciones es (n). Por lo tanto, el coste de lamultiplicacin mediante este algoritmo divide y vencers vienedado por la recurrencia

    M(n) = (n) + 4 M(n/2), n > 1

    cuya solucin es M(n) = (n2). En este caso = 1, a = 4 yb = 2, y = 1 < c = log2 4 = 2.

  • El algoritmo de Karatsuba y Ofmann (1962) realiza lamultiplicacin dividiendo los dos nmeros como antes pero serealizan las siguientes 3 multiplicaciones (recursivamente)

    U = A CV = B DW = (A+B) (C +D)

    Y el resultado se calcula a partir de las tres solucionesobtenidas

    x y = U 2n + (W (U + V )) 2n/2 + V.

  • El algoritmo requiere realizar 6 adiciones (una de ellas es dehecho una resta) frente a las 3 adiciones que se empleaban enel algoritmo anterior. El coste mejora pues el coste de lasfunciones de divisin y de combinacin del algoritmo deKaratsuba y Ofmann sigue siendo lineal y disminuye el nmerode llamadas recursivas.

    M(n) = (n) + 3 M(n/2)

    M(n) = (nlog2 3) = (n1,5849625...)

    La constante oculta en la notacin asinttica es grande y laventaja de este algoritmo frente a los algoritmos bsicos no semanifiesta hasta que n es relativamente grande (del orden de200 a 250 bits por multiplicando).

  • procedure MULT(x,y, i, j, i, j)Require: 1 i j n, 1 i j n, j i = j iEnsure: Devuelve el producto de x[i..j] por y[i..j]

    n := j i+ 1if n < M then

    Usar un mtodo simple para calcular el resultadoelse

    m := (i+ j) div 2;m := (i + j) div 2U := MULT(x, y,m+ 1, j,m + 1, j)V := MULT(x, y, i,m, i,m)U.DECALA_IZQUIERDA(n)W1 := x[i..m] + y[i

    ..m]W2 := x[m+ 1..j] + y[m

    + 1..j]Aadir un bit a W1 W2 si es necesarioW := MULT(W1,W2, . . .)W :=W (U + V )W.DECALA_IZQUIERDA(n div 2)return U +W + V

    end ifend procedure

  • Ordenacin por fusin

    El algoritmo de ordenacin por fusin (mergesort) fue uno delos primeros algoritmos eficentes de ordenacin propuestos.Diversas variantes de este algoritmo son particularmente tilespara la ordenacin de datos residentes en memoria externa. Elpropio mergesort es un mtodo muy eficaz para la ordenacinde listas enlazadas.La idea bsica es simple: se divide la secuencia de datos aordenar en dos subsecuencias de igual o similar tamao, seordena recursivamente cada una de ellas, y finalmente seobtiene una secuencia ordenada fusionando las dossubsecuencias ordenadas. Si la secuencia es suficientementepequea puede emplearse un mtodo ms simple (y eficazpara entradas de tamao reducido).

  • Supondremos que nuestra entrada es una lista enlazada Lconteniendo una secuencia de elementos x1, . . . , xn. Cadaelemento se almacena en un nodo con dos campos: infocontiene el elemento y next es un apuntador al siguiente nodode la lista (indicaremos que un nodo no tiene sucesor con elvalor especial next = null). La propia lista L es, de hecho, unapuntador a su primer nodo. La notacin apuntador campoindica el campo referido del objeto apuntado como en C y C++.Es equivalente a la expresin p.campo de Pascal y Modula-2.

  • procedure SPLIT(L, L, n)Require: L = [`1, . . . , `m],m nEnsure: L = [`1, . . . , `n], L = [`n+1, . . . , `m]

    p := Lwhile n > 1 do

    p := p nextn := n 1

    end whileL := p next; p next := null

    end procedureprocedure MERGESORT(L, n)

    if n > 1 thenm := n div 2SPLIT(L,L,m)MERGESORT(L,m)MERGESORT(L, nm). fusiona las listas L y L

    L := MERGE(L,L)end if

    end procedure

  • Para fusionar razonamos de la siguiente forma: Si L L esvaca la lista resultante es la otra lista, es decir, la queeventualmente es no vaca. Si ambas son no vacascomparamos sus respectivos primeros elementos: el menor delos dos ser el primer elemento de la lista fusionada resultantey a continuacin vendr la lista resultado de fusionar la sublistaque sucede a ese primer elemento con la otra lista.

    procedure MERGE(L, L)if L = null then return Lend ifif L = null then return Lend ifif L info L info then

    L next := MERGE(L next, L)return L

    elseL next := MERGE(L,L next)return L

    end ifend procedure

  • Cada elemento de L y L es visitado exactamente una vez,por lo que el coste de la operacin MERGE es proporcional a lasuma de los tamaos de las listas L y L; es decir, su coste es(n). La funcin definida es recursiva final y podemos obtenercon poco esfuerzo una versin iterativa algo ms eficiente.Observese que si el primer elemento de la lista L es igual alprimer elemento de L se pone en primer lugar al que provienede L, por lo que MERGESORT es un algoritmo estable.El coste de MERGESORT viene descrito por la recurrencia

    M(n) = (n) +M(n

    2

    )+M

    (n2

    )= (n) + 2 M

    (n2

    )cuya solucin es M(n) = (n log n), aplicando el segundocaso del Teorema 1.

  • Algoritmo de Strassen

    El algoritmo convencional de multiplicacin de matrices tienecoste (n3) para multiplicar dos matrices n n.for i := 1 to n do

    for j := 1 to n doC[i, j] := 0for k := 1 to n do

    C[i, j] := C[i, j] +A[i, k] B[k, j]end for

    end forend for

  • Para abordar una solucin con el esquema divide y vencersse descomponen las matrices en bloques:(

    A11 A12A21 A22

    )(B11 B12B21 B22

    )=

    (C11 C12C21 C22

    )donde C11 = A11 B11 +A12 B21, etc.

  • Cada bloque de C requiere dos multiplicaciones de bloques detamao n/2 n/2 y dos sumas cuyo coste es (n2). El costedel algoritmo divide y vencers as planteado tendra coste

    M(n) = (n2) + 8 M(n/2),

    es decir, M(n) = (n3).Para conseguir una solucin ms eficiente debemos reducir elnmero de llamadas recursivas.

  • Strassen (1969) propuso una forma de hacer justamente esto.En el libro de Brassard & Bratley se detalla una posible manerade hallar la serie de frmulas que producen el resultadodeseado. Un factor que complica las cosas es que lamultiplicacin de matrices no es conmutativa, a diferencia de loque sucede con la multiplicacin de enteros.Se obtienen las siguientes 7 matrices n/2 n/2, mediante 7productos y 14 adiciones/sustracciones

    M1 = (A21 +A22 A11) (B11 +B22 B12)M2 = A11 B11M3 = A12 B21M4 = (A11 A21) (B22 B12)M5 = (A21 +A22) (B12 B11)M6 = (A12 A21 +A11 A22) B22M7 = A22 (B11 +B22 B12 B21)

  • Mediante 10 adiciones/sustracciones ms, podemos obtenerlos bloques de la matriz resultante C:

    C11 = M2 +M3

    C12 = M1 +M2 +M5 +M6

    C21 = M1 +M2 +M4 M7C22 = M1 +M2 +M4 +M5

    Puesto que las operaciones aditivas tienen coste (n2), elcoste del algoritmo de Strassen viene dado por la recurrencia

    M(n) = (n2) + 7 M(n/2),

    cuya solucin es (nlog2 7) = (n2,807...).

  • El algoritmo de Strassen tuvo un enorme impacto terico yaque fue el primer algoritmo de multiplicacin de matrices cuyacomplejidad era o(n3); lo cual tena tambin implicaciones en eldesarrollo de algoritmos ms eficientes para el clculo dematrices inversas, de determinantes, etc. Adems era uno delos primeros casos en que las tcnicas algortmicas superabanlo que hasta el momento pareca una barrera infranqueable.En aos posteriores se han obtenido algoritmos todava mseficientes. El ms eficiente conocido es el de Coppersmith yWinograd (1986) cuyo coste es (n2,376...).El algoritmo de Strassen no es competitivo en la prctica,excepto si n es muy grande (n 500), ya que las constantes ytrminos de orden inferior del coste son muy grandes.

  • QuickSortQUICKSORT (Hoare, 1962) es un algoritmo de ordenacin queusa el principio de divide y vencers, pero a diferencia de losejemplos anteriores, no garantiza que cada subejemplar tendrun tamao que es fraccin del tamao original.La base de quicksort es el procedimiento de PARTICIN: dadoun elemento p denominado pivote, debe reorganizarse elsegmento como ilustra la figura.

  • El procedimiento de particin sita a un elemento, el pivote, ensu lugar apropiado. Luego no queda ms que ordenar lossegmentos que quedan a su izquierda y a su derecha. Mientrasque en MERGESORT la divisin es simple y el trabajo se realizadurante la fase de combinacin, en QUICKSORT sucede locontrario.Para ordenar el segmento A[`..u] el algoritmo queda as

    procedure QUICKSORT(A, `, u)Ensure: Ordena el subvector A[`..u]

    if u `+ 1 M thenusar un algoritmo de ordenacin simple

    elsePARTICIN(A, `, u, k). A[`..k 1] A[k] A[k + 1..u]QUICKSORT((A, `, k 1)QUICKSORT(A, k + 1, u)

    end ifend procedure

  • En vez de usar un algoritmo de ordenacin simple (p.e.ordenacin por insercin) con cada segmento de M o menoselementos, puede ordenarse mediante el algoritmo deinsercin al final:

    QUICKSORT(A, 1, A.SIZE())INSERTSORT(A, 1, A.SIZE())

    Puesto que el vector A est quasi-ordenado tras aplicarQUICKSORT, el ltimo paso se hace en tiempo (n), donden = A.SIZE().Se estima que la eleccin ptima para el umbral o corte derecursin M oscila entre 20 y 25.

  • Existen muchas formas posibles de realizar la particin. EnBentley & McIlroy (1993) se discute un procedimiento departicin muy eficiente, incluso si hay elementos repetidos.Aqu examinamos un algoritmo bsico, pero razonablementeeficaz.Se mantienen dos ndices i y j de tal modo que A[`+ 1..i 1]contiene elementos menores o iguales que el pivote p, yA[j + 1..u] contiene elementos mayores o iguales. Los ndicesbarren el segmento (de izquierda a derecha, y de derecha aizquierda, respectivamente), hasta que A[i] > p y A[j] < p o secruzan (i = j + 1).

  • procedure PARTICIN(A, `, u, k)Require: ` uEnsure: A[`..k 1] A[k] A[k + 1..u]

    i := `+ 1; j := u; p :=A[`]while i < j + 1 do

    while i < j + 1 A[i] p doi := i+ 1

    end whilewhile i < j + 1 A[j] p do

    j := j 1end whileif i < j + 1 then

    A[i] :=: A[j]end if

    end whileA[`] :=: A[j]; k := j

    end procedure

  • El coste de QUICKSORT en caso peor es (n2) y por lo tantopoco atractivo en trminos prcticos. Esto ocurre si en todos ola gran mayora de los casos uno de los subsegementoscontiene muy pocos elementos y el otro casi todos, p.e. assucede si el vector est ordenado creciente odecrecientemente. El coste de la particin es (n) y entoncestenemos

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

    = =ni=0

    (i) =

    0in

    i

    = (n2).

    Sin embargo, en promedio, el pivote quedar ms o menoscentrado hacia la mitad del segmento como sera deseablejustificando que quicksort sea considerado un algoritmo dedivide y vencers.

  • Para analizar el comportamiento de QUICKSORT slo importa elorden relativo de los elementos. Tambin podemos investigarexclusivamente el nmero de comparaciones entre elementos,ya que el coste total es proporcional a dicho nmero.Supongamos que cualquiera de los n! ordenes relativosposibles tiene idntica probabilidad, y sea qn el nmero mediode comparaciones.

    qn =

    1jnE[# compar. |pivote es j-simo] Pr{pivote es j-simo}

    =

    1jn(n 1 + qj1 + qnj) 1

    n

    = n+O(1) + 1n

    1jn

    (qj1 + qnj)

    = n+O(1) + 2n

    0j

  • El CMT considera recurrencias de divide y vencers con elsiguiente formato

    Fn = tn +

    0j

  • DefinicinDado un conjunto de pesos n,j , (z) es una funcin de formapara el conjunto de pesos si

    1 1

    0 (z) dz 12 existe una constante > 0 tal que

    0j

  • Las extensiones a los nmeros reales de muchas funcionesdiscretas son inmediatas, p.e. j2 z2.Para los nmeros binomiales se puede emplear laaproximacin (

    z nk

    ) (z n)

    k

    k!.

    La extensin de los factoriales a los reales viene dada por lafuncin gamma de Euler (z) y la de los nmeros armnicoses la funcin (z) = d ln (z)dz .Por ejemplo, en quicksort los pesos son todos iguales:n,j =

    2n . La funcin de forma correspondiente es

    (z) = lmn n n,zn = 2.

  • Teorema (Roura, 1997)Sea Fn descrita por la recurrencia

    Fn = tn +

    0j 1 constantes.Sea H = 1 10 (z)za dz y H = (b+ 1) 10 (z)za ln z dz.Entonces

    Fn =

    tnH + o(tn) si H > 0,tnH lnn+ o(tn log n) si H = 0 y H 6= 0,(n) si H < 0,

    donde x = es la nica solucin real no negativa de laecuacin

    1 1

    0(z)zx dz = 0.

  • Consideremos qn. Ya hemos visto que los pesos sonn,j = 2/n y tn = n 1. Por tanto (z) = 2, a = 1 y b = 0.Puede comprobarse fcilmente que se dan todas lascondiciones necesarias para la aplicacin del CMT.Calculamos

    H = 1 1

    02z dz = 1 z2z=1

    z=0= 0,

    por lo que tendremos que aplicar el caso 2 del CMT y calcularH

    H = 1

    02z ln z dz =

    z2

    2 z2 ln z

    z=1z=0

    =1

    2.

    Por lo tanto,

    qn =n lnn

    1/2+ o(n log n) = 2n lnn+ o(n log n)

    = 1,386 . . . n log2 n+ o(n log n).

  • QuickSelectEl problema de la seleccin consiste en hallar el j-simo deentre n elementos dados. En concreto, dado un vector A con nelementos y un rango j, 1 j n, un algoritmo de seleccindebe hallar el j-simo elemento en orden ascendente. Si j = 1entonces hay que encontrar el mnimo, si j = n entonces hayque hallar el mximo, si j = bn/2c entonces debemos hallar lamediana, etc.Es fcil resolver el problema con coste (n log n) ordenandopreviamente el vector y con coste (j n), recorriendo el vectory manteniendo los j elementos menores de entre los yaexaminados. Con las estructuras de datos apropiadas puederebajarse el coste a (n log j), lo cual no supone una mejorasobre la primera alternativa si j = (n).QUICKSELECT (Hoare, 1962), tambin llamado FIND yone-sided QUICKSORT, es una variante del algoritmoQUICKSORT para la seleccin del j-simo de entre nelementos.

  • Supongamos que efectuamos una particin de un subvectorA[`..u], conteniendo los elementos `-simo a u-simo de A, ytal que ` j u, respecto a un pivote p. Una vez finalizada laparticin, supongamos que el pivote acaba en la posicin k.Por tanto, en A[`..k 1] estn los elementos `-simo a(k 1)-simo de A y en A[k + 1..u] estn los elementos(k + 1)-simo au-simo. Si j = k hemos acabado ya quehemos encontrado el elemento solicitado. Si j < k entoncesprocedemos recursivamente en el subvector de la izquierdaA[`..k 1] y si j > k entonces encontraremos el elementobuscado en el subvector A[k + 1..u].

  • procedure QUICKSELECT(A, `, j, u)Ensure: Retorna el (j + 1 `)-simo menor elemento de A[`..u],` j u

    if ` = u thenreturn A[`]

    end ifPARTICIN(A, `, u, k)if j = k then

    return A[k]end ifif j < k then

    return QUICKSELECT(A, `, j, k 1)else

    return QUICKSELECT(A, k + 1, j, u)end if

    end procedure

  • Puesto que QUICKSELECT es recursiva final es muy simpleobtener una versin iterativa eficiente que no necesita espacioauxiliar.En caso peor, el coste de QUICKSELECT es (n2). Sinembargo, su coste promedio es (n) donde la constante deproporcionalidad depende del cociente j/n. Knuth (1971) hademostrado que C(j)n , el nmero medio de comparacionesnecesarias para seleccionar el j-simo de entre n es:

    C(j)n = 2((n+ 1)Hn (n+ 3 j)Hn+1j

    (j + 2)Hj + n+ 3)

    El valor mximo se alcanza para j = bn/2c; entoncesC

    (j)n = 2(ln 2 + 1)n+ o(n).

  • Consideremos ahora el anlisis del coste promedio Cnsuponiendo que j adopta cualquier valor entre 1 y n con igualprobabilidad.

    Cn = n+O(1)+

    1

    n

    1kn

    E[nm. de comp. |el pivote es el k-simo] ,

    puesto que el pivote es el k-simo con igual probabilidad paratoda k.

  • La probabilidad de que j = k es 1/n y entonces ya habremosacabado. La probabilidad de continuar a la izquierda es(k 1)/n y entonces se habrn de hacer Ck1 comparaciones.Anlogamente, con probabilidad (n k)/n se continuar en elsubvector a la derecha y se harn Cnk comparaciones.

    Cn = n+O(1) + 1n

    1kn

    k 1n

    Ck1 +n kn

    Cnk

    = n+O(1) + 2n

    0k

  • Podemos obtener un algoritmo cuyo coste en caso peor sealineal si garantizamos que cada paso el pivote escogido divideel vector en dos subvectores cuya talla sea una fraccin de latalla del vector original, con coste O(n) (incluyendo la seleccindel pivote). Entonces, en caso peor,

    C(n) = O(n) + C(p n),

    donde p < 1. Puesto que log1/p 1 = 0 < 1 conclumos queC(n) = O(n). Por otra parte, es bastante obvio queC(n) = (n); luego, C(n) = (n).Este es el principio del algoritmo de seleccin de Rivest y Floyd(1970).

  • La nica diferencia entre el algoritmo de seleccin de Hoare yel de Rivest y Floyd reside en la manera en que elegimos lospivotes. El algoritmo de Rivest y Floyd obtiene un pivote decalidad empleando el propio algoritmo, recursivamente, paraseleccionar los pivotes!De hecho, el algoritmo calcula una pseudomediana y elige stacomo pivote. Se subdivide el subvector A[`..u] en bloques de qelementos (excepto posiblemente el ltimo bloque), con qconstante e impar, y para cada uno de ellos se obtiene sumediana. El coste de esta fase es (n) y como resultado seobtiene un vector con dn/qe elementos. Sobre dicho vector seaplica el algoritmo de seleccin para hallar la mediana. Comoel pivote seleccionado es la pseudomediana de los nelementos originales, ello garantiza que al menosn/2q q/2 = n/4 elementos son mayores que el pivote.

  • Por lo tanto, el coste C(n) en caso peor satisface

    C(n) = O(n) + C(n/q) + C(3n/4).

    Utilizando la versin discreta del CMT, C(n) = O(n) si3

    4+

    1

    q< 1.

    Rivest y Floyd usaron el valor q = 5 en su formulacin originalde este algoritmo.

  • 1 Anlisis de Algoritmos

    2 Divide y vencers

    3 rboles binarios de bsqueda

    4 rboles balanceados (AVLs)

    5 Tablas de Hash

    6 Colas de prioridad

    7 Grafos y Recorridos

  • DefinicinUn rbol binario de bsqueda T es un rbol binario tal que o esvaco o bien contiene un elemento x y satisface

    1 Los subrboles izquierdo y derecho, L y R,respectivamente, son rboles binarios de bsqueda.

    2 Para todo elemento y de L, CLAVE(y) < CLAVE(x), y paratodo elemento z de R, CLAVE(z) > CLAVE(x).

  • LemaUn recorrido en inorden de un rbol binario de bsqueda Tvisita los elementos de T por orden creciente de clave.

    Vamos a considerar ahora el diseo del algoritmo de bsquedaen rboles binarios de bsqueda (BSTs), en lo sucesivo). Dadala naturaleza recursiva de la definicin de BST es razonableabordar el diseo de este algoritmo de manera recursiva.Sea T el BST que representa al diccionario y k la clavebuscada. Si T = entonces k no se encuentra el diccionario yello se habr de indicar de algn modo conveniente. Si T no esvaco entonces tendremos que considerar la relacin queexiste entre la clave del elemento x que ocupa la raz de T y laclave dada k.

  • Si k = CLAVE(x) la bsqueda ha tenido xito y finalizamos,retornando el elemento x (o la informacin asociada a x quenos interesa). Si k < CLAVE(x), se sigue de la definicin de losBSTs, que si hay un elemento en T cuya clave es k entoncesdicho elemento se habr de encontrar en el subrbol izquierdode T , por lo que habr que efectuar una llamada recursivasobre el hijo izquierdo de T . Anlogamente, si k > CLAVE(x)entonces la bsqueda habr de continuar recursivamente en elsubrbol derecho de T .

  • template class Diccionario {public:

    ...void busca(const Clave& k,

    bool& esta, Valor& v) const throw(error);void inserta(const Clave& k,

    const Valor& v) throw(error);void elimina(const Clave& k) throw();...

    private:struct nodo_bst {

    Clave _k;Valor _v;nodo_bst* _izq;nodo_bst* _der;// constructora de la clase nodo_bstnodo_bst(const Clave& k, const Valor& v,

    nodo_bst* izq = NULL, nodo_bst* der = NULL);};nodo_bst* raiz;

    static nodo_bst* busca_en_bst(nodo_bst* p,const Clave& k) throw();

    static nodo_bst* inserta_en_bst(nodo_bst* p,const Clave& k, const Valor& v) throw(error);

    static nodo_bst* elimina_en_bst(nodo_bst* p,const Clave& k) throw();

    static nodo_bst* juntar(nodo_bst* t1,nodo_bst* t2) throw();

    static nodo_bst* reubicar_max(nodo_bst* p) throw();...

    }

  • El mtodo BUSCA emplea el mtodo privado BUSCA_EN_BST.ste ltimo recibe un apuntador p a la raz del BST en el quese ha de hacer la bsqueda y una clave k. Devuelve unapuntador, o bien nulo, si k no est presente en el BST, o bienque apunta al nodo del BST que contiene la clave k.

    template void Diccionario::busca(const Clave& k,

    bool& esta, Valor& v) const throw(error) {

    nodo_bst* p = busca_en_bst(raiz, k);if (p == NULL)

    esta = false;else {

    esta = true;v = p -> _v;

    }}

  • La implementacin recursiva del mtodo privadoBUSCA_EN_BST es casi inmediata a partir de la definicin deBST. Si el rbol es vaco su raz contiene la clave k,devolvemos el apuntador p ya que apunta al lugar correcto (anulo o al nodo con la clave). En caso contrario, se compara kcon la clave almacenada en el nodo raz al que apunta p y seprosigue recursivamente la bsqueda en el subrbol izquierdoo derecho, segn toque.

    // implementacin recursivatemplate Diccionario::nodo_bst*

    Diccionario::busca_en_bst(nodo_bst* p,const Clave& k) throw() {

    if (p == NULL or k == p -> _k)return p;

    // p != NULL and k != p -> _kif (k < p -> _k)

    return busca_en_bst(p -> _izq, k);else // p -> _k < k

    return busca_en_bst(p -> _der, k);}

  • Puesto que el algoritmo de buqueda recursivo es recursivo finales inmediato obtener una versin iterativa.

    // implementacin iterativatemplate Diccionario::nodo_bst*

    Diccionario::busca_en_bst(nodo_bst* p,const Clave& k) throw() {

    while (p != NULL and k != p -> _k) {if (k < p -> _k)

    p = p -> _izq;else // p -> _k < k

    p = p -> _der;}return p;

    }

  • El algoritmo de insercin es tambin extremadamente simple yse obtiene a partir de un razonamiento similar al utilizado paradesarrollar el algoritmo de bsqueda: si la nueva clave esmenor que la clave en la raz entonces el nuevo elemento seha de insertar (recursivamente) en el subrbol izquierda; si esmayor, la insercin se realiza en el subrbol derecho.El mtodo pblico INSERTA se apoya en otro mtodo privado declase llamado INSERTA_EN_BST. ste recibe un apuntador a laraz del BST donde se debe insertar el par k, v y nos devuelveun apuntador a la raz del BST resultante de la insercin. Si kno aparece en el BST, se aade un nodo con el par k, v en ellugar apropiado. Si k ya exista, entonces el mtodo modifica elvalor asociado a k, reemplazando el valor anterior por v.

  • Antes de pasar a la implementacin de las inserciones,veamos la implementacin (trivial) de la constructora de laclase nodo_bst:

    template Diccionario::nodo_bst(const Clave& k,

    Valor& v, nodo_bst* izq, nodo_bst* der) throw(error) :_k(k), _v(v), _izq(izq), _der(der) {

    }

  • template void Diccionario::inserta(const Clave& k,

    const Valor& v) throw(error) {

    raiz = inserta_en_bst(raiz, k, v);

    }

    // implementacin recursivatemplate Diccionario::nodo*

    Diccionario::inserta_en_bst(nodo_bst* p,const Clave& k, const Valor& v) throw(error) {

    if (p == NULL)return new nodo_bst(k, v);

    // p != NULL, contina la insercin en el// subrbol apropiado o reemplaza el valor// asociado si p -> _k == kif (k < p -> _k)

    p -> _izq = inserta_en_bst(p -> _izq, k, v);else if (p -> _k < k)

    p -> _der = inserta_en_bst(p -> _der, k, v);else // p -> _k == k

    p -> _v = v;return p;

    }

  • La versin iterativa es ms compleja, ya que adems delocalizar la hoja en la que se ha de realizar la insercin, debermantenerse un apuntador padre al que ser padre del nuevonodo.

  • // implementacin iterativatemplate Diccionario::nodo_bst*

    Diccionario::inserta_en_bst(nodo_bst* p,const Clave& k, const Valor& v) throw(error) {

    // el BST est vacoif (p == NULL)

    return new nodo_bst(k, v);

    // el BST no est vaconodo_bst* padre = NULL;nodo_bst* p_orig = p;

    // buscamos el punto de insercinwhile (p != NULL and k != p -> _k) {

    padre = p;if (k < p -> _k)

    p = p -> _izq;else // p -> _k < k

    p = p -> _der;}

    // insertamos el nuevo nodo como hoja// o modificamos el valor asociado, si// ya haba un nodo con la clave k dadaif (p == NULL) {

    if (k < padre -> _k)padre -> izq = new nodo_bst(k, v);

    else // k > padre -> _kpadre -> der = new nodo_bst(k, v);

    }else // k == p -> _k

    p -> _v = v;

    return p_orig;}

  • Slo nos queda por considerar la eliminacin de elementos enBSTs. Si el elemento a eliminar se encuentra en un nodo cuyosdos subrboles son vacos basta eliminar el nodo en cuestin.Otro tanto sucede si el nodo x a eliminar slo tiene un subrbolno vaco: basta hacer que la raz del subrbol no vaco quedecomo hijo del padre de x.

  • El problema surge si hay que eliminar un nodo que contienedos subrboles no vacos. Podemos reformular el problema dela siguiente forma: dados dos BSTs T1 y T2 tales que todas lasclaves de T1 son menores que las claves de T2 obtener unnuevo BST que contenga todas las claves: T = JUNTAR(T1, T2).Obviamente:

    JUNTAR(T,) = TJUNTAR(, T ) = T

    En particular, JUNTAR(,) = .

  • template void Diccionario::elimina(

    const Clave& k) throw() {

    raiz = elimina_en_bst(raiz, k);}

    template Diccionario::nodo_bst*

    Diccionario::elimina_en_bst(nodo_bst* p,const Clave& k) throw() {

    // la clave no estif (p == NULL) return p;

    if (k < p -> _k)p -> _izq = elimina_en_bst(p -> _izq, k);

    else if (p -> k < k)p -> _der = elimina_en_bst(p -> _der, k);

    else { // k == p -> knodo_bst* to_kill = p;p = juntar(p -> _izq, p -> _der);delete to_kill;

    }return p;

    }

  • Sea z+ la mayor clave de T1. Puesto que es mayor que todaslas dems en T1 pero al mismo tiempo menor que cualquierclave de T2 podemos construir T colocando un nodo raz quecontenga al elemento de clave z+, a T2 como subrbol derechoy al resultado de eliminar z+ de T1 llammosle T 1 comosubrbol izquierdo. Adems puesto que z+ es la mayor clavede T1 el correspondiente nodo no tiene subrbol derecho, y esel nodo ms a la derecha en T1, lo que nos permitedesarrollar un procedimiento ad-hoc para eliminarlo.

    T1 T2+z z

  • template Diccionario::nodo_bst*

    Diccionario::juntar(nodo_bst* t1,nodo_bst* t2) throw() {

    // si uno de los dos subrboles es// vaco, la implementacin es trivialif (t1 == NULL) return t2;if (t2 == NULL) return t1;

    // t1 != NULL y tambin t2 != NULLnodo_bst* z = reubica_max(t1);z -> _der = t2;return z;

    // alternativa: z = reubica_min(t2);// z -> _izq = t1;// return z;

    }

  • El mtodo privado REUBICA_MAX recibe un apuntador p a laraz de un BST T y nos devuelve un apuntador a la raz de unnuevo BST T . La raz de T es el elemento mximo de T . Elsubrbol derecho de T es nulo y el subrbol izquierdo de T esel BST que se obtiene al eliminar el elemento mximo de T .La nica dificultad estriba en tratar adecuadamente lasituacin en la que el elemento mximo del BST se encuentraya como raz del mismodicha situacin debe detectarse y enese caso el mtodo no debe hacer nada.template Diccionario::nodo_bst*

    Diccionario::reubica_max(nodo_bst* p) throw() {

    nodo_bst* padre = NULL;nodo_bst* p_orig = p;while (p -> _der != NULL) {

    padre = p;p = p -> _der;

    }if (padre != NULL) {

    padre -> _der = p -> _izq;p -> _izq = p_orig;

    }return p;

    }

  • Un razonamiento anlogo nos lleva a una versin de JUNTARen la que se emplea la clave mnima z del rbol T2 para queocupe la raz del resultado, en el caso en que T1 y T2 no sonvacos.Se ha evidenciado experimentalmente que conviene alternarentre el predecesor z+ y el sucesor z del nodo a eliminar enlos borrados (por ejemplo, mediante una decisin aleatoria ocon bit que va pasando alternativamente de 0 a 1 y de 1 a 0) yno utilizar sistemticamente una de las versiones.

  • Un BST de n elementos puede ser equivalente a una lista,pues puede contener un nodo sin hijos y n 1 con slo un hijo(p.e. si insertamos una secuencia de n elementos con clavescrecientes en un BST inicialmente vaco). Un BST con estascaractersticas tiene altura n. En caso peor, el coste de unabsqueda, insercin o borrado en dicho rbol es (n). Engeneral, una bsqueda, insercin o borrado en un BST dealtura h tendr coste (h) en caso peor. Como funcin de n, laaltura de un rbol puede llegar a ser n y de ah que el coste delas diversas operaciones es, en caso peor, (n).

  • Pero normalmente el coste de estas operaciones ser menor.Supongamos que cualquier orden de insercin de loselementos es equiprobable. Para bsquedas con xitosupondremos que buscamos cualquiera de las n claves conprobabilidad 1/n. Para bsquedas sin xito o insercionessupondremos que finalizamos en cualquiera de las n+ 1 hojascon igual probabilidad.El coste de una de estas operaciones va a ser proporcional alnmero de comparaciones que habr que efectuar entre laclave dada y las claves de los nodos examinados. Sea C(n) elnmero medio de comparaciones y C(n; k) el nmero medio decomparaciones si la raz est ocupada por la k-sima clave.

    C(n) =

    1knC(n; k) P[raz es k-sima] .

  • C(n) =1

    n

    1kn

    C(n; k)

    = 1 +1

    n

    1kn

    (1

    n 0 + k 1

    n C(k 1) + n k

    n C(n k)

    )= 1 +

    1

    n2

    0k

  • La longitud media de caminos internos satisface la recurrencia

    I(n) = n 1 + 2n

    0k

  • Podemos asociar a cada ejecucin de QUICKSORT un BSTcomo sigue. En la raz se coloca el pivote de la fase inicial; lossubrboles izquierdo y derecho corresponden a lasejecuciones recursivas de QUICKSORT sobre los subvectores ala izquierda y a la derecha del pivote.Consideremos un subrbol cualquiera de este BST. Todos loselementos del subrbol, salvo la raz, son comparados con elnodo raz durante la fase a la que corresponde ese subrbol. Yrecprocamente el pivote de una determinada fase ha sidocomparado con los elementos (pivotes) que son susantecesores en el rbol y ya no se comparar con ningn otropivote. Por lo tanto, el nmero de comparaciones en las queinterviene un cierto elemento no siendo el pivote es igual a sudistancia a la raz del BST. Por esta razn I(n) = Q(n).

  • Para resolver la recurrencia de I(n) (longitud de caminosinternos en BSTs) calculamos (n+ 1)I(n+ 1) nI(n):

    (n+ 1)I(n+ 1) nI(n) = (n+ 1)n n(n 1) + 2I(n)= 2n+ 2I(n);

    (n+ 1)I(n+ 1) = 2n+ (n+ 2)I(n)

  • I(n+ 1) =2n

    n+ 1+n+ 2

    n+ 1I(n) =

    2n

    n+ 1+

    2(n 1)(n+ 2)n(n+ 1)

    +n+ 2

    nI(n 1)

    =2n

    n+ 1+

    2(n 1)(n+ 2)n(n+ 1)

    +2(n 2)(n+ 2)

    n(n 1) +n+ 2

    n 1I(n 2)

    =2n

    n+ 1+ 2(n+ 2)

    i=ki=1

    n i(n i+ 1)(n i+ 2) +

    n+ 2

    n k + 1I(n k)

    =2n

    n+ 1+ 2(n+ 2)

    i=ni=1

    i

    (i+ 1)(i+ 2)

    = O(1) + 2(n+ 2)

    1in

    (2

    i+ 2 1i+ 1

    )= O(1) + 2(n+ 2)( 2

    n+ 2+

    1

    n+ 1+Hn 2)

    = 2nHn 4n+ 4Hn +O(1),

  • donde Hn =

    1in 1/i.Puesto que Hn = lnn+O(1) se sigue que

    I(n) = Q(n) = 2n lnn+O(n)= 1,386 . . . n log2 n+O(n)

    y que C(n) = 2 lnn+O(1).

  • Los BSTs permiten otras varias operaciones siendo losalgoritmos correspondientes simples y con costes (promedio)razonables. Algunas operaciones como las de bsqueda oborrado por rango (e.g. busca el 17o elemento) o de clculo delrango de un elemento dado requieren una ligera modificacinde la implementacin estndar, de tal forma que cada nodocontenga informacin relativa a tamao del subrbol en lenraizado. Concluimos esta parte con un ejemplo concreto:dadas dos claves k1 y k2, k1 < k2 se precisa una operacin quedevuelve una lista ordenada de todos los elementos cuya clavek est comprendida entre las dos dadas, esto es, k1 k k2.

  • Si el BST es vaco, la lista a devolver es tambin vaca.Supongamos que el BST no es vaco y que la clave en la razes k. Si k < k1 entonces todas las claves buscadas debenencontrarse, si las hay, en el subrbol derecho. Anlogamente,si k2 < k se proseguir la bsqueda recursivamente en elsubrbol izquierdo. Finalmente, si k1 k k2 entonces puedehaber claves que caen dentro del intervalo tanto en el subrbolizquierdo como en el derecho. Para respetar el orden crecienteen la lista, deber buscarse recursivamente a la izquierda,luego listar la raz y finalmente buscarse recursivamente a laderecha.

  • // El mtodo en_rango se ha de declarar como// mtodo pblico de la clase Diccionario.// Dicho mtodo usa a otro auxiliar,// en_rango_en_bst, que se habra declarado// como mtodo privado de clase (static).template void Diccionario::en_rango(

    const Clave& k1, const Clave& k2,list& result) const throw(error) {

    en_rango_en_bst(raiz, k1, k2, result);}

    template void Diccionario::en_rango_en_bst(nodo_bst* p,

    const Clave& k1, const Clave& k2,list& result) const throw(error) {

    if (p == NULL) return;

    if (k1 _k)en_rango_en_bst(p -> _izq, k1, k2, result);

    if (k1 _k and p -> _k _k, p -> _v));

    if (k _k)en_rango_en_bst(p -> _der, k1, k2, result);

    }

  • 1 Anlisis de Algoritmos

    2 Divide y vencers

    3 rboles binarios de bsqueda

    4 rboles balanceados (AVLs)

    5 Tablas de Hash

    6 Colas de prioridad

    7 Grafos y Recorridos

  • rboles equilibrados

    El problema de los BSTs estndar es que, como resultado deciertas secuencias de inserciones y/o borrados, pueden quedarmuy desequilibrados.En caso peor la altura de un BST de tamao n es (n) y enconsecuencia el coste en caso peor de las operaciones debsqueda, insercin y borrado es (n).A fin de evitar este problema, se han propuesto diversassoluciones; la ms antigua y una de las ms elegantes ysencillas, es el equilibrado en altura, propuesto en 1962 porAdelson-Velskii y Landis.Los rboles de bsqueda resultantes se denominan AVLs enhonor a sus inventores.

  • DefinicinUn AVL T es un rbol binario de bsqueda tal que o es vaco obien cumple que

    1 Los subrboles izquierdo y derecho, L y R,respectivamente, son AVLs.

    2 Las alturas de L y de R difieren a lo sumo en una unidad:

    |altura(L) altura(R)| 1

  • La naturaleza recursiva de la definicin anterior garantiza quela condicin de equilibrio se cumple en cada nodo x de un AVL.Si denotamos bal(x) la diferencia entre las alturas de lossubrboles izquierdo y derecho de un nodo cualquiera xtendremos que bal(x) {1, 0,+1}.Por otro lado todo AVL es un rbol binario de bsqueda, por loque el algoritmo de bsqueda en AVLs es idntico al de BSTs,y un recorrido en inorden de un AVL visitar todos suselementos en orden creciente de claves.

  • febrero

    marzo

    agosto

    abril enero julio

    octurbre

    mayo

    noviembre

    septiembre

    diciembre

    junio

    {enero, febrero, marzo, abril, mayo, junio, julio, agosto, septiembre, octubrenoviembre, diciembre}

  • LemaLa altura h(T ) de un AVL de tamao n es (log n).

    Demostracin. Puesto que el AVL es un rbol binario secumple que h(T ) dlog2(n+ 1)e, es decir, h(T ) (log n).Ahora vamos a demostrar que h(T ) O(log n).Sea Nh el mnimo nmero de nodos necesario para construirun AVL de altura h. Claramente N0 = 0 y N1 = 1. El AVL msdesequilibrado posible de altura h > 1 se consigue poniendoun subrbol, digamos el izquierdo, de altura h 1 empleando elmnimo posible de nodos Nh1 y otro subrbol, el derecho, quetendr altura h 2 (pero no puede tener menos!) en el quepondremos tambin el mnimo posible de nodos, Nh2. Portanto,

    Nh = 1 +Nh1 +Nh2

  • Veamos algunos valores de la secuencia {Nh}h0:

    0, 1, 2, 4, 7, 12, 20, 33, 54, 88, . . .

    La similitud con la secuencia de los nmeros de Fibonaccillama rpidamente nuestra atencin y no es casual: puesto queF0 = 0, F1 = 1, Fn = Fn1 +Fn2 se cumple que Nh = Fh+11para toda h 0. En efecto, N0 = F1 1 = 1 1 = 0 y

    Nh = 1 +Nh1 +Nh2 = 1 + (Fh 1) + (Fh1 1) = Fh+1 1

    El nmero n-simo de Fibonacci cumple:

    Fn =

    n

    5+

    1

    2

    ,

    donde = (1 +

    5)/2 1,61803 . . . es la razn urea.

  • Consideremos ahora un AVL de tamao n y altura h. Entoncesn Nh por definicin y

    n Fh+1 1 h+1

    5 3

    2

    Luego,

    (n+3

    2)

    5

    h

    Tomando logaritmos en base , y simplificando

    h log n+O(1) = 1,44 log2 n+O(1)

    El lema queda demostrado.

  • Del lema anterior deducimos que el coste de cualquierbsqueda en un AVL de tamao n ser, incluso en caso peor,O(log n).El problema reside en cmo conseguir que se cumpla lacondicin de equilibrio en cada uno de los nodos del AVL trasuna insercin o un borrado.La idea es que ambos algoritmos actan igual que loscorrespondientes en BSTs, pero cada uno de los nodos en elcamino que va de la raz al punto de insercin (o borrado) debeser comprobado para verificar que contina cumpliendo lacondicin de balance y si no es as hacer algo al efecto.En primer lugar nos damos cuenta de que ser necesario quecada nodo almacene informacin sobre su altura (o subalance), para evitar cmputos demasiado costosos.

  • template class Diccionario {public:

    ...void buscar(const Clave& k,

    bool& esta, Valor& v) const throw(error);void inserta(const Clave& k,

    const Valor& v) throw(error);void elimina(const Clave& k) throw();...

    private:struct nodo_avl {

    Clave _k;Valor _v;int _alt;nodo_avl* _izq;nodo_avl* _der;// constructora de la clase nodo_avlnodo_avl(const Clave& k, const Valor& v,

    int alt = 1,nodo_avl* izq = NULL,nodo_avl* der = NULL);

    };nodo_avl* raiz;...static int altura(nodo_avl* p) throw();static void actualizar_altura(nodo_avl* p) throw();

    };

  • int max(int x, int y) {

    return x > y ? x : y;}

    template static int Diccionario::altura(

    nodo_avl* p) throw() {

    if (p == NULL)return 0;

    elsereturn p -> _alt;

    }

    template static void Diccionario::actualizar_altura(

    nodo_avl* p) throw() {

    p -> _alt = 1 + max(altura(p -> _izq), altura(p -> _der));}

  • Para reestablecer el balance en un nodo que ya no cumpla lacondicin de balance se utilizan rotaciones.La figura muestra las rotaciones ms simples. Obsrvese quesi el rbol de la izquierda es un BST (A < x < B < y < C)entonces el de la derecha tambin lo es, y viceversa.

    y

    C

    BA

    x

    B C

    x

    yA

  • Supongamos un subrbol de un AVL con raz y en el queestamos haciendo la insercin de una clave k tal que k < x < yy que finalizado el proceso de insercin recursiva se produceun desequilibrio en y.Si C tiene altura h entonces el subrbol enraizado en x ha detener altura h altura h+ 1. Si la altura de x fuera h entoncesla insercin de la nueva clave k no podra provocar eldesequilibrio en y, de manera que vamos a suponer que laaltura de x es h+ 1.

  • Razonando de manera parecida llegamos a la conclusin deque la altura de A tiene que ser h. As pues la altura de y,previa a la insercin era h+ 2. Como consecuencia de lainsercin vamos a suponer que la altura del subrbol A seincrementa, de manera que el nuevo subrbol A tiene alturah+ 1. Si suponemos que x mantiene la condicin de AVL trasla insercin (pero y no) eso nos lleva a concluir que B tambintena altura h.Tras la insercin el subrbol enraizado en x pasa a tener alturah+ 2 de manera que bal(y) = +2!

  • h+1

    B Chh+1

    x

    y

    A

    hh

    h

    y

    Antes:

    h+1C

    BA

    x

    h

    y

    x

    h+1

    hC

    Bh+2

    A

    Despues:

    LL

  • Al aplicar la rotacin sobre el nodo y, se preserva la propiedadde BST y adems se reestablece la propiedad de AVL. Porhiptesis, A, B y C son AVLs. Tras la rotacin la altura de y esh+ 1 y su balance 0, y la altura de x pasa a ser h+ 2 y subalance 0.No slo hemos solventado el problema de desequilibrio en y: elsubrbol donde hemos aplicado la insercin es un AVL y tienela misma altura que el subrbol antes de hacer la insercin, demanera que ninguno de los antecesores de y en el AVL va aestar desbalanceado.A la rotacin que hemos empleado se le suele denominar LL(left-left).

  • Un anlisis anlogo revela que si la clave insertada k cumplex < y < k y se produce un desbalanceo en el nodo x, entoncespodemos reestablecer el equilibrio aplicando una rotacin RR(right-right) sobre el nodo x. La rotacin RR es la simtrica dela rotacin LL.

    h+2

    x

    y

    h Bh+1

    A h

    C

    y

    x

    h BA

    h+1 h+1C

    RR

  • Analicemos ahora el caso de un subrbol de un AVL con raz yen el que estamos haciendo la insercin de una clave k, peroesta vez x < k < y y que finalizado el proceso de insercinrecursiva se produce un desequilibrio en y.Si C tiene altura h entonces el subrbol enraizado en x ha detener altura h+ 1, la altura de B ha de ser h (aqu razonamosigual que cuando hicimos el anlisis de la situacin LL).Como consecuencia de la insercin vamos a suponer que laaltura del subrbol B se incrementa, de manera que el nuevosubrbol B tiene altura h+ 1. Si suponemos que x mantiene lacondicin de AVL tras la insercin (pero y no) eso nos lleva aconcluir que A tambin tena altura h.Tras la insercin el subrbol enraizado en x pasa a tener alturah+ 2 de manera que bal(y) = +2!

  • Si aplicamos la rotacin simple LL sobre el nodo y, se preservala propiedad de BST pero no se reestablece la propiedad deAVL. Por hiptesis, A, B y C son AVLs. Pero tras la rotacin LLla altura de y es h+ 2 y su balance +1, y la altura de x pasa aser h+ 3 y su balance 2!

    h

    y

    h+1

    hh+1

    C

    A

    x

    B

    x

    A

    BCh

    y

    h+1

    h

    h+2

    LL no sirve

    Vamos a tener que pensar alguna otra cosa y hacer un anlisisms fino.

  • Vamos a suponer que la raz del subrbol B es z y que tienesubrboles B1 y B2, ambos AVLs. Puesto que B tiene alturah+ 1 y es un AVL, al menos uno de los dos subrboles Bi tienealtura h y el otro Bj tiene altura h h 1.Si aplicamos una rotacin que lleva a z a la raz entonces x esla raz de su subrbol izquierdo y sus hijos son A y B1, y y esla raz de su subrbol derecho y sus hijos son B2 y C.

    y

    x h

    LR

    B1

    z

    2B

    z

    CB

    y

    2A B1

    _< h+1

    x

    h

    h+2

    h+1

    _< h

    C

    Ahh+2

    h+1

    Ntese que antes de la rotacin el inorden eraA < x < B1 < z < B2 < y < C, exactamente igual que tras larotacin, esto, este nuevo tipo de rotacin tambin preserva lapropiedad de BST.

  • Al aplicar la rotacin sobre el nodo y, se preserva la propiedadde BST y adems se reestablece la propiedad de AVL. Porhiptesis, A, B y C son AVLs. Tras la rotacin la altura de x esh+ 1 y su balance 0 +1, la altura de y es h+ 1 y su balance 0 1, y la altura de z pasa a ser h+ 2 y su balance 0.Igual que con las rotaciones simples LL y RR, no sloarreglamos el problema de desequilibrio en y: el subrboldonde hemos aplicado la insercin es un AVL y tiene la mismaaltura que el subrbol antes de hacer la insercin, de maneraque ninguno de los antecesores de y en el AVL va a estardesbalanceado.A esta nueva rotacin se le denomina rotacin doble LR(left-right).

  • Para la situacin en que una clave se inserta a la derecha yluego hacia la izquierda, el desequilibrio que se pudieraproducir se corrige mediante una rotacin RL, que escompletamente anloga a la LR, cambiando derecha porizquierda y viceversa.

    B1

    z

    2B_< h

    z

    CB

    y

    2A B1

    _< h+1

    x

    h

    h+2

    h+1

    RL

    x

    yA h

    Ch+2h+1

  • Las rotaciones dobles (LR y RL) se denominan as porquepodemos descomponerlas como una secuencia de dosrotaciones simples. Por ejemplo la rotacin doble LR sobre elnodo y consiste en primero aplicar una rotacin RR sobre x y acontinuacin una rotacin LL sobre y (su hijo izquierdo hapasado a ser z y no x).

    RR(x)

    z

    A B1

    y

    C

    2B_< h1

    xh+2

    h

    h

    y

    x h

    _< h1B1

    C

    A

    h

    z

    2B

    h

    h+2

    LL(y)

    z

    CB

    y

    2A B1

    x

    h

    h+2

    h+1

    h

  • De manera similar, una rotacin RL se compone de unarotacin LL seguida de una rotacin RR.

    LL(y)

    RR(x)

    B1

    z

    2B_< h

    x

    yA h

    Ch+2

    h

    z

    CB

    y

    2

    x

    A

    B1 _< hh

    h+2 h

    h

    z

    CB

    y

    2A B1

    _< h

    x

    h

    h+2

    h+1

    h

  • Ejemplo de los meses del ao en un AVL:RR febrero

    enero marzo0

    0

    0

    enero

    febrero

    marzo0

    1

    2

    abril

    enero

    julio

    junio

    marzo

    febrero

    mayo

    agosto

    + {abril, mayo, junio, julio, agosto}

    00

    0+1

    +1

    1

    0

    +2

    febrero

    abril

    agosto

    enero

    marzo

    julio

    junio mayo0

    1

    0

    0 +1

    0

    0+1

    +{enero, febrero, marzo}

    LR

  • Ejemplo de los meses del ao en un AVL:

    RL

    febrero

    abril

    agosto

    enero

    julio

    junio

    marzo

    mayo septiembre

    octubre00

    0

    1

    0

    +1 0

    0 0

    0

    febrero

    abril

    agosto

    enero

    julio

    junio

    marzo

    mayo

    septiembre

    octubre

    + {septiembre, octubre}

    00

    0

    2

    0

    +1

    0

    2

    +1

    1

  • Ejemplo de los meses del ao en un AVL:febrero

    abril

    agosto

    enero

    julio

    junio

    marzo

    mayo septiembre

    octubre00

    0

    0

    +1

    01

    +1

    0noviembre

    1

    2

    RR

    febrero

    abril enero

    junioagosto

    marzo

    mayo

    noviembre

    octubre

    julio

    septiembre+1

    +1

    10

    0

    0

    0 000

    0

    + {noviembre}

    febrero

    abril enero

    junioagosto

    marzo

    mayo

    noviembrejulio

    septiembre

    diciembre

    octubre

    +1

    +1

    0+1

    1

    +1

    +1

    1

    0

    + {diciembre}

    0

    0

    0

  • De nuestro anlisis previo podemos establecer los siguienteshechos y sus consecuencias:

    1 Una rotacin simple o doble slo involucra la modificacinde unos pocos apuntadores y actualizaciones de alturas, ysu coste es por tanto (1), independiente de la talla delrbol.

    2 En una insercin en un AVL slo habr que efectuarse a losumo una rotacin (simple o doble) para reestablecer elequilibrio del AVL. Dicha rotacin se aplica, en su caso, enuno de los nodos en el camino entre la raz y el punto deinsercin.

    3 El coste en caso peor de una insercin en un AVL es(log n).

  • template class Diccionario {public:

    ...private:

    struct nodo_avl {...

    };nodo_avl* raiz;...static nodo_avl* inserta_en_avl(nodo_avl* p,

    const Clave& k, const Valor& v) throw(error);static nodo_avl* rotacionLL(nodo_avl* p) throw();static nodo_avl* rotacionLR(nodo_avl* p) throw();static nodo_avl* rotacionRL(nodo_avl* p) throw();static nodo_avl* rotacionRR(nodo_avl* p) throw();...

    };

  • template Diccionario::nodo_avl*

    Diccionario::rotacionLL(nodo_avl* p)throw() {

    nodo_avl* q = p -> _izq;p -> _izq = q -> _der;q -> _der = p;actualiza_altura(p);actualiza_altura(q);return q;

    }

    template Diccionario::nodo_avl*

    Diccionario::rotacionRR(nodo_avl* p)throw() {

    nodo_avl* q = p -> _der;p -> _der = q -> _izq;q -> _izq = p;actualiza_altura(p);actualiza_altura(q);return q;

    }

  • template Diccionario::nodo_avl*

    Diccionario::rotacionLR(nodo_avl* p)throw() {

    p -> _izq = rotacionRR(p -> _izq);return rotacionLL(p);

    }

    template Diccionario::nodo_avl*

    Diccionario::rotacionRL(nodo_avl* p)throw() {

    p -> _der = rotacionLL(p -> _der);return rotacionRR(p);

    }

  • template Diccionario::nodo_avl*

    Diccionario::inserta_en_avl(nodo_avl* p,const Clave& k, const Valor& v) throw(error) {

    if (p == NULL)return new nodo_avl(k, v);

    if (k < p -> _k) {p -> _izq = inserta_en_avl(p -> _izq, k, v);// comprobamos si hay desequilibrio en p// y rotamos si es necesarioif (altura(p -> _izq) - altura(p -> _der) == 2) {

    // p -> _izq no puede ser vacoif (k < p -> _izq -> _k) // caso LL

    p = rotacionLL(p);else // caso LR

    p = rotacionLR(p);}

    }else if (p -> _k < k) { // analogo al caso anterior ...

    p -> _der = inserta_en_avl(p -> _der, k, v);if (altura(p -> _der) - altura(p -> _izq) == 2) {

    if (p -> _der -> _k < k) // caso RRp = rotacionRR(p);

    else // caso RLp = rotacionRL(p);

    }}else // p -> _k == k

    p -> _v = v;

    actualiza_altura(p);

    return p;}

  • Si bien resulta posible escribir una versin iterativa delalgoritmo de insercin, resulta complicada ya que una vezefectuada la insercin del nuevo nodo en la hoja quecorresponde deberemos deshacer el camino hasta la raz paradetectar si en alguno de los nodos del camino se produce undesequilibrio y entonces aplicar la rotacin correspondiente.Este deshacer el camino ocurre de manera natural con larecursividad, a la vuelta de cada llamada recursivacomprobamos si en el nodo desde el que se hace la llamadarecursiva hay o no desequilibrio al terminar la insercin.Para una versin iterativa necesitaramos que cada nodocontuviese un apuntador explcito a su padre o bien tendremosque almacenar la secuencia de nodos visitados durante labajada en una pila, y despus ir desapilando uno a uno paradeshacer el camino.

  • El algoritmo de borrado en AVLs se fundamenta en las mismasideas, si bien el anlisis de qu rotacin aplicar en cada casoes un poco ms complejo.Baste decir que el coste en caso peor de un borrado en un AVLes (log n). Los desequilibrios se solucionan aplicandorotaciones simples o dobles, pero a diferencia de lo que sucedecon las inserciones, podemos tener que aplicar varias. Noobstante, todas las rotaciones (de coste (1)) se aplican a losumo una vez sobre cada uno de los nodos del camino desdela raz hasta el punto de borrado, y como slo hay O(log n) destos, el coste es logartmico en caso peor.El algoritmo de borrado en AVLs es relativamente mscomplejo que el de insercin, y la versin iterativa an ms.En las siguientes transparencias se muestra el cdigo delalgoritmo de borrado, en su versin recursiva, sin mscomentarios.

  • template Diccionario::nodo_avl*

    Diccionario::elimina_en_avl(nodo_avl* p,const Clave& k) throw() {

    if (p == NULL) return p;if (k < p -> _k) {

    p -> _izq = elimina_en_avl(p -> _izq, k);// comprobamos si hay desequilibrio en p// y rotamos si es necesarioif (altura(p -> _der) - altura(p -> _izq) == 2) {

    // p -> _der no puede ser vacoif (altura(p -> _der -> _izq)

    - altura(p -> _der -> _der) == 1)p = rotacionRL(p);

    elsep = rotacionRR(p);

    }}else if (p -> _k < k) { // analogo al caso anterior ...

    p -> _der = elimina_en_avl(p -> _der, k);if (altura(p -> _izq) - altura(p -> _der) == 2) {

    if (altura(p -> _izq -> _der)- altura(p -> _der -> _izq) == 1)

    p = rotacionLR(p);else

    p = rotacionLL(p);}

    }else { // p -> _k == k

    nodo_avl* to_kill = p;p = juntar(p -> _izq, p -> _der);delete to_kill;

    }actualiza_altura(p);return p;

    }

  • template Diccionario::nodo_avl*

    Diccionario::juntar(nodo_avl* t1,nodo_avl* t2) throw() {

    // si uno de los dos subrboles es// vaco, la implementacin es trivialif (t1 == NULL) return t2;if (t2 == NULL) return t1;

    // t1 != NULL y tambin t2 != NULLnodo_alv* z;elimina_min(t2, z);z -> _izq = t1;z -> _der = t2;actualiza_altura(z);return z;

    }

  • template Diccionario::nodo_avl*

    Diccionario::elimina_min(nodo_avl*& p, nod_avl*& z) throw() {

    if (p -> _izq != NULL) {elimina_min(p -> _izq, z);// comprobamos si hay desequilibrio en p// y rotamos si es necesarioif (altura(p -> _der) - altura(p -> _izq) == 2) {

    // p -> _der no puede ser vacoif (altura(p -> _der -> _izq)

    - altura(p -> _der -> _der) == 1)p = rotacionRL(p);

    elsep = rotacionRR(p);

    }} else {

    z = p;p = p -> _der;

    }actualiza_altura(p);

    }

  • 1 Anlisis de Algoritmos

    2 Divide y vencers

    3 rboles binarios de bsqueda

    4 rboles balanceados (AVLs)

    5 Tablas de Hash

    6 Colas de prioridad

    7 Grafos y Recorridos

  • Tablas de dispersin (hash)

    Una tabla de dispersin (ing: hash table) permite almacenar unconjunto de elementos (o pares clave-valor) mediante unafuncin de dispersin h que va del conjunto de claves alconjunto de ndices o posiciones de la tabla (p.e. 0..M 1).Idealmente la funcin de dispersin h hara corresponder acada uno de los n elementos (de hecho a sus claves) quequeremos almacenar una posicin distinta. Obviamente, estono ser posible en general y a claves distintas lescorresponder la misma posicin de la tabla.

  • Pero si la funcin de dispersin dispersa eficazmente lasclaves, el esquema seguir siendo til, ya que la probabilidadde que el diccionario a representar mediante la tabla de hashcontenga muchas claves con igual valor de h ser pequea.Dadas dos claves x e y distintas, se dice que x e y sonsinnimos, o que colisionan, si h(x) = h(y).El problema bsico de la implementacin de un diccionariomediante una tabla de hash consistir en la definicin de unaestrategia de resolucin de colisiones.

  • template class Hash {public:

    int operator()(const T& x) const throw();};

    template

    class Diccionario {public:

    ...void busca(const Clave& k,

    bool& esta, Valor& v) const throw(error);void inserta(const Clave& k, const Valor& v) throw(error);void elimina(const Clave& k) throw();...

    private:struct nodo {

    Clave _k;Valor _v;...

    };nat _M; // tamao de la tablanat _n; // nm. de elementos en la tabladouble _alfa_max; // factor de carga

    // direccionamiento abiertonodo* _Thash; // tabla con los pares

    // separate chaining// nodo** _Thash; // tabla de punteros a nodo

    static int hash(const Clave& k) throw() {HashFunct h;return h(k) % _M;

    }};

  • Funciones de hash

    Una buena funcin de hash debe tener las siguientespropiedades:

    1 Debe ser fcil de calcular2 Debe dispersar de manera razonablemente uniforme el

    espacio de las claves, es decir, si dividimos el conjunto detodas las claves en grupos de sinnimos, todos los gruposdeben contener aproximadamente el mismo nmero declaves.

    3 Debe enviar a posiciones distantes a claves que sonsimilares.

  • La construccin de buenas funciones de hash no es fcil yrequiere conocimientos muy slidos de varias ramas de lasmatemticas. Est fuertemente relacionada con laconstruccin de generadores de nmeros pseudoaleatorios.Como regla general da buenos resultados calcular un nmeroentero positivo a partir de la representacin binaria de la clave(p.e. sumando los bytes que la componen) y tomar mdulo M ,el tamao de la tabla. Se recomienda que M sea un nmeroprimo.La clase Hash define el operador () de manera que si hes un objeto de la clase Hash y x es un objeto de la claseT, podemos aplicar h(x). Esto nos devolver un nmeroentero. La operacin privada hash de la clase Diccionariocalcular el mdulo mediante h(x)% _M de manera queobtengamos un ndice vlido de la tabla, entre 0 y _M - 1.

  • // especializacin del template para T = stringtemplate class Hash {public:

    int operator()(const string& x) const throw() {

    int s = 0;for (int i = 0; i < x.length(); ++i)

    s = s * 37 + x[i];return s;

    };

    // especializacin del template para T = inttemplate class Hash {static long const MULT = 31415926;public:

    int operator()(const int& x) const throw() {

    long y = ((x * x * MULT) > 4;return y;

    }};

    Otras funciones de hash ms sofisticadas emplean sumasponderadas o transformaciones no lineales (p.e. elevan alcuadrado el nmero representado por los k bytes centrales dela clave).

  • Resolucin de colisiones

    Existen dos grandes familias de estrategias de resolucin decolisiones. Por razones histricas, que no lgicas, se utilizanlos trminos dispersin abierta (ing: open hashing) ydireccionamiento abierto (ing: open addressing).Estudiaremos dos ejemplos representativos: tablas abiertascon sinnimos encadenados (ing: separate chaining) y sondeolineal (ing: linear probing).

  • Sinnimos encadenados

    En las tablas con sinnimos encadenados cada entrada de latabla da acceso a una lista enlazada de sinnimos.template class Diccionario {

    ...private:

    struct nodo {Clave _k;Valor _v;nodo* _sig;// constructora de la clase nodonodo(const Clave& k, const Valor& v,

    nodo* sig = NULL);};nodo** _Thash; // tabla de punteros a nodoint _M; // tamao de la tablaint _n; // numero de elementosdouble _alfa_max; // factor de carga

    nodo* busca_sep_chain(const Clave& k) const throw();void inserta_sep_chain(const Clave& k,

    const Valor& v) throw(error);void elimina_sep_chain(const Clave& k) throw();

    };

  • 06

    10

    12

    13

    19

    23

    25

    41730

    0

    2

    5

    7

    1

    3

    4

    6

    8

    9

    10

    11

    12

    M = 13

    h (x) = x mod MX = { 0, 4, 6, 10, 12, 13, 17, 19, 23, 25, 30}

  • Para la insercin, se accede a la lista correspondientemediante la funcin de hash, y se recorre para determinar si yaexista o no un elemento con la clave dada. En el primer caso,se modifica la informacin asociada; en el segundo se aadeun nuevo nodo a la lista con el nuevo elemento.Puesto que estas listas de sinnimos contienen por lo generalpocos elementos lo ms efectivo es efectuar las insercionespor el inicio (no hace falta usar fantasmas, cierre circular, etc.).Existen variantes en que las listas de sinnimos estnordenadas o se sustituyen por rboles de bsqueda, pero nocomportan una ventaja real en trminos prcticos (el costeasinttico terico es mejor, pero el criterio no es efectivo ya queel nmero de elementos involucrado es pequeo).

  • La bsqueda es tambin simple: se accede a la lista apropiadamediante la funcin de hash y se realiza un recorridosecuencial de la lista hasta que se encuentra un nodo con laclave dada o la lista se ha examinado sin xito.

  • template

    void Diccionario::inserta(const Clave& k,const Valor& v) throw(error) {

    if (_n / _M > _alfa_max)// la tabla est demasiado llena ... enviar un error// o redimensionar

    inserta_sep_chain(k, v);}

    template

    void Diccionario::inserta_sep_chain(const Clave& k, const Valor& v) throw(error) {

    int i = hash(k);nodo* p = _Thash[i];

    // buscamos en la lista de sinnimoswhile (p != NULL and p -> _k != k)

    p = p -> _sig;

    // lo insertamos al principio// si no estabaif (p == NULL) {

    _Thash[i] = new nodo(k, v, _Thash[i]);++_n;

    }else

    p -> _v = v;}

  • template

    void Diccionario::busca(const Clave& k,bool& esta, Valor& v) const throw(error) {

    nodo* p = buscar_sep_chain(k);if (p == NULL)

    esta = false;else {

    esta = true;v = p -> _v;

    }}

    template

    Diccionario::nodo*Diccionario::busca_sep_chain(const

    Clave& k) const throw() {

    int i = hash(k);nodo* p = _Thash[i];

    // buscamos en su lista de sinnimoswhile (p != NULL and p -> _k != k)

    p = p -> _sig;

    return p;}

  • template

    void Diccionario::elimina(constClave& k) throw() {

    eliminar_sep_chain(k);}

    template

    void Diccionario::eliminar_sep_chain(const Clave& k) throw() {

    int i = hash(k);nodo* p = _Thash[i];nodo* ant = NULL; // apuntara al anterior de p

    // buscamos en su lista de sinnimoswhile (p != NULL and p -> _k != k) {

    ant = p;p = p -> _sig;

    }

    // si p != NULL, lo quitamos de la// lista teniendo en cuenta que puede// ser el primeroif (p != NULL) {

    --_n;if (ant != NULL)

    ant -> _sig = p -> _sig;else

    _Thash[i] = p -> _sig;delete p;

    }}

  • Sea n el nmero de elementos almacenados en la tabla conencadenamiento de sinnimos. En promedio, cada lista tendr = n/M elementos y el coste de bsquedas (con y sin xito),de inserciones y borrados ser proporcional a . Si es unvalor razonablemente pequeo entonces podemos considerarque el coste promedio de todas las operaciones sobre la tablade hash es (1), ya que es constante.

  • Al valor se le denomina factor de carga (ing: load factor). Otravariantes de hash abierto almacenan los sinnimos en unazona particular de la propia tabla, la llamada zona deexcedentes (ing: cellar). Las posiciones de esta zona no sondireccionables, es decir, la funcin de hash nunca toma susvalores.

  • Direccionamiento abierto

    En las estrategias de direccionamiento abierto los sinnimosse almacenan en la tabla de hash, en la zona dedireccionamiento. Bsicamente, tanto en bsquedas como eninserciones se realiza una secuencia de sondeos quecomienza en la posicin i0 = h(k) y a partir de ah contina eni1, i2, . . . hasta que encontramos una posicin ocupada por unelemento cuya clave es k (bsqueda con xito), una posicinlibre (bsqueda sin xito, insercin) o hemos explorado la tablaen su totalidad. Las distintas estrategias varan segn lasecuencia de sondeos que realizan. La ms sencilla de todases el sondeo lineal: i1 = i0 + 1, i2 = i1 + 1, . . ., realizndose losincrementos mdulo M .

  • Sondeo lineal

    template

    class Diccionario {...

    private:struct nodo {

    Clave _k;Valor _v;bool _libre;// constructora de la clase nodonodo(const Clave& k, const Valor& v, bool libre = true);

    };nodo* _Thash; // tabla de nodosint _M; // tamao de la tablaint _n; // numero de elementosdouble _alfa_max; // factor de carga

    int busca_linear_probing(const Clave& k) const throw();void inserta_linear_probing(const Clave& k,

    const Valor& v) throw(error);void elimina_linear_probing(const Clave& k) throw();};

  • 02

    5

    7

    1

    3

    4

    6

    8

    9

    10

    11

    12 ocupado12

    10

    6

    4

    0

    libre

    17

    19

    ocupado

    ocupado

    ocupado

    ocupado

    ocupado

    ocupado

    ocupado

    ocupado

    libre

    libre

    libre

    23

    130

    2

    5

    7

    1

    3

    4

    6

    8

    9

    10

    11

    12 ocupado12

    10

    6

    4

    0

    libre

    17

    19

    ocupado

    ocupado

    ocupado

    ocupado

    ocupado

    ocupado

    ocupado

    ocupado

    libre

    23

    13

    25

    30

    ocupado

    ocupado

    0

    2

    5

    7

    1

    3

    4

    6

    8

    9

    10

    11

    12 12

    10

    4

    0

    6

    M = 13

    h (x) = x mod MX = { 0, 4, 6, 10, 12, 13, 17, 19, 23, 25, 30}

    (incremento 1)

    + {0, 4, 6, 10, 12} + {13, 17, 19, 23} + {25, 30}

  • // Solo se invoca si hay al menos un sitio// no ocupado en la tabla: _n < _Mtemplate void

    Diccionario::inserta_linear_probing(const Clave& k, const Valor& v) throw(error) {

    int i = hash(k);while (not _Thash[i]._libre and _Thash[i]._k != k)

    i = (i + 1) % _M;}_Thash[i]._k = k; _Thash[i]._v = v;if (_Thash[i]._libre) ++_n;_Thash[i]._libre = false;

    }

  • template

    int Diccionario::busca(const Clave& k,bool& esta, Valor& v) const throw(error) {

    int i = busca_linear_probing(k);

    if (not _Thash[i]._libre and _Thash[i]._k == k) {esta = true;v = _Thash[i]. _v;

    }else

    esta = false;}

    template

    int Diccionario::busca_linear_probing(const Clave& k) const throw() {

    int i = hash(k);int vistos = 0; // para asegurarnoss una pasada,

    // no ms; es inncesario si// n < _M ya que entonces habr// una posicin i tal que _libre == true

    while (not _Thash[i]._libre and _Thash[i]._k != kand vistos < _M) {

    ++vistos;i = (i + 1) % _M;

    }return i;

    }

  • El borrado en tablas de direccionamiento abierto es algo mscomplicado. No basta con marcar la posicin correspondientecomo libre ya que una bsqueda posterior podra no hallar elelemento buscado aunque ste se encontrase en la tabla. Seha de recorrer los elementos que siguen al que se borra en elmismo clster desplazando al lugar desocupado a todoelemento cuya posicin inicial de hash preceda o sea igual a laposicin desocupada. Para ello necesitamos la funcindispl(j, i) que nos da la distancia entre las posiciones j e i enel orden cclico: si j > i hay que dar la vuelta pasando por laposicin _M 1 y regresando a la posicin 0.int displ(j, i, M) {

    if (i >= j)return i - j;

    elsereturn M + (i - j);

    }

  • // asumimos para simplificar que _n < _M

    template

    int Diccionario::elimina_linear_probing(const Clave& k) const throw() {

    int i = busca_linear_probing(k);

    if (not _Thash[i]._libre) {// _Thash[i] es el elemento que se quiere eliminarint free = i; i = (i + 1) % _M; int d = 1;while (not _Thash[i]._libre) {

    int i_home = hash(_Thash[i]._k);if (displ(i_home, i, _M) >= d) {

    _Thash[free] = _Thash[i]; free = i; d = 0;}i = (i + 1) % _M; ++d;

    }_Thash[free]._libre = true; --_n;

    }}

  • El sondeo lineal ofrece algunas ventajas ya que los sinnimostienden a encontrarse en posiciones consecutivas, lo queresulta ventajoso en implementaciones sobre memoria externa.Generalmente se usara junto a alguna tcnica de bucketing:cada posicin de la tabla es capaz de albergar b > 1 elementos.Por otra parte tiene algunas serias desventajas y surendimiento es especialmente sensible al factor de carga = n/M .

  • Si es prximo a 1 (tabla llena o casi llena) el rendimiento sermuy pobre. Si < 1 el coste de las bsquedas con xito (ymodificaciones) ser proporcional a

    1

    2

    (1 +

    1

    1 )

    y el de bsquedas sin xito e inserciones ser proporcional a

    1

    2

    (1 +

    1

    (1 )2).

  • Un fenmeno indeseable que se acenta cuando es prximoa 1 es el apiamiento (ing: clustering). Muchos elementos nopueden ocupar su posicin preferida al estar ocupada porotro elemento que no tiene porqu ser un sinnimo (invasor).Grupos de sinnimos ms o menos dispersos acabanfundindose en grandes clusters y cuando buscamos unacierta clave tenemos que examinar no slo sus sinnimos sinoun buen nmero de claves que no tienen relacin alguna con laclave buscada.

  • Redimensionamiento

    Muchos lenguajes de programacin permiten crear tablasfijando su tamao en tiempo de ejecucin. Entonces es posibleutilizar la llamada tcnica de redimensionamiento (ing:resizing). Si el factor de carga es muy alto, superando un ciertovalores umbral, se reclama a la memoria dinmica una tablacuyo tamao es aproximadamente el doble del tamao de latabla en curso y se reinserta toda la informacin de la tabla encurso sobre la nueva tabla.

  • Para ello se recorre secuencialmente la tabla en curso y cadauno de los elementos presentes se inserta por el procedimientohabitual en la nueva tabla, usando una funcin de hash distinta,obviamente. La operacin de redimensionamiento requieretiempo proporcional al tamao de la tabla en curso (y por tanto(n)); pero hay que hacerla slo muy de vez en cuando. Elredimensionamiento permite que el diccionario crezca sinlmites prefijados, garantizando un buen rendimiento de todaslas operaciones y sin desperdiciar demasiada memoria. Lamisma tcnica puede aplicarse a la inversa, para evitar que elfactor de carga sea excesivamente bajo (desperdicio dememoria).Puede demostrarse que, aunque una operacin individualpodra llegar tener coste (n), una secuencia de noperaciones de actualizacin tendr coste total (n).

  • 1 Anlisis de Algoritmos

    2 Divide y vencers

    3 rboles binarios de bsqueda

    4 rboles balanceados (AVLs)

    5 Tablas de Hash

    6 Colas de prioridad

    7 Grafos y Recorridos

  • Una cola de prioridad (cat: cua de prioritat; ing: priority queue)es una coleccin de elementos donde cada elemento tieneasociado un valor susceptible de ordenacin denominadoprioridad.Una cola de prioridad se caracteriza por admitir inserciones denuevos elementos y la consulta y eliminacin del elemento demnima (o mxima) prioridad.

  • template class ColaPrioridad {public:

    ...// Aade el elemento x con prioridad p a la cola de// prioridad.void inserta(cons Elem& x, const Prio& p) throw(error)

    // Devuelve un elemento de mnima prioridad en la cola de// prioridad. Se lanza un error si la cola est vaca.Elem min() const throw(error);

    // Devuelve la mnima prioridad presente en la cola de// prioridad. Se lanza un error si la cola est vaca.Prio prio_min() const throw(error);

    // Elimina un elemento de mnima prioridad de la cola de// prioridad. Se lanza un error si la cola est vaca.void elim_min() throw(error);

    // Devuelve cierto si y slo si la cola est vaca.bool vacia() const throw();};

  • // Tenemos dos arrays Peso y Simb con los pesos atmicos// y smbolos de n elementos qumicos,// p.e., Simb[i] = "C" y Peso[i] = 12.2.// Utilizamos una cola de prioridad para ordenar la// informacin de menor a mayor smbolo en orden alfabticoColaPrioridad P;for (int i = 0; i < n; ++i)

    P.inserta(Peso[i], Simb[i]);int i = 0;while(not P.vacia()) {

    Peso[i] = P.min();Simb[i] = P.prio_min();++i;P.elim_min();

    }

  • Se puede usar una cola de prioridad para hallar el k-simoelemento de un vector no ordenado. Se colocan los k primeroselementos del vector en una max-cola de prioridad y acontinuacin se recorre el resto del vector, actualizando la colade prioridad cada vez que el elemento es menor que el mayorde los elementos de la cola, eliminando al mximo e insertandoel elemento en curso.

  • Muchas de las tcnicas empleadas para la implementacinde diccionarios puede usarse para implementar colas deprioridad (no las tablas de hash ni los tries)P.e., con rboles binarios de bsqueda equilibrados sepuede conseguir coste O(log n) para inserciones yeliminaciones

  • DefinicinUn montculo (ing: heap) es un rbol binario tal que

    1 todos las hojas (subrboles son vacos) se sitan en losdos ltimos niveles del rbol.

    2 en el antepenltimo nivel existe a lo sumo un nodo internocon un slo hijo, que ser su hijo izquierdo, y todos losnodos a su derecha en el mismo nivel son nodos internossin hijos.

    3 el elemento (su prioridad) almacenado en un nodocualquiera es mayor (menor) o igual que los elementosalmacenados en sus hijos izquierdo y derecho.

  • Se dice que un montculo es un rbol binario quasi-completodebido a las propiedades 1-2. La propiedad 3 se denominaorden de montculo, y se habla de max-heaps o min-heapssegn que los elementos sean que sus hijos.

  • Proposicin1 El elemento mximo de un max-heap se encuentra en la

    raz.2 Un heap de n elementos tiene altura

    h = dlog2(n+ 1)e.

    La consulta del mximo es sencilla y eficiente pues bastaexaminar la raz.

  • Cmo eliminar el mximo?1 Ubicar al ltimo elemento del montculo (el del ltimo nivel

    ms a la derecha) en la raz, sustituyendo al mximo2 Reestablecer el invariante (orden de heap) hundiendo la

    raz.El mtodo hundir intercambia un nodo dado con el mayor desus dos hijos si el nodo es menor que alguno de ellos, y repeteeste paso hasta que el invariante de heap se ha reestablecido

  • Cmo aadir un nuevo elemento?

    1 Colocar el nuevo elemento como ltimo elemento delmontculo, justo a la derecha del ltimo o como primero deun nuevo nivel

    2 Reestablecer el orden de montculo flotando el elementorecin aadido

    En el mtodo flotar el nodo dado se compara con su nodopadre y se realiza el intercambio si ste es mayor que el padre,iterando este paso mientras sea necesario

  • Puesto que la altura del heap es (log n) el coste deinserciones y eliminaciones ser O(log n).Se puede implementar un heap mediante memoria dinmica,con apuntadores al hijo izquierdo y derecho y tambin al padreen cada nodoPero es mucho ms fcil y eficiente implementar los heapsmediante un vector. No se desperdicia demasiado espacio yaque el heap es quasi-completo; en caso necesario puedeusarse el redimensionado

  • Reglas para representar un heap en vector:1 A[1] contiene la raz.2 Si 2i n entonces A[2i] contiene al hijo izquierdo del

    elemento en A[i] y si 2i+ 1 n entonces A[2i+ 1]contiene al hijo derecho de A[i]

    3 Si i 2 entonces A[i/2] contiene al padre de A[i]

  • Las reglas anteriores implican que los elementos del heap seubican en posiciones consecutivas del vector, colocando la razen la primera posicin y recorriendo el rbol por niveles, deizquierda a derecha.template class ColaPrioridad {public:...

    private:// la componente 0 no se usa; el constructor de la clase// inserta un elemento ficticiovector h;

    int nelems;

    void flotar(int j) throw();void hundir(int j) throw();

    };

  • template bool ColaPrioridad::vacia() const throw() {

    return nelems == 0;}

    template Elem ColaPrioridad::min() const throw(error) {

    if (nelems