Sorting, segunda parte. Clase 21 (10 de Junio, 2011)

Anuncio
Sorting++
Herman Schinca
Clase 21
10 de Junio de 2011
Recordando
Ya vimos 3 algoritmos de ordenamiento basados
en la comparación entre elementos:
Selection, Insertion y Bubble.
Los 3 en peor caso (cuando el arreglo está
ordenado “al revés”) demandan un orden
cuadrático respecto de la cantidad de
operaciones que realizan.
¿Hay mejores?
Sí, hoy veremos 2 de ellos cuya complejidad
temporal es menor. No sólo menor sino óptima.
Decidir cuándo un algoritmo tiene una
complejidad óptima es un problema “mucho muy
muy mucho” difícil.
Interesados ver: Cormen, 2001 “Introduction to
algorithms”, MIT Press, página 167.
Sorting Recursivo
Los 2 algoritmos que veremos son recursivos y
están basados en la técnica Divide & Conquer.
Quick Sort
Elegimos un elemento cualquiera del arreglo (por
ejemplo, el primero) y dividimos el arreglo en dos
usando ese elemento para separar los que son
menores o iguales a él de los que son mayores.
Luego, aplico este criterio a la parte derecha y a
la parte izquierda.
Quick Sort: Código
void quickSort (int *a, int tam){
if (tam > 1){
int *menores = malloc(tam*sizeof(int)); int tam_menores = 0;
int *mayores = malloc(tam*sizeof(int)); int tam_mayores = 0;
int pivot = a[0];
int i;
for (i = 1; i < tam; i++) {
if (a[i] <= a[0]){
menores[tam_menores] = a[i]; tam_menores++;
} else {
mayores[tam_mayores] = a[i]; tam_mayores++;
}
}
Quick Sort: Código (cont.)
quickSort (menores, tam_menores);
quickSort (mayores, tam_mayores);
i++)
}
}
for (i = 0; i < tam_menores; i++) {
a[i] = menores[i];
}
a[tam_menores] = pivot;
for (i = tam_menores+1; i < tam_menores+tam_mayores+1;
{
a[i] = mayores[i-tam_menores-1];
}
free(menores); free(mayores);
Quick Sort: Complejidad
¿Cuál sería el peor caso? ¿Cuántas operaciones
se deberán realizar en dicha situación?
Quick Sort: Caso Promedio
Si bien en el peor caso QuickSort tiene un orden
cuadrático respecto de la cantidad de
operaciones que realiza, se puede realizar un
análisis del caso promedio y concluir que su costo
es del orden de N*log(N), con N el tamaño del
arreglo.
Quick Sort: Creer o reventar
Generalmente, de todos los algoritmos de
ordenamiento basados en comparación
existentes, el QuickSort es el que anda más
rápido aunque su complejidad en peor caso sea
cuadrática.
Es una decisión no trivial y determinante la
elección del pivot (de hecho se puede lograr
complejidad N*log(N) en peor caso...).
Quick Sort: Memoria
Se puede realizar un análisis respecto de la
complejidad espacial, es decir, el uso de
memoria.
Notemos que en cada llamado recursivo estamos
creando nuevos arreglos que en total suman 4*N
bytes que no los destruimos hasta finalizar el
ordenamiento global.
Quick Sort in-place
Se puede evitar la creación de nuevos arreglos y
realizar el ordenamiento dentro del mismo arreglo.
Como ejercicio deben tratar de comprender y
explicar cómo funciona el siguiente algoritmo de
QuickSort in-place :-).
Quick Sort in-place: Código
void quickSortInPlace (int *a, int izq, int der){
if (der > izq){
int posNuevaPivot = partir (a, izq, der, izq);
quickSortInPlace (a, izq, posNuevaPivot-1);
quickSortInPlace (a, posNuevaPivot+1, der);
}
}
Quick Sort in-place: Código (cont.)
int partir (int *a, int izq, int der, int posPivot){
int pivot = a[posPivot];
int aux = a[posPivot]; a[posPivot] = a[der]; a[der] = aux;
int pos = izq;
int i;
for (i = izq; i < der; i++){
if (a[i] <= pivot){
int aux2 = a[i]; a[i] = a[pos]; a[pos] = aux2;
pos++;
}
}
int aux3 = a[pos]; a[pos] = a[der]; a[der] = aux3;
return pos;
}
Merge Sort
Partimos el arreglo en dos partes “iguales”.
Ordeno ambas partes y luego mezclo los dos
arreglos según los valores.
Merge Sort: Código
void mergeSort (int *a, int tam){
if (tam > 1){
int *mitad1 = malloc((tam/2)*sizeof(int));
int *mitad2 = malloc((tam-tam/2)*sizeof(int));
int i;
for (i = 0; i < tam; i++) {
if (i < tam/2){
mitad1[i] = a[i];
} else {
mitad2[i-tam/2] = a[i];
}
}
mergeSort (mitad1, tam/2);
mergeSort (mitad2, tam - tam/2);
Merge Sort: Código (cont.)
int posMitad1 = 0, posMitad2 = 0;
for (i = 0; i < tam; i++) {
if (posMitad1 < tam/2 && posMitad2 < tam - tam/2){
if (mitad1[posMitad1] <= mitad2[posMitad2]){
a[i] = mitad1[posMitad1]; posMitad1++;
} else {
a[i] = mitad2[posMitad2]; posMitad2++;
}
}
Merge Sort: Código (cont.)
} else {
if (posMitad1 >= tam/2){
a[i] = mitad2[posMitad2]; posMitad2++;
} else {
a[i] = mitad1[posMitad1]; posMitad1++;
}
}
}
free(mitad1); free(mitad2);
}
}
Merge Sort: Complejidad
Analizando el peor caso concluimos que la
cantidad de operaciones que realiza es del orden
de N*log(N), donde N es el tamaño del arreglo.
Ejercicio
Se tiene un conjunto de n secuencias {s1,s2,...,sn}
en donde cada si (1 <= i <= n) es una secuencia
ordenada de naturales. ¿Qué método utilizaría
para obtener un arreglo que contenga todos los
elementos de la unión de los si ordenados?
Describirlo. Justificar.
Descargar