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.