vi colas de prioridad - cs.upc.edups/downloads/matdoc/transparencias/ps/ps... · tambi´en se usan...

Post on 23-Sep-2018

217 Views

Category:

Documents

0 Downloads

Preview:

Click to see full reader

TRANSCRIPT

VI Colas de prioridad

Una cola de prioridad (cat: cua de prioritat; ing:priority queue) es una coleccion de elementos dondecada elemento tiene asociado un valor susceptiblede ordenacion denominado prioridad. Una cola deprioridad se caracteriza por admitir inserciones denuevos elementos y la consulta y eliminacion delelemento de mınima prioridad. Analogamente sepueden definir colas de prioridad que admitan laconsulta y eliminacion del elemento de maximaprioridad en la coleccion.

Colas de prioridad 323

En esta especificacion asumimos que el tipo Prio

ofrece una relacion de orden total <. Por otra parte,puede darse el caso de que existan varios elementoscon igual prioridad y en dicho caso es irrelevantecual de los elementos es devuelto por min oeliminado por elim min. En ocasiones se utilizauna operacion prio min que devuelve la mınimaprioridad.

Colas de prioridad 324

template <typename Elem , typename Prio >

class ColaPrioridad

public:

// Constructora , crea una cola vacıa.

ColaPrioridad () throw(error);

// Destructora , constr. por copia y asignacion

~ColaPrioridad () throw ();

ColaPrioridad(const ColaPrioridad& P) throw(error);

ColaPrioridad& operator =( const ColaPrioridad P) throw(error);

// A~nade 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 mınima prioridad en la cola de

// prioridad. Se lanza un error si la cola esta vacıa.

Elem min() const throw(error );

// Devuelve la mınima prioridad presente en la cola de

// prioridad. Se lanza un error si la cola esta vacıa.

Prio prio_min () const throw(error );

// Elimina un elemento de mınima prioridad de la cola de

// prioridad. Se lanza un error si la cola esta vacıa.

void elim_min () throw(error );

// Devuelve cierto si y solo si la cola esta vacıa.

bool vacia() const throw ();

;

Colas de prioridad 325

Las colas de prioridad tienen multiples usos. Confrecuencia se emplean para implementar algoritmosvoraces. Este tipo de algoritmos suele tener unaiteracion principal, y una de las tareas a realizar encada una de dichas iteraciones es seleccionar unelemento de entre varios que minimiza (o maximiza)un cierto criterio de optimalidad local. El conjuntode elementos entre los que se ha de efectuar laseleccion es frecuentemente dinamico y admitirinserciones eficientes. Algunos de estos algoritmosincluyen:

Algoritmos de Kruskal y Prim para el calculo delarbol de expansion mınimo de un grafo etiquetado.

Algoritmo de Dijkstra para el calculo de caminosmınimos en un grafo etiquetado.

Construccion de codigos de Huffman (codigos bi-narios de longitud media mınima).

Otra tarea para la que obviamente podemos usaruna cola de prioridad es para ordenar.

Colas de prioridad 326

// Tenemos dos arrays Peso y Simb con los pesos atomicos

// y sımbolos de n elementos quımicos ,

// p.e., Simb[i] = "C" y Peso[i] = 12.2.

// Utilizamos una cola de prioridad para ordenar la

// informacion de menor a mayor sımbolo en orden alfabetico

ColaPrioridad <double , string > P;

for (int i = 0; i < n; ++i)

P.inserta(Peso[i], Simb[i]);

int i = 0;

while (! P.vacia ())

Peso[i] = P.min ();

Simb[i] = P.prio_min ();

++i;

P.elim_min ();

Tambien se usan colas de prioridad para hallar elk-esimo elemento de un vector no ordenado. Secolocan los k primeros elementos del vector en unamax-cola de prioridad y a continuacion se recorre elresto del vector, actualizando la cola de prioridadcada vez que el elemento es menor que el mayor delos elementos de la cola, eliminando al maximo einsertando el elemento en curso.

Colas de prioridad 327

La mayorıa de tecnicas empleadas en laimplementacion de diccionarios puede usarse paraimplementar colas de prioridad. La excepcion laconstituyen las tablas de dispersion y los tries. Sepuede usar una lista ordenada por prioridad. Tantola consulta como la eliminacion del mınimo sontriviales y su coste es Θ(1). Pero las insercionestienen coste lineal, tanto en caso peor como enpromedio.

