UAA – Sistemas Electrónicos 3 Estructura de Datos Serna Métodos de Ordenamiento 3.1 Tipos de Ordenamiento La ordenación o clasificación de datos consiste en la disposición de los mismos de acuerdo con algún valor o característica. Por ejemplo, cada elemento de una agenda telefónica tiene un campo nombre, un campo dirección y un campo número telefónico. Por lo regular los datos en la agenda se encuentran organizados en un orden de la A la Z. De la misma forma un lista ó vector de datos se dice que esta ordenado de manera ascendente, si X [ i ] <= X [ i +1] y, por otro lado, se dice que esta ordenado de manera descendente sí X [ i ] >= X [ i +1]. El proceso de ordenación es uno de los mecanismos más interesantes cuando llega el momento de mostrar que existen múltiples soluciones para un mismo problema, y que cada solución algorítmica tiene sus propias ventajas y desventajas. Una forma de medir la eficiencia de un algoritmo de esta clase, es verificar el número de comparaciones entre valores clave, además del número de movimientos que se tengan que realizar entre elementos (intercambios) de la lista. Los métodos de ordenamiento que trabajan con estructuras de datos residentes en memoria principal se denominan Ordenamientos Internos, mientras que las implementaciones que utilizan estructuras de datos residentes en archivos se conocen como Ordenamientos externos. 3.2 Ordenamiento Interno Los métodos de ordenamiento interno trabajan en memoria principal y sus implementaciones son muy variadas, de manera que la elección del algoritmo adecuado debe realizarse con criterios de eficiencia (tiempo y ejecución) y en función de la memoria disponible. Dividiremos los métodos en dos grandes grupos: • • Directos (burbuja, selección e inserción). Logarítmicos (Shell sort, Merge sort, Heap sort, Quick sort, Radix). En el caso de listas pequeñas, los métodos directos se desempeñan de manera relativamente eficientes, ya que la codificación del algoritmo correspondiente no es compleja. Su uso es muy frecuente. Sin embargo, en arreglos grandes las ordenaciones directas resultan ineficientes y se necesitara un método logarítmico para su solución. 3.2.1 Ordenación por intercambio (burbuja) Es uno de los métodos relativamente más sencillo e intuitivo, pero también resulta ser muy ineficiente. Se basa en la ordenación por cambio, y recibe su nombre de la semejanza con las burbujas de un depósito de agua donde cada burbuja busca su propio nivel. Los pasos a efectuar en el caso de una ordenación ascendente (en el caso de la ordenación descenderte solo habría que cambiar el signo de comparación) son: 1. Comparar el primer y segundo elemento, intercambiarlos si el primero es mayor que el segundo; luego se compara el primero con el tercero, intercambiándose en caso necesario, y el proceso se repite hasta llegar al último elemento. De este modo, tras la primera iteración la casilla primera conservara el elemento más pequeño de esa iteración. 1 UAA – Sistemas Electrónicos Estructura de Datos Serna 2. Se repite el paso anterior, pero ahora con el segundo y tercero, en caso de ser necesario se intercambian, y así hasta llegar a comparar el segundo con el ultimo. Consideremos el siguiente ejemplo. Se cuenta con un vector de 6 posiciones donde se inicia una lista de números { 7, 2, 8, 3, 5, 1 }, la cual será ordenada en forma ascendente { 1, 2, 3, 5, 7, 8 }, observe como se declara una variable constante entera llamada n la cual tiene un valor de 6, enseguida se declara un vector de tipo entero que contendrá una cantidad n de casillas, en este caso 6, declaramos también las variables i y j que nos ayudaran a desplazarnos entre casilla y casilla para hacer las comparaciones. Y finalmente la variable tem, almacenara temporalmente el valor a intercambiar entre las casillas que lo necesiten. El proceso sería de la siguiente manera: 1ra iteración, i permanece fijo en la casilla 0 y j se incrementa hasta llegar al último elemento: { 7, 2, 8, 3, 5, 1 } j = 1, intercambio generando { 2, 7, 8, 3, 5, 1 }, enseguida { 2, 7, 8, 3, 5, 1 } j = 2, no genera intercambio { 2, 7, 8, 3, 5, 1 } j = 3, no genera intercambio { 2, 7, 8, 3, 5, 1 } j = 4, no genera intercambio { 2, 7, 8, 3, 5, 1 } j = 5, intercambio generando { 1, 7, 8, 3, 5, 2 }, termina iteración 2da iteración, i permanece fijo en la casilla 1 y j se incrementa hasta llegar al último elemento: { 1, 7, 8, 3, 5, 2 } j = 2, no genera intercambio { 1, 7, 8, 3, 5, 2 } j = 3, intercambio generando { 1, 3, 8, 7, 5, 2 }, enseguida { 1, 3, 8, 7, 5, 2 } j = 4, no genera intercambio { 1, 3, 8, 7, 5, 2 } j = 5, intercambio generando { 1, 2, 8, 7, 5, 3 }, termina iteración 3ra iteración, i permanece fijo en la casilla 2 y j se incrementa hasta llegar al último elemento: { 1, 2, 8, 7, 5, 3 } j = 3, intercambio generando { 1, 2, 7, 8, 5, 3 } { 1, 2, 7, 8, 5, 3 } j = 4, intercambio generando { 1, 2, 5, 8, 7, 3 } { 1, 2, 5, 8, 7, 3 } j = 5, intercambio generando { 1, 2, 3, 8, 7, 5 }, termina iteración 4ta iteración, i permanece fijo en la casilla 3 y j se incrementa hasta llegar al último elemento: { 1, 2, 3, 8, 7, 5 } j = 4, intercambio generando { 1, 2, 3, 7, 8, 5 } { 1, 2, 3, 7, 8, 5 } j = 5, intercambio generando { 1, 2, 3, 5, 8, 7 }, termina iteración 5ta iteración, i permanece fijo en la casilla 4 y j se incrementa hasta llegar al último elemento: { 1, 2, 3, 5, 8, 7 } j = 5, intercambio generando { 1, 2, 3, 5, 7, 8 }, termina proceso void burbuja(int * v, int n) /*** código en C++ ***/ { /* ordenamiento de burbuja */ for (int i = 0; i < n-1; i++) for (int j = i + 1; j < n; j++) { if ( v[i] > v[j] ) { //realiza la comparación int tem = v[i]; //si es cierta intercambia los datos v[i] = v[j]; v[j] = tem; } } } Algoritmo de Burbuja mejorado Existe una versión mejorada de dicho algoritmo en donde se integra una variable que funge como switch (bandera) que permite detectar el momento en que ya no se presenten mas intercambios aunque su mejora no suele ser tan importante pues el algoritmo sigue comportándose como una 2 ordenación cuadrática O(n ). 2 UAA – Sistemas Electrónicos Estructura de Datos Serna void burbuja(int *v, int n) /*** código en C++ ***/ { bool sw = true; int li = 0; do { li++; sw = true; for (int i = 0; i < n - li; i++) { if ( v[i] > v[i+1] ) { // compara los valores // intercambia los datos int tem = v[i]; v[i] = v[i+1]; v[i+1] = tem; sw = false; } } } while(!sw); } 3.2.2 Método de selección La idea básica es encontrar el elemento más pequeño (grande), en orden ascendente de la lista, e intercambiarlo con el elemento que ocupa la primera posición en la lista, a continuación se busca el siguiente elemento más pequeño y se transfiere a la segunda posición. Se repite el proceso hasta que el último elemento ha sido transferido a su posición correcta. El algoritmo de ordenación depende a su vez del algoritmo necesario para localizar el componente mayor (menor) de un array. Es un proceso muy similar al método de la burbuja pero haciendo más eficiente la búsqueda y evitando intercambios innecesarios. Consideremos el mismo arreglo del ejemplo anterior { 7, 2, 8, 3, 5, 1 }. El proceso sería de la siguiente manera: 1ra iteración, i permanece fijo en la casilla 0 y j se incrementa hasta llegar al último elemento: { 7, 2, 8, 3, 5, 1 } k = 0, j = 1, cambio k = j ya que en j esta el menor { 7, 2, 8, 3, 5, 1 } k = 1, j = 2, no genera cambio { 7, 2, 8, 3, 5, 1 } k = 1, j = 3, no genera cambio { 7, 2, 8, 3, 5, 1 } k = 1, j = 4, no genera cambio { 7, 2, 8, 3, 5, 1 } k = 1, j = 5, cambio k = j ya que en j esta el menor, genera { 1, 2, 8, 3, 5, 7 } 2da iteración, i permanece fijo en la casilla 1 y j se incrementa hasta llegar al último elemento: { 1, 2, 8, 3, 5, 7 } k = 1, j = 2 - 5, no genera cambios 3ra iteración, i permanece fijo en la casilla 2 y j se incrementa hasta llegar al último elemento: { 1, 2, 8, 3, 5, 7 } k = 2, j = 3, cambio k = j ya que en j esta el menor { 1, 2, 8, 3, 5, 7 } k = 3, j = 4, no genera cambio { 1, 2, 8, 3, 5, 7 } k = 3, j = 5, no genera cambio, termina el ciclo, genera { 1, 2, 3, 8, 5, 7 } 4ta iteración, i permanece fijo en la casilla 3 y j se incrementa hasta llegar al último elemento: { 1, 2, 3, 8, 5, 7 } k = 3, j = 4, cambio k = j ya que en j esta el menor { 1, 2, 3, 8, 5, 7 } k = 4, j = 5, no genera cambio, termina el ciclo, genera { 1, 2, 3, 5, 8, 7 } 5ta iteración, i permanece fijo en la casilla 4 y j se incrementa hasta llegar al último elemento: { 1, 2, 3, 5, 8, 7 } k = 4, j = 5, cambio k = j ya que en j esta el menor, genera { 1, 2, 3, 5, 7, 8 } 3 UAA – Sistemas Electrónicos Estructura de Datos Serna void seleccion( int * v, int n) /*** código en C++ ***/ { /* ordenamiento de seleccion */ for (int i = 0, j = 0, k = 0; i < n-1; i++) { k = i; for (j = i+1; j < n; j++) if ( v[k] > v[j] ) k = j; int tem = v[i]; v[i] = v[k]; v[k] = tem; } } 3.2.3 Método de Inserción Este método también se denomina “método del jugador de cartas”, por la semejanza con la forma de clasificar las cartas de una baraja, insertando cada carta en el lugar adecuado. El algoritmo ordena los dos primeros elementos de la lista, a continuación el tercer elemento se inserta en la posición que corresponda, el cuarto se inserta en la lista de tres elementos, y así sucesivamente. Este proceso continua hasta que la lista este totalmente ordenada. Sea una lista A[1], A[2], ... A[n]. Los pasos a dar para una ordenación ascendente son: 1. Ordenar A[1] y A[2]. 2. Comparar A[3] con A[2], si A[3] es mayor o igual a que A[2], sigue con el siguiente elemento si no se compara A[3] con A[1]; si A[3] es mayor o igual que A[1], insertar A[3] entre A[1] yA[2]. Si A[3] es menor que A[1], entonces transferir A[3] a A[1], A[1] a A[2] y A[2] a A[3]. 3. Se suponen ordenados los n-1 primeros elementos y corresponde insertar el n-ésimo elemento. Si A[m] es mayor que A[k] (con K = 1, 2, ..., m-1), se debe correr una posición A[k+1], ... A[m-1] y almacenar A[m] en la posición k+1. Consideremos el mismo arreglo del ejemplo anterior { 7, 2, 8, 3, 5, 1 }. El proceso sería de la siguiente manera: 1ra iteración, i permanece fijo en la casilla 1 y j se decrementa mientras el elemento es menor a j: { 7, 2, 8, 3, 5, 1 } tem = 2, j = 0, mientras j >= 0 y tem < 7, { 7, 7, 8, 3, 5, 1 }, j se decrementa en 1 { 7, 7, 8, 3, 5, 1 } tem = 2, j = -1, mientras j >=0, rompe mientras, tem ingresa { 2, 7, 8, 3, 5, 1 } 2da iteración, i permanece fijo en la casilla 2 y j se decrementa mientras el elemento es menor a j: { 2, 7, 8, 3, 5, 1 } tem = 8, j = 1, mientras j >= 0 y tem < 7, rompe mientras { 2, 7, 8, 3, 5, 1 } 3ra iteración, i permanece fijo en la casilla 3 y j se decrementa mientras el elemento es menor a j: { 2, 7, 8, 3, 5, 1 } tem = 3, j = 2, mientras j >= 0 y tem < 8, { 2, 7, 8, 8, 5, 1 }, j se decrementa en 1 { 2, 7, 8, 8, 5, 1 } tem = 3, j = 1, mientras j >= 0 y tem < 7, { 2, 7, 7, 8, 5, 1 }, j se decrementa en 1 { 2, 7, 7, 8, 5, 1 } tem = 3, j = 0, mientras j >= 0 y tem < 2, rompe, tem ingresa { 2, 3, 7, 8, 5, 1 } 4ta iteración, i permanece fijo en la casilla 4 y j se decrementa mientras el elemento es menor a j: { 2, 3, 7, 8, 5, 1 } tem = 5, j = 3, mientras j >= 0 y tem < 8, { 2, 3, 7, 8, 8, 1 }, j se decrementa en 1 { 2, 3, 7, 8, 8, 1 } tem = 5, j = 2, mientras j >= 0 y tem < 7, { 2, 3, 7, 7, 8, 1 }, j se decrementa en 1 { 2, 3, 7, 7, 8, 1 } tem = 5, j = 1, mientras j >= 0 y tem < 3, rompe, tem ingresa { 2, 3, 5, 7, 8, 1 } 5ta iteración, i permanece fijo en la casilla 5 y j se decrementa mientras el elemento es menor a j: { 2, 3, 5, 7, 8, 1 } tem = 1, j = 4, mientras j >= 0 y tem < 8, { 2, 3, 5, 7, 8, 8 }, j se decrementa en 1 { 2, 3, 5, 7, 8, 8 } tem = 1, j = 3, mientras j >= 0 y tem < 7, { 2, 3, 5, 7, 7, 8 }, j se decrementa en 1 4 UAA – Sistemas Electrónicos { 2, 3, 5, 7, 7, 8 } tem { 2, 3, 5, 5, 7, 8 } tem { 2, 3, 3, 5, 7, 8 } tem { 2, 2, 3, 5, 7, 8 } tem Estructura de Datos Serna = 1, j = 2, mientras j >= 0 y tem < 5, { 2, 3, 5, 5, 7, 8 }, j se decrementa en 1 = 1, j = 1, mientras j >= 0 y tem < 3, { 2, 3, 3, 5, 7, 8 }, j se decrementa en 1 = 1, j = 0, mientras j >= 0 y tem < 2, { 2, 2, 3, 5, 7, 8 }, j se decrementa en 1 = 1, j = -1, mientras j >= 0, rompe mientras, tem ingresa { 1, 2, 3, 5, 7, 8 } void insercion( int * v, int n) /*** código en C++ ***/ { /* ordenamiento de insercion */ for (int i = 1, j = 0; i < n; i++) { int tem = v[i]; j = i - 1; while ( j >= 0 && tem < v[j]) { v[j+1] = v[j]; j--; } v[j+1] = tem; } } 3.2.4 Ordenación Shell Shell sort lleva este nombre en honor a su inventor, Donald Shell, que lo publicó en 1959. La idea básica de este método es distribuir el arreglo de manera que se genere una matriz de valores donde cada elemento es comparado de manera adyacente empleando un mecanismo de inserción directa simple, dicho rango que genera grupos de manera matricial que es reducido gradualmente hasta estabilizarse en un valor uniforme de 1. En el método de ordenación por inserción directa es empleado en cada sub grupo de manera que cada elemento se compara para su ubicación correcta en el arreglo con los elementos que se encuentran en su parte izquierda. Si el elemento a insertar es más pequeño que el grupo de elementos que se encuentran a su izquierda, será necesario efectuar varias comparaciones antes de su ubicación. Shell propone que las comparaciones entre elementos se efectúen con saltos de mayor tamaño, pero con incrementos decrecientes; así, los elementos quedaran ordenados más rápidamente. El algoritmo para Shell sort seria el siguiente. Algunos autores suponen una secuencia geométrica de decremento (2.2) que permite una distribución presumiblemente más razonable para el acomodo de los grupos de elementos a comparar. El algoritmo emplea un arreglo a de 0 a n-1: inc = round(n/2) mientras inc > 0 { para i = inc hasta n – 1 { temp = a[i] j = i mientras j = inc && a[j - inc] > temp { a[j] = a[j - inc] j = j – inc } a[j] = temp } inc = round(inc / 2.2) } 5 UAA – Sistemas Electrónicos Estructura de Datos Serna Consideremos el siguiente ejemplo, tenemos una lista de números como 13 14 94 33 82 25 59 94 65 23 45 27 73 25 39 10. Si comenzamos con un tamaño de paso de 5, podríamos visualizar esto dividiendo la lista de números en una tabla con 5 columnas. Esto quedaría así: 13 14 94 33 82 25 59 94 65 23 45 27 73 25 39 10 25 59 94 65 82 45 Entonces ordenamos cada columna, lo que nos da 10 14 73 25 23 13 27 94 33 39 Cuando lo leemos de nuevo como una única lista de números, obtenemos 10 14 73 25 23 13 27 94 33 39 25 59 94 65 82 45. Aquí, el 10 que estaba en el extremo final, se ha movido hasta el extremo inicial. Esta lista es entonces de nuevo ordenada usando un ordenamiento con un espacio de 3 posiciones, y después un ordenamiento con un espacio de 1 posición (ordenamiento por inserción simple). 10 14 73 25 23 13 27 94 33 39 25 59 94 65 82 45 25 23 33 27 25 59 39 65 73 45 94 82 94 Generando … 10 14 13 Y finalmente … 10 14 13 25 23 33 27 25 59 39 65 73 45 94 82 94 10 13 14 23 25 25 27 33 39 45 59 65 73 82 94 94 void shell_sort(int A[], int size) /*** código en C++ ***/ { int j, temp; int incrmnt = size/2; while (incrmnt > 0) { for (int i = incrmnt; i < size; i++) { j = i; temp = A[i]; while ((j >= incrmnt) && (A[j-incrmnt] > temp)) { A[j] = A[j - incrmnt]; j = j - incrmnt; } A[j] = temp; } incrmnt /= 2; } } 3.2.5 Ordenación por Mezcla (Merge Sort) Fue desarrollado en 1945 por John Von Neumann. Conceptualmente, el ordenamiento por mezcla funciona de la siguiente manera: 1. Si la longitud de la lista es 0 ó 1, entonces ya está ordenada. En otro caso: 2. Dividir la lista desordenada en dos sublistas de aproximadamente la mitad del tamaño. 6 UAA – Sistemas Electrónicos Estructura de Datos Serna 3. Ordenar cada sublista recursivamente aplicando el ordenamiento por mezcla. 4. Mezclar las dos sublistas en una sola lista ordenada. El ordenamiento por mezcla incorpora dos ideas principales para mejorar su tiempo de ejecución: Una lista pequeña necesitará menos pasos para ordenarse que una lista grande. Se necesitan menos pasos para construir una lista ordenada a partir de dos listas también ordenadas, que a partir de dos listas desordenadas. Por ejemplo, sólo será necesario entrelazar cada lista una vez que están ordenadas. A continuación se describe el algoritmo en pseudocódigo (se advierte de que no se incluyen casos especiales para vectores vacíos, una implementación en un lenguaje de programación real debería tener en cuenta estos detalles): funcion merge_sort( list m ) if length(m) ≤ 1 return m var list left, right, result var integer middle = length(m) / 2 for each x in m up to middle add x to left for each x in m after middle add x to right left = merge_sort(left) right = merge_sort(right) result = merge(left, right) return result funcion merge( list left, right) var list result while length(left) > 0 or length(right) > 0 if length(left) > 0 and length(right) > 0 if first(left) ≤ first(right) append first(left) to result left = rest(left) else append first(right) to result right = rest(right) else if length(left) > 0 append first(left) to result left = rest(left) else if length(right) > 0 append first(right) to result right = rest(right) end while return result Considere el siguiente ejemplo con una lista { 5, 2, 4, 6, 1, 3, 2, 6 }, la lista es dividida en la primera de las dos llamadas a merge con { 5, 2, 4, 6 }, después nuevamente es dividida en { 5, 2 }, se intercala obteniendo { 2, 5 }, la segunda llamada recupera { 4, 6 } y se intercalan para obtener { 2, 4, 5, 6 }. Con la mitad { 1, 3, 2, 6 } ocurre la misma reducción de elementos tras las llamadas intercalando finalmente la salida para obtener { 1, 2, 2, 3, 4, 5, 6, 6 }. 7 UAA – Sistemas Electrónicos Estructura de Datos Serna La siguiente Figura1 describe mejor el proceso de retro-seguimiento (backtrack) que construye la lista ordenada una vez que la recursividad alcanza su caso base. Figura 1 void intercala(int *a, int na, int *b, int nb, int *v)/* código en C++ */ { int xa=0, xb=0, xv=0; while (xa < na && xb < nb) { if (a[xa] < b[xb]) v[xv] = a[xa++]; else v[xv] = b[xb++]; xv++; } while (xa < na) v[xv++] = a[xa++]; while (xb < nb) v[xv++] = b[xb++]; } void merge(int v[], int n) { int *a,*b, na,nb, x,y; if ( n > 1 ) { if (n%2 == 0) na = nb = (int) n / 2; else { na = (int) n / 2; nb = na + 1; } a = new int [na]; b = new int [nb]; for(x = 0; x < na; x++) a[x] = v[x]; for(y = 0; y < nb; x++,y++) b[y] = v[x]; merge(a, na); 8 UAA – Sistemas Electrónicos Estructura de Datos Serna merge(b, nb); intercala(a, na, b, nb, v); delete a, b; } } 3.2.6 Ordenación por partición e intercambio (Quick Sort) Es un algoritmo relativamente eficiente y representa una mejora sustancial al método de intercambio directo. El algoritmo es el siguiente: 1. Elegir un elemento de la lista de elementos a ordenar (pivote). 2. Resituar los demás elementos de la lista a cada lado del pivote, de manera que a un lado queden todos los menores que él, y al otro los mayores. Los elementos iguales al pivote pueden ser colocados tanto a su derecha como a su izquierda, dependiendo de la implementación deseada. En este momento, el pivote ocupa exactamente el lugar que le corresponderá en la lista ordenada. 3. La lista queda separada en dos sub-listas, una formada por los elementos a la izquierda del pivote, y otra por los elementos a su derecha. 4. Repetir este proceso de forma recursiva para cada sub-lista mientras éstas contengan más de un elemento. Una vez terminado este proceso todos los elementos estarán ordenados. Como se puede suponer, la eficiencia del algoritmo depende de la posición en la que termine el pivote elegido. En el mejor caso, el pivote termina en el centro de la lista, dividiéndola en dos sublistas de igual tamaño. En este caso, el orden de complejidad del algoritmo es O(n log n). En el peor caso, el pivote termina en un extremo de la lista. El orden de complejidad del algoritmo es entonces de O(n²). El peor caso dependerá de la implementación del algoritmo, aunque habitualmente ocurre en listas que se encuentran ordenadas, o casi ordenadas. Pero principalmente depende del pivote, por ejemplo el algoritmo implementado toma como pivote siempre el primer elemento del arreglo, y el arreglo que le pasamos está ordenado, siempre va a generar a su izquierda un arreglo vacío, lo que es ineficiente. En el siguiente ejemplo se marcan el pivote y los índices i y j con las letras p, i y j respectivamente. Comenzamos con la lista completa. El elemento pivote será el 4: 5 - 3 - 7 - 6 - 2 - 1 - 4 p Comparamos con el 5 por la izquierda y el 1 por la derecha. 5 - 3 - 7 - 6 - 2 - 1 - 4 i j p 5 es mayor que 4 y 1 es menor. Intercambiamos: 1 - 3 - 7 - 6 - 2 - 5 - 4 i j p 9 UAA – Sistemas Electrónicos Estructura de Datos Serna Avanzamos por la izquierda y la derecha: 1 - 3 - 7 - 6 - 2 - 5 - 4 i j p 3 es menor que 4: avanzamos por la izquierda. 2 es menor que 4: nos mantenemos ahí. 1 - 3 - 7 - 6 - 2 - 5 - 4 i j p 7 es mayor que 4 y 2 es menor: intercambiamos. 1 - 3 - 2 - 6 - 7 - 5 - 4 i j p Avanzamos por ambos lados: 1 - 3 - 2 - 6 - 7 - 5 - 4 iyj p En este momento termina el ciclo principal, porque los índices se cruzaron. Ahora intercambiamos lista[i] con lista[sup]. 1 - 3 - 2 - 4 - 7 - 5 - 6 p Aplicamos recursivamente a la sub-lista de la izquierda (índices 0 - 2). Tenemos lo siguiente: 1 - 3 - 2 1 es menor que 2: avanzamos por la izquierda. 3 es mayor: avanzamos por la derecha. Como se intercambiaron los índices termina el ciclo. Se intercambia lista[i] con lista[sup]: 1 - 2 - 3 El mismo procedimiento se aplicará a la otra sub-lista. Al finalizar y unir todas las sub-listas queda la lista inicial ordenada en forma ascendente. 1 - 2 - 3 - 4 - 5 - 6 - 7 int partions(int l[],int low,int high) { int prvotkey=l[low]; while (low<high) { while (low<high && l[high]>=prvotkey) --high; swap(l,high,low); while (low<high && l[low]<=prvotkey) ++low; int dummy; dummy=l[low]; l[low]=l[high]; l[high]=dummy; } 10 UAA – Sistemas Electrónicos Estructura de Datos Serna } return low; } void qsort(int l[],int low,int high) { int prvotloc; if(low<high) { prvotloc=partions(l,low,high); qsort(l,low,prvotloc); qsort(l,prvotloc+1,high); } } void quicksort(int l[],int n) { qsort(l,0,n); } 3.2.7 Ordenación basado en comparaciones (Heap Sort) Es una variante del algoritmo de selección, El ordenamiento por montículos (Heap sort) es un algoritmo de ordenación no recursivo, no estable, con complejidad computacional O(n log n). Este algoritmo consiste en almacenar todos los elementos del vector a ordenar en un montículo (heap), y luego extraer el nodo que queda como nodo raíz del montículo (cima) en sucesivas iteraciones obteniendo el conjunto ordenado. Basa su funcionamiento en una propiedad de los montículos, por la cual, la cima contiene siempre el menor elemento (o el mayor, según se haya definido el montículo) de todos los almacenados en él. El significado de heap en computación es el de una cola de prioridades (priority queue). Tiene las siguientes características: Un heap es un arreglo de n posiciones ocupado por los elementos de la cola. Se mapea un árbol binario de tal manera en el arreglo que el nodo en la posición i es el padre de los nodos en las posiciones (2*i) y (2*i+1). El valor en un nodo es mayor o igual a los valores de sus hijos. Por consiguiente, el nodo padre tiene el mayor valor de todo su subárbol heap arreglo 11 UAA – Sistemas Electrónicos Estructura de Datos Serna Heap Sort consiste esencialmente en: • • • 3.3 convertir el arreglo en un heap construir un arreglo ordenado de atrás hacia adelante (mayor a menor) repitiendo los siguientes pasos: o sacar el valor máximo en el heap (el de la posición 1) o poner ese valor en el arreglo ordenado o reconstruir el heap con un elemento menos utilizar el mismo arreglo para el heap y el arreglo ordenado. Ordenamiento Externo La ordenación de archivos se lleva a cabo cuando el volumen de los datos a tratar es demasiado grande y los mismos no caben en la memoria principal de la computadora. Al ocurrir esta situación no pueden aplicarse los métodos de ordenación interna, de modo que debe pensarse en otro tipo de algoritmos para ordenar datos almacenados en archivos. Por ordenación de archivos se entiende, entonces, la ordenación o clasificación de éstos, ascendente o descendentemente, de acuerdo con un campo determinado al que se denominará campo clave. La principal desventaja de esta ordenación es el tiempo de ejecución, debido a las sucesivas operaciones de entrada y salida. Los dos métodos de ordenación externa más importantes son los basados en la mezcla directa y en la mezcla equilibrada. 3.3.1 Ordenación por mezcla directa El método de ordenación por mezcla directa es probablemente el más utilizado por su fácil comprensión. La idea central de este algoritmo consiste en la realización sucesiva de una partición y una fusión que produce secuencias ordenadas de longitud cada vez mayor. En la primera pasada la participación es de longitud 1 y la fusión o mezcla produce secuencias ordenadas de longitud 4. Este proceso se repite hasta que la longitud de la secuencia para la partición sea mayor o igual que la longitud de la secuencia para la partición sea mayor o igual que el número de elementos del archivo original. Supóngase que se desean ordenar las claves del archivo F. Para realizar tal actividad se utilizan dos archivos auxiliares a los que se les denominará F1 y F2. F: 09 75 14 68 29 17 31 25 04 05 13 18 72 46 61 PRIMERA PASADA Partición en secuencias de longitud 1. F1: 09 14 29 31 04 13 72 61 12 UAA – Sistemas Electrónicos Estructura de Datos Serna F2: 75 68 17 25 05 18 46 Fusión en secuencias de longitud 2. F1: 09 75 14 68 17 29 25 31 04 05 13 18 46 72 61 SEGUNDA PASADA Partición en secuencias de longitud 2. F1: 09 75 17 29 04 05 46 72 F2: 14 68 25 31 13 18 61 Fusión en secuencias de longitud 4. F1: 09 14 68 75 17 25 29 31 04 05 13 18 46 61 72 TERCERA PASADA Partición en secuencias de longitud 4. F1: 09 14 68 75 04 05 13 18 F2: 17 25 29 31 46 61 72 Fusión en secuencias de longitud 8. F1: 09 14 75 25 29 31 68 75 04 05 13 18 46 61 72 CUARTA PASADA Partición en secuencias de longitud 8. F1: 09 14 17 25 29 31 68 75 F2: 04 05 13 18 46 61 72 Fusión en secuencias de longitud 16. F1: 04 05 09 13 14 17 18 25 29 31 46 61 68 72 75 3.3.2 Ordenación por mezcla equilibrada El método de ordenación por mezcla equilibrada, conocido también con el nombre de mezcla natural, es una optimización del método de mezcla directa. La idea central de este método consiste en realizar las particiones tomando secuencias ordenadas de máxima longitud en lugar de secuencias de tamaño fijo previamente determinadas. Luego realiza la fusión de las secuencias ordenadas, alternativamente sobre dos archivos aplicando estas acciones en forma repetida se lograra que el archivo original quede ordenado. Para la realización de este proceso de ordenación se necesitaran cuatro archivos. El archivo original F y tres archivos auxiliares a los que se denominaran F1, F2 y F3. De estos archivos, dos serán considerados de entrada y dos de salida; esto, alternativamente, con el objeto de realizar la fusión-partición. El proceso termina cuando en la realización de una fusión-partición el segundo archivo quede vacío. 13 UAA – Sistemas Electrónicos Estructura de Datos Serna EJEMPLO : Suponga que se desean ordenar las claves del archivo F utilizando el método de mezcla equilibrada. F: 09 75 14 68 29 17 31 25 04 05 13 18 72 46 61 Los pasos que se realizan son los siguientes: PARTICIÓN INICIAL F2: 09 75 29 25 46 61 F3: 14 68 17 31 04 05 13 18 72 PRIMERA FUSION-PARTICION F: 09 14 68 75 04 05 13 18 25 46 61 72 F1: 17 29 31 SEGUNDA FUSION-PARTICION F2: 09 14 17 29 31 68 75 F3: 04 05 13 18 25 46 61 72 TERCERA FUSION-PARTICION F: 04 05 09 13 14 17 18 25 29 31 46 61 68 72 75 F1: Obsérvese que al realizar la tercera fusión-partición el segundo archivo queda vació, por lo que se puede afirmar que el archivo ya se encuentra ordenado. 14 UAA – Sistemas Electrónicos Estructura de Datos Serna Bibliografía Y. Langsam, M. J. Augenstein, A. Tenenbaum. Data Structures using C and C++. Prentice Hall, Second edition. ISBN 0-13-036997-7. http://en.wikipedia.org/wiki/Sorting_algorithm 15