ELO320 Estructuras de Datos y Algoritmos Heap & HeapSort...
Transcript of ELO320 Estructuras de Datos y Algoritmos Heap & HeapSort...
12: Heap 1
ELO320 Estructuras de Datos y Algoritmos
Heap & HeapSort
Tomás Arredondo Vidal
Este material está basado en:
Robert Sedgewick, "Algorithms in C", (third edition), Addison-Wesley, 2001 Thomas Cormen et al, “Algorithms”, (eighth edition), MIT Press, 1992. material del curso ELO320 del Prof. Leopoldo Silva material en el sitio http://es.wikipedia.org
12: Heap 2
12-Heap
12.1 Definicion12.2 Operaciones12.3 Heap12.4 Heapsort
12: Heap 3
Definiciones
Se desea disponer de una estructura de datos y encontrar los algoritmos asociados que sean eficientes para seleccionar un elemento de un grupo.
Uno de los valores de la información agrupada en la estructura se denomina prioridad. Es un valor entero, y por tradición, el menor valor entero está asociado a la estructura que tiene mayor prioridad.
Prioridad se entiende como sinónimo de lo más importante. Puede haber varias estructuras con igual prioridad; en este sentido no son conjuntos.
12: Heap 4
12-Heap
12.1 Definicion12.2 Operaciones12.3 Heap12.4 Heapsort
12: Heap 5
Operaciones
Se definen dos operaciones: – Insertar un elemento en el conjunto, y – otra que busca y descarta (selecciona) el elemento
con valor menor del campo prioridad. Pueden haber nodos con la misma prioridad. Es necesario encontrar una nueva estructura, ya que
las vistas anteriormente tienen limitaciones. Una lista ordenada en forma ascendente por la
prioridad, permite seleccionar el mínimo con costo O(1).
12: Heap 6
Operaciones
La inserción, manteniendo en orden tiene costo promedio O(n); si encuentra el lugar para insertar a la primera es costo 1; pero si debe recorrer toda la lista, debe efectuar n comparaciones; en promedio debe efectuar un recorrido de n/2 pasos.
Una lista no ordenada, tiene costo de inserción O(1), y búsqueda O(n).
En ambos casos la repetición de n operaciones sobre la estructura da origen a complejidad O(n2).
Estudiaremos usar la estructura de un árbol binario modificada, ya que ésta garantiza operaciones de inserción y descarte de complejidad O( log2 (n) ).
12: Heap 7
12-Heap
12.1 Definicion12.2 Operaciones12.3 Heap12.4 Heapsort
12: Heap 8
Altura y Complejidad La altura, es importante para la complejidad, ya que
define el número de nodos a revisar en una trayectoria.ta las hojas.
Se tienen para árboles de altura tres, los casos con n=4, 5, y 6. Es decir para n >= 23-1 y n < 23-1
Para árboles incompletos de altura h se tienen árboles con nodos en el intervalo: 23-1 <= n < 23-1
12: Heap 9
Altura y Complejidad II Lo que implica: log2(n+1) < h <= log2(n) + 1 Se puede encontrar un n0 y un c tal que c*log2(n+1)
sea una cota superior para h. Entonces: h = O(log2(n) )
12: Heap 10
Heap Escogemos un árbol binario parcialmente completo, lo
más balanceado posible; que podemos definir como un árbol que en su nivel más bajo (cerca de las hojas) cumple con la condición de que las hojas que le faltan están a la derecha de las presentes.
Además debemos definirlo como parcialmente ordenado, ya que es posible tener elementos repetidos. Esta condición la logramos estableciendo, que los nodos queden ordenados según:
valor (hijo) >= valor (padre)
12: Heap 11
Heap II
Dado que el valor(hijo) >= valor (padre) Si el elemento a insertar tiene un valor menor que su
padre, debe ascender por intercambio. Si se descarta la raíz, para mantener la estructura,
se reemplaza la posición vacante en la raíz por la hoja ubicada más a la derecha, y si ésta es mayor que los hijos se la hace descender por intercambio.
Ambas operaciones tienen complejidad O(a), donde a es la altura del árbol. Esta estructura se denomina heap. Que equivale, en español, a grupo de cosas unas al lado de otras (montón, colección).
12: Heap 12
Heap III Se visualiza como un árbol, pero el valor de cualquier
nodo es menor o igual a los valores de los nodos hijos. Se agregan nodos primero en el sub-árbol izquierdo, y
luego en el derecho, manteniendo un árbol binario lo más balanceado posible.
De esta forma el nodo de mayor prioridad (el de menor valor) se encuentra ubicado en la raíz.– La estructura anterior puede almacenarse en un
arreglo.
12: Heap 13
Heap como Arreglo La clave de la raíz se almacena en el primer elemento
del arreglo, con índice 1 (no se usa el 0); – luego se almacenan los nodos de primer nivel (de
izquierda a derecha); – luego los de segundo nivel (siempre de izquierda a
derecha) y así sucesivamente; el último presente debe marcarse con el cursor last.
12: Heap 14
Heap como Arreglo II De este modo:
– la raiz tiene el índice 1, – el hijo izquierdo del nodo i tiene el índice 2*i,– el hijo derecho del nodo i, si existe, tiene el índice:
2*i + 1, – el padre del nodo i, tiene el índice: i/2, – el nodo no existe si: (i < 1) o (i > last).
12: Heap 15
Complejidad del Heap En el caso del heap, como veremos a continuación, la
inserción tiene complejidad log2(n); La extracción del mínimo, manteniendo el heap,
también es log2(n). Si se realizan n operaciones en un heap éstas tienen un
costo: nlog2(n). Como veremos más adelante se requieren realizar n
operaciones para formar un heap a partir de un arreglo desordenado, mostrando de este modo que el heap puede emplearse para desarrollar un eficiente algoritmo de ordenamiento.
12: Heap 16
Operaciones en el Heap Se ilustra la inserción de un nodo con valor 13. Se agrega al final, y luego se lo hace ascender,
manteniendo la propiedad del heap: – El valor de cualquier nodo es menor o igual a los
valores de los nodos hijos. El algoritmo se denomina sift-up (filtrar, separar,
examinar).
12: Heap 17
Operaciones en el Heap II El número de comparaciones es el del número de nodos
desde una hoja hasta la raíz en un árbol binario, lo más balanceado posible (este número es log(n) ).
Solo es necesario comparar el valor del nodo con el valor del padre del nodo, ya que el hermano tiene un valor mayor o igual que el del padre.
Se intercambia el nodo con valor 18 y el reciéninsertado. Aún no se cumple la propiedad de ser un heap.
12: Heap 18
Operaciones en el Heap III Debe continuarse la revisión, en forma ascendente hacia
la raíz, para que se mantenga la propiedad del heap. Y debe intercambiarse el nodo de valor 13, con su padre, el nodo 16.
Comparando con la raíz, se advierte que el recién insertado no debe intercambiarse. Luego de estas operaciones, se mantiene la propiedad del heap.
12: Heap 19
Heap: Definiciones y estructura de datos Definiremos un tipo de datos denominado registro, que contenga la
prioridad y agregaremos ntarea como dato que contiene el registro.typedef struct nn1{
int prioridad; int ntarea;
} registro, *preg;
#define Ntareas 10 registro r[Ntareas+1];
int last=0; /* Apunta al último */registro Tarea; /*se asume un registro iniciado con valores */ preg nuevo=&Tarea
12: Heap 20
Heap: siftup Siftup() se implementa como descrita previamente.void siftup(int n) {
int i, j; registro temp; for (j=n; j>1; j=i) { /*Al inicio j apunta al último ( n ) */
i=j/2 ; /* i es el cursor del padre de j*/ if ( r[i].prioridad <= r[j].prioridad )
break; // Sale del for si esta en formato heap. temp=r[j]; r[j]= r[i]; r[i]=temp; /* intercambio si hijo < padre*/
/* La condición de reinicio hace que j apunte a su padre. j=i; */ }
}
12: Heap 21
Heap: insert insert() se apoya en siftup( ) para reordenar el arbol en heap.
void insert(preg nuevo) {
last++; r[last]=*nuevo; /* Antes se tenía un heap(1, last-1) */ siftup(last); /* Luego de sift-up se tiene un heap(1, last) */
}
12: Heap 22
Heap: siftdown Se saca elemento en la raiz, una vez extraida se intercambia con el
elemento apuntado por last y hace un siftdown para manter el heap.void siftdown(int n) { // n es el elemento mayor en el heap
int i=1, j=2*i; registro temp; /* al inicio i apunta a la raíz; j al hijo izquierdo */ while (j <=n) {
if ( ( (j+1) <=n ) && ( r[j+1].prioridad < r[j].prioridad)) j++; /* j apunta al menor de los hijos */
if (r[i].prioridad <= r[j].prioridad) break; // esta en formato heap
temp=r[j]; r[j]=r[i]; r[i]=temp; /* intercambia hijo < padre */ i=j; // nueva raiz en descensoj=2*i; // nuevo hijo izquierdo en descenso
} }
12: Heap 23
Heap: extraer Se saca elemento en la raiz, una vez extraida se intercambia con el
elemento apuntado por last y hace un siftdown para manter el heap.
int extraemin(void) { if (last>=1) {
Tarea = r[1] ; // Salva raiz printf(" %d ", Tarea.ntarea); // Hace algo con la tarea r[1]=r[last--]; // Mueve ultimo a raíz , decrementa last. siftdown(last); // Lo hace descender, manteniendo heap. return(0);
} else
return(1); /*error intento extracción en heap vacio*/ }
12: Heap 24
12-Heap
12.1 Definicion12.2 Operaciones12.3 Heap12.4 Heapsort
12: Heap 25
Heapsort Debido al gran número de algoritmos para ordenar, se estudiarán
en detalle dos de los algoritmos más conocidos y eficientes. Heapsort en peor caso es O(n*log2(n)) y es una refinación del
método de selección. Los primeros en exponer este algoritmo fueron: J.W.J. Williams y
Robert Floyd (1964). Algoritmo Heapsort:
Se forma un heap, a partir del arreglo desordenado. Luego, repetidamente se intercambia la raíz con la posición del último, y se hace descender la nueva raíz en un heap de largo disminuido en uno. El arreglo queda ordenado en forma descendente.
12: Heap 26
Heapsort: formación de heap Se analiza primero la formación de un
heap, dado un arreglo no ordenado: Existen dos procedimientos para la
formación de un heap a partir de un arreglo desordenado.
La primera manera se logra con: for(i=last/2; i>0; i--)
siftdown(i, last); i = 4
12: Heap 27
Heapsort: formación de heap Finalmente luego de
siftdown(1,8) desciende la raiz y se logra formar un heap.
Esto se logra con un costo T(n)=(n/2)log(n) es decir O(nlogn)
12: Heap 28
Heapsort: siftdown Se modifica levemente la rutina vista en colas de prioridad, se pasa
i de variable local, a argumento de la función.void siftdown(int i, int n){ int j; registro temp; /* al inicio i apunta a la subraíz; j al hijo izquierdo */ while ( (j=(i<<1)) <= n ) { /* j = 2*i */ if ( ( (j+1) <= n ) && ( r[j+1].prioridad < r[j].prioridad)) j++; /*Si hay hijo der. y es menor que el izq., j apunta al der. */ if (r[i].prioridad <= r[j].prioridad) break; /*Intercambia si el hijo menor es menor que el padre */ temp=r[j]; r[j]= r[i]; r[i]=temp; i=j; /* Nueva raíz en el descenso */ }}
12: Heap 29
Heapsort: siftdown De esta forma la función forma un heap(i, n). En la primera iteración con n=8 e i=4, no se producen cambios. Se revisa el subárbol del padre del último.
12: Heap 30
Heapsort: siftup Formar el heap también se logra con:
for(i=last; i>last/2; i--) siftup(i);
Tambien tiene una complejidad O(nlogn) La version siftup():void siftuph(int n) //inserta en posición n-ésima y lo hace ascender { int i, j;
registro temp; for (j=n; j>1; j=i) { /*Al inicio j apunta al último ( n ) */
i=(j>>1) ; /* i= j/2 donde i es el cursor del padre de j*/ if ( r[i].prioridad <= r[j].prioridad )
continue; //Si el ascenso no es en un heap, debe llegar hasta la raíz. temp=r[j], r[j]= r[i], r[i]=temp; /* intercambio si hijo < padre*/ /* La condición de reinicio hace que j apunte a su padre. j=i */ }
}
12: Heap 31
Heapsort: ordenamiento El ordenamiento del heap se logra con:
– Repetidamente se intercambia la raíz con la posición del último,
– y se hace descender la nueva raíz en un heap de largo disminuido en uno.
for(i=last; i>1; i--) { temp=r[1]; r[1]=r[i]; r[i]=temp; /*swap(1, i) */
siftdown(1, i-1);
} Como se realiza n veces el lazo for, si se asume costo
constante para el intercambio, se tiene un costo: (n)*(log(n) +1) que es O(n*log(n)).
El algoritmo ordena en forma descendente.
12: Heap 32
Heapsort: código Un codigo para ordenar usando heapsort, ini debe ser siempre
1, salvo que se modifiquen las rutinas de ascenso y descenso. void heapsort(preg a, Indice ini, Indice last) {
int i; registro temp; // otra opcion: for(i=last; i>last/2; i--) siftuph(a, ini, i); // ordena trayectos desde las hojas hasta la raiz
for(i=last/2; i>ini-1; i--) siftdown(a, i, last); // ordena subárboles
// desde padre del último for(i=last; i>ini; i--) {
temp=a[ini]; /*swap(ini, i) */ a[ini]= a[i]; a[i]=temp; siftdown(a, ini, i-1);
} }