Otra opcion es usar un arbol de busqueda(equilibrado o no), utilizando como criterio de ordende sus elementos las correspondientes prioridades.Hay que modificar ligeramente el invariante derepresentacion para admitir y tratar adecuadamentelas prioridades repetidas. Puede garantizarse en talcaso que todas las operaciones (inserciones,consultas, eliminaciones) tienen coste Θ(logn): encaso peor si el BST es equilibrado (AVL), y en casomedio si no lo es.

Colas de prioridad 328

Tambien pueden usarse skip lists con identicorendimiento al que ofrecen los BSTs (aunque loscostes son logarıtmicos solo en caso medio, dichocaso medio no depende ni del orden de insercion nide la existencia de pocas prioridades repetidas).

Si el conjunto de posibles prioridades es reducidoentonces sera conveniente emplear una tabla delistas, correspondiendo cada lista a una prioridad ointervalo reducido de prioridades.

En lo que resta estudiaremos una tecnica especıficapara la implementacion de colas de prioridad basadaen los denominados montıculos.

Colas de prioridad 329

Un montıculo (ing: heap) es un arbol binario tal que

1. todos las hojas (subarboles son vacıos) se situanen los dos ultimos niveles del arbol.

2. en el antepenultimo nivel existe a lo sumo unnodo interno con un solo hijo, que sera su hijoizquierdo, y todos los nodos a su derecha en elmismo nivel son nodos internos sin hijos.

3. el elemento (su prioridad) almacenado en un no-do cualquiera es mayor (menor) o igual que loselementos almacenados en sus hijos izquierdo yderecho.

Se dice que un montıculo es un arbol binarioquasi-completo debido a las propiedades 1-2. Lapropiedad 3 se denomina orden de montıculo, y sehabla de max-heaps o min-heaps segun que loselementos sean ≥ o ≤ ques sus hijos. En lo sucesivosolo consideraremos max-heaps.

Colas de prioridad 330

76

72 34

59 63 17 29

37 33 29

nivel 0

nivel 1

nivel 2

nivel 3

nivel 4

altura = 4

hojas

n = 10

Colas de prioridad 331

De las propiedades 1-3 se desprenden dosconsecuencias importantes:

1. El elemento maximo se encuentra en la raız.

2. Un heap de n elementos tiene altura

h = ⌈log2(n+1)⌉.

La consulta del maximo es sencilla y eficiente puesbasta examinar la raız.

Colas de prioridad 332

Como eliminar el maximo? Un procedimiento que seemplea a menudo consiste en ubicar al ultimoelemento del montıculo (el del ultimo nivel mas a laderecha) en la raız, sustituyendo al maximo; ellogarantiza que se preservan las propiedades 1-2. Perocomo la propiedad 3 deja eventualmente desatisfacerse, debe reestablecerse el invariante para locual se emplea un procedimiento privadodenominado hundir.

Este consiste en intercambiar un nodo con el mayorde sus dos hijos si el nodo es menor que alguno deellos, y repetir este paso hasta que el invariante sehaya reestablecido.

Colas de prioridad 333

34

59 63 17 29

37 33 29

32

72

34

59 63 17 29

37 33 29

72

32

34

59 17 29

37 33 29

72

63

32

Colas de prioridad 334

Como anadir un nuevo elemento? Una posibilidadconsiste en colocar el nuevo elemento como ultimoelemento del montıculo, justo a la derecha delultimo o como primero de un nuevo nivel. Para ellohay que localizar al padre de la primera hoja ysustituirla por un nuevo nodo con el elemento ainsertar. A continuacion hay que reestablecer elorden de montıculo empleando para ello unprocedimiento flotar, que trabaja de manerasimilar pero a la inversa de hundir: el nodo encurso se compara con su nodo padre y se realiza elintercambio si este es mayor que el padre, iterandoeste paso mientras sea necesario.

Colas de prioridad 335

Puesto que la altura del heap es Θ(logn) el coste deinserciones y eliminaciones es O(logn). Se puedeimplementar un heap mediante memoria dinamica.La representacion elegida debe incluir apuntadoresal hijo izquierdo y derecho y tambien al padre, yresolver de manera eficaz la localizacion del ultimoelemento y del padre de la primera hoja.

Una alternativa atractiva es la implementacion deheaps mediante un vector. No se desperdiciademasiado espacio ya que el heap esquasi-completo. Las reglas para representar loselementos del heap en un vector son simples:

1. A[1] contiene la raız.

2. Si 2i≤ n entonces A[2i] contiene al hijo izquierdodel elemento en A[i] y si 2i + 1 ≤ n entoncesA[2i+1] contiene al hijo derecho de A[i].

3. Si idiv2≥ 1 entonces A[idiv2] contiene al padrede A[i].

Colas de prioridad 336

Notese que las reglas anteriores implican que loselementos del montıculo se ubican en posicionesconsecutivas del vector, colocando la raız en laprimera posicion y recorriendo el arbol por niveles,de izquierda a derecha.

template <typename Elem , typename Prio >

class ColaPrioridad

public:

...

private:

static const int MAX_ELEM = ...;

int nelems; // numero de elementos en el heap

// array de MAX_ELEMS pares <Elem , Prio >,

// la componente 0 no se usa

pair <Elem , Prio > h[MAX_ELEM + 1];

void flotar(int j) throw ();

void hundir(int j) throw ();

;

Colas de prioridad 337

template <typename Elem , typename Prio >

bool ColaPrioridad <Elem ,Prio >:: vacia () const throw ()

return nelems == 0;

template <typename Elem , typename Prio >

void ColaPrioridad <Elem ,Prio >:: inserta(cons Elem& x,

cons Prio& p) throw(error)

if (nelems == MAX_ELEMS) throw error(ColaLlena );

++ nelems;

h[nelems] = make_pair(x, p);

flotar(nelems );

template <typename Elem , typename Prio >

Elem ColaPrioridad <Elem ,Prio >:: min() const throw(error)

if (nelems == 0) throw error(ColaVacia );

return h[1]. first;

template <typename Elem , typename Prio >

Prio ColaPrioridad <Elem ,Prio >:: prio_min () const throw(error)

if (nelems == 0) throw error(ColaVacia );

return h[1]. second;

template <typename Elem , typename Prio >

void ColaPrioridad <Elem ,Prio >:: elim_min () const throw(error)

if (nelems == 0) throw error(ColaVacia );

swap(h[1], h[nelems ]);

--nelems;

hundir (1);

Colas de prioridad 338

// Version recursiva.

// Hunde el elemento en la posicion j del heap

// hasta reestablecer el orden del heap; por

// hipotesis los subarboles del nodo j son heaps.

// Coste: O(log(n/j))

template <typename Elem , typename Prio >

void ColaPrioridad <Elem ,Prio >:: hundir(int j) throw ()

// si j no tiene hijo izquierdo , hemos terminado

if (2 * j > nelems) return;

int minhijo = 2 * j;

if (minhijo < nelems &&

h[minhijo ]. second > h[minhijo + 1]. second)

++ minhijo;

// minhijo apunta al hijo de minima prioridad de j

// si la prioridad de j es mayor que la de su menor hijo

// intercambiar y seguir hundiendo

if (h[j]. second > h[minhijo ]. second)

swap(h[j], h[minhijo ]);

hundir(minhijo );

Colas de prioridad 339

// Version iterativa.

// Hunde el elemento en la posicion j del heap

// hasta reestablecer el orden del heap; por

// hipotesis los subarboles del nodo j son heaps.

// Coste: O(log(n/j))

template <typename Elem , typename Prio >

void ColaPrioridad <Elem ,Prio >:: hundir(int j) throw ()

bool fin = false;

while (2 * j <= nelems && !fin)

int minhijo = 2 * j;

if (minhijo < nelems &&

h[minhijo ]. second > h[minhijo + 1]. second)

++ minhijo;

if (h[j]. second > h[minhijo ]. second)

swap(h[j], h[minhijo ]);

j = minhijo;

else

fin = true;

Colas de prioridad 340

// Flota al nodo j hasta reestablecer el orden del heap;

// todos los nodos excepto el j satisfacen la propiedad

// de heap

// Coste: O(log j)

template <typename Elem , typename Prio >

void ColaPrioridad <Elem ,Prio >:: flotar(int j) throw ()

// si j es la raız , hemos terminado

if (j == 1) return;

int padre = j / 2;

// si el padre tiene mayor prioridad

// que j, intercambiar y seguir flotando

if (h[j]. second < h[padre ]. second)

swap(h[j], h[padre ]);

flotar(padre );

Colas de prioridad 341

Heapsort

Heapsort (Williams, 1964) ordena un vector de n

elementos construyendo un heap con los n

elementos y extrayendolos, uno a uno del heap acontinuacion. El propio vector que almacena a los n

elementos se emplea para construir el heap, demodo que heapsort actua in-situ y solo requiere unespacio auxiliar de memoria constante. El coste deeste algoritmo es Θ(n logn) (incluso en caso mejor)si todos los elementos son diferentes.

En la practica su coste es superior al de quicksort,ya que el factor constante multiplicativo del terminon logn es mayor.

Colas de prioridad 342

// Ordena el vector v[1..n]

// (v[0] no se usa)

// de Elem’s de menor a mayor

template <typename Elem >

void heapsort(Elem v[], int n)

crea_max_heap(v, n);

for (int i = n; i > 0; --i)

// saca el mayor elemento del heap

swap(v[1], v[i]);

// hunde el elemento de indice 1

// para reestablecer un max -heap en

// el subvector v[1..i-1]

hundir(v, i-1, 1);

<_ <_1 k iA[1] = max A[k]

<_ <_ <_ <_A[1] A[i+1] A[i+2] ... A[n]

1

ordenado

n

heap

A

i

Colas de prioridad 343

72 63 34 59 29 17 29 37 33 76

34

59 17 29

37 33

72

63

29

i = 9

76 72 34 59 63 17 29 37 33 29

i = 10

34

17 29

37 33 29

76

59 63

72

29 72 34 59 63 17 29 37 33 76

i = 9

34

59 63 17 29

37 33

72

29

Colas de prioridad 344

33 59 34 37 29 17 29 63 72 76

34

17 29

59

2937

33

i = 7

59 37 34 33 29 17 29 63 72 76

i = 7

34

17 292933

37

59

33 63 34 59 29 17 29 37 72 76

34

17 29

37

59

33

63

29

i = 8

Colas de prioridad 345

// Da estructura de max -heap al

// vector v[1..n] de Elem’s; aquı

// cada elemento se identifica con su

// prioridad

template <typename Elem >

void crea_max_heap(Elem v[], int n)

for (int i = n/2; i > 0; --i)

hundir(v, n, i);

Colas de prioridad 346

13

2 5

27 8 15 10

4

13 2 5 27 8 15 10 4

i = 8

satisfacen la propiedad de heap

13

2

27 8 10

4

5

15

hundir(A, 8, 4)

hundir(A, 8, 3)

3

4

2

5 6 7

8

1

8 105

15

hundir(A, 8, 2)

hundir(A, 8, 1)

27

2

4

13

1

2 3

4 5 6 7

8

Colas de prioridad 347

Sea H(n) el coste en caso peor de heapsort y B(n)el coste de crear el heap inicial. El coste en casopeor de hundir(v, i−1,1) es O(log i) y por lo tanto

H(n) = B(n)+i=n

∑i=1

O(log i)

= B(n)+O

(

∑1≤i≤n

log2 i

)

= B(n)+O(log(n!)) = B(n)+O(n logn)

Un analisis “grueso” de B(n) indica queB(n) = O(n logn) ya que hay Θ(n) llamadas ahundir, cada una de las cuales tiene coste O(logn).Podemos concluir por tanto que H(n) = O(n logn).No es difıcil construir una entrada de tamano n talque H(n) = Ω(n logn) y por tanto H(n) = Θ(n logn)en caso peor. La demostracion de que el coste deheapsort en caso mejor3 es tambien Θ(n logn) esbastante mas complicada.

3Siendo todos los elementos distintos.

Colas de prioridad 348

Por otra parte, la cota dada para B(n) podemosrefinarla ya que

B(n) = ∑1≤i≤⌊n/2⌋

O(log(n/i))

= O

(

logn

n/2

(n/2)!

)

= O

(

log(2e)n/2)

= O(n).

Puesto que B(n) = Ω(n), podemos afirmar queB(n) = Θ(n).

Otra forma de demostrar que B(n) es lineal consisteen razonar del siguiente modo: Seah = ⌈log2(n+1)⌉ la altura del heap. En el nivelh−1− k hay como mucho

2h−1−k <n+1

2k

nodos y cada uno de ellos habra de hundirse en casopeor hasta el nivel h−1; eso tiene coste O(k).

Colas de prioridad 349

Por lo tanto,

B(n) = ∑0≤k≤h−1

O(k)n+1

2k

= O

(

n ∑0≤k≤h−1

k

2k

)

= O

(

n ∑k≥0

k

2k

)

= O(n),

ya que

∑k≥0

k

2k= 2.

En general, si r < 1,

∑k≥0

k · rk =r

(1− r)2.

Colas de prioridad 350

Aunque globalmente H(n) = Θ(n logn), esinteresante el analisis detallado de B(n). Porejemplo, utilizando un min-heap podemos hallar losk menores elementos de un vector (y en particular elk-esimo) con coste:

S(n,k) = B(n)+ k ·O(logn)

= O(n+ k logn).

y si k = O(n/ logn) entonces S(n,k) = O(n).

Colas de prioridad 351

top related