Ordenamiento Rápido o

13
Ordenamiento rápido o Quick sort Veremos ahora el más famoso de los algoritmos recursivos de ordenamiento. Su fama radica en que en la práctica, con casos reales, es uno de los algoritmos más eficientes para ordenar. Este método se basa en la siguiente idea: 1. Si la lista es pequeña (vacía o de tamaño 1) ya está ordenada y no hay nada que hacer. De lo contrario hacer lo siguiente: 2. Tomar un elemento de la lista (por ejemplo el primero) al que llamaremos pivote y armar a partir de esa lista tres sublistas: la de todos los elementos de la lista menores al pivote, la formada sólo por el pivote, y la de los elementos mayores o iguales al pivote, pero sin contarlo al pivote. 3. Ordenar cada una de esas tres sublistas (usando este mismo método). 4. Concatenar las tres sublistas ya ordenadas. Por ejemplo, si la lista original es [6, 7, -1, 0, 5, 2, 3, 8] consideramos que el pivote es el primer elemento (el 6) y armamos las sublistas [-1, 0, 5, 2, 3], [6] y [7,8]. Se ordenan recursivamente [-1, 0, 5, 2, 3] (obtenemos [-1, 0, 2, 3, 5]) y [7, 8] (obtenemos la misma) y concatenamos en el orden adecuado, y así obtenemos [-1, 0, 2, 3, 5, 6, 7, 8]. Para diseñar, vemos que lo más importante es conseguir armar las tres listas en las que se parte la lista original. Para eso definiremos una función auxiliar _partition que recibe una lista no vacía y devuelve las tres sublistas menores, medio y mayores (incluye los iguales, de haberlos) en las que se parte la lista original usando como pivote al primer elemento.

description

algortimo de quicksort

Transcript of Ordenamiento Rápido o

Page 1: Ordenamiento Rápido o

 Ordenamiento rápido o Quick sortVeremos ahora el más famoso de los algoritmos recursivos de ordenamiento. Su fama radica en que en la práctica, con casos reales, es uno de los algoritmos más eficientes para ordenar. Este método se basa en la siguiente idea:

1. Si la lista es pequeña (vacía o de tamaño 1) ya está ordenada y no hay nada que hacer. De lo contrario hacer lo siguiente:

2. Tomar un elemento de la lista (por ejemplo el primero) al que llamaremos pivote y armar a partir de esa lista tres sublistas: la de todos los elementos de la lista menores al pivote, la formada sólo por el pivote, y la de los elementos mayores o iguales al pivote, pero sin contarlo al pivote.

3. Ordenar cada una de esas tres sublistas (usando este mismo método).

4. Concatenar las tres sublistas ya ordenadas.

Por ejemplo, si la lista original es [6, 7, -1, 0, 5, 2, 3, 8] consideramos que el pivote es el primer elemento (el 6) y armamos las sublistas [-1, 0, 5, 2, 3], [6] y [7,8]. Se ordenan recursivamente [-1, 0, 5, 2, 3] (obtenemos [-1, 0, 2, 3, 5]) y [7, 8] (obtenemos la misma) y concatenamos en el orden adecuado, y así obtenemos [-1, 0, 2, 3, 5, 6, 7, 8].

Para diseñar, vemos que lo más importante es conseguir armar las tres listas en las que se parte la lista original. Para eso definiremos una función auxiliar _partition que recibe una lista no vacía y devuelve las tres sublistas menores, medio y mayores (incluye los iguales, de haberlos) en las que se parte la lista original usando como pivote al primer elemento.

Contando con la función _partition, el diseño del Quick sort es muy simple:

1. Si lista es pequeña (vacía o de tamaño 1) ya está ordenada y no hay nada que hacer. Se devuelve lista tal cual.

2. De lo contrario:

o Dividir la lista en tres, usando _partition.

Page 2: Ordenamiento Rápido o

o Llamar a quick_sort(menores), quick_sort(mayores), y concatenarlo con medio en el medio.

Por otro lado, en cuanto a la función _partition(lista):

1. Tiene como precondición que la lista es no vacía.

2. Se elige el primer elemento como pivote.

3. Se inicializan como vacías las listas menores y mayores.

4. Para cada elemento de la lista después del primero:

o Si es menor que el pivote, se lo agrega a menores.

o De lo contrario, se lo a agrega a mayores.

5. Devolver menores, [pivote], mayores

Quicksort

Quicksort en acción sobre una lista de números aleatorios. Las líneas horizontales son valores pivote.

El ordenamiento rápido (quicksort en inglés) es un algoritmo creado por el científico británico en computación C. A. R. Hoare, basado en la técnica de divide y vencerás, que permite, en promedio, ordenar nelementos en un tiempo proporcional a n log n.

4 Enlaces externos

Descripción del algoritmoEl algoritmo trabaja de la siguiente forma:

Elegir un elemento de la lista de elementos a ordenar, al que llamaremos pivote. 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.

La lista queda separada en dos sublistas, una formada por los elementos a la izquierda del pivote, y otra por los elementos a su derecha.

Repetir este proceso de forma recursiva para cada sublista 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.

Page 3: Ordenamiento Rápido o

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 deO(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, si por ejemplo el algoritmo implementado toma como pivote siempre el primer elemento del array, y el array que le pasamos está ordenado, siempre va a generar a su izquierda un array vacío, lo que es ineficiente.

En el caso promedio, el orden es O(n·log n).No es extraño, pues, que la mayoría de optimizaciones que se aplican al algoritmo se centren en la elección del pivote.

Demostración de un caso particular[editar]Supongamos que el número de elementos a ordenar es una potencia de dos, es

decir,   para algún natural  . Inmediatamente  , donde k es el número de divisiones que realizará el algoritmo.

En la primera fase del algoritmo habrá n comparaciones. En la segunda fase el algoritmo instanciará dos sublistas de tamaño aproximadamente n/2. El número total de comparaciones de estas dos sublistas es: 2(n/2) = n. En la tercera fase el algoritmo procesará 4 sublistas más, por tanto el número total de comparaciones en esta fase es 4(n/4) = n.

En conclusión, el número total de comparaciones que hace el algoritmo es:

, donde  , por tanto el Orden de Complejidad

del algoritmo en el peor caso es  .

Técnicas de elección del pivote[editar]El algoritmo básico del método Quicksort consiste en tomar cualquier elemento de la lista al cual denominaremos como pivote, dependiendo de la partición en que se elija, el algoritmo será más o menos eficiente.

Tomar un elemento cualquiera como pivote tiene la ventaja de no requerir ningún cálculo adicional, lo cual lo hace bastante rápido. Sin embargo, esta elección «a ciegas» siempre provoca que el algoritmo tenga un orden de O(n²) para ciertas permutaciones de los elementos en la lista.

Otra opción puede ser recorrer la lista para saber de antemano qué elemento ocupará la posición central de la lista, para elegirlo como pivote. Esto puede hacerse en O(n) y asegura que hasta en el peor de los casos, el algoritmo seaO(n·log n). No obstante, el cálculo adicional rebaja bastante la eficiencia del algoritmo en el caso promedio.

La opción a medio camino es tomar tres elementos de la lista - por ejemplo, el primero, el segundo, y el último - y compararlos, eligiendo el valor del medio como pivote.

Técnicas de reposicionamiento[editar]Una idea preliminar para ubicar el pivote en su posición final sería contar la cantidad de elementos menores que él, y colocarlo un lugar más arriba, moviendo luego todos esos elementos menores que él a su izquierda, para que pueda aplicarse la recursividad.

Page 4: Ordenamiento Rápido o

Existe, no obstante, un procedimiento mucho más efectivo. Se utilizan dos índices: i, al que llamaremos índice izquierdo, yj, al que llamaremos índice derecho. El algoritmo es el siguiente:

Recorrer la lista simultáneamente con i y j: por la izquierda con i (desde el primer elemento), y por la derecha con j (desde el último elemento).

Cuando lista[i] sea mayor que el pivote y lista[j] sea menor, se intercambian los elementos en esas posiciones.

Repetir esto hasta que se crucen los índices. El punto en que se cruzan los índices es la posición adecuada para colocar el pivote,

porque sabemos que a un lado los elementos son todos menores y al otro son todos mayores (o habrían sido intercambiados).

Transición a otro algoritmo[editar]Como se mencionó anteriormente, el algoritmo quicksort ofrece un orden de ejecución O(n²) para ciertas permutaciones "críticas" de los elementos de la lista, que siempre surgen cuando se elige el pivote «a ciegas». La permutación concreta depende del pivote elegido, pero suele corresponder a secuencias ordenadas. Se tiene que la probabilidad de encontrarse con una de estas secuencias es inversamente proporcional a su tamaño.

Los últimos pases de quicksort son numerosos y ordenan cantidades pequeña de elementos. Un porcentaje medianamente alto de ellos estarán dispuestos de una manera similar al peor caso del algoritmo, volviendo a éste ineficiente. Una solución a este problema consiste en ordenar las secuencias pequeñas usando otro algoritmo. Habitualmente se aplica el algoritmo de inserción para secuencias de tamaño menores de 8-15 elementos.

Pese a que en secuencias largas de elementos la probabilidad de hallarse con una configuración de elementos "crítica" es muy baja, esto no evita que sigan apareciendo (a veces, de manera intencionada). El algoritmo introsort es una extensión del algoritmo quicksort que resuelve este problema utilizando heapsort en vez de quicksort cuando el número de recursiones excede al esperado.

Nota: Los tres parámetros de la llamada inicial a Quicksort serán array[0], 0, numero_elementos -1, es decir, si es un array de 6 elementos array, 0, 5

Ejemplo[editar]

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

Page 5: Ordenamiento Rápido o

i j p

5 es mayor que 4 y 1 es menor. Intercambiamos:

1 - 3 - 7 - 6 - 2 - 5 - 4i j p

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] (pasos 16-18):

1 - 3 - 2 - 4 - 7 - 5 - 6 p

Page 6: Ordenamiento Rápido o

Aplicamos recursivamente a la sublista 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 sublista. Al finalizar y unir todas las sublistas queda la lista inicial ordenada en forma ascendente.

1 - 2 - 3 - 4 - 5 - 6 - 7

No es extraño, pues, que la mayoría de optimizaciones que se aplican al algoritmo se centren en la elección del pivote.Pseudocódigo

inicio

variables A: arreglo[1..100] entero

variables i,j,central:entero

variables primero, ultimo: entero

para i = 1 hasta 100

leer(A[i])

Fin para

primero = 1

ultimo = 100

qsort(A[],100)

Fin

Funcion qsort(primero, ultimo:entero)

i = primero

Page 7: Ordenamiento Rápido o

j = ultimo

central = A[(primero,ultimo) div 2]

repetir

mientras A[i]central

j = j - 1

fin mientras

si i < = j

aux = A[i]

A[j] = A[i]

A[i] = aux

i = i + 1

j = j - 1

fin si

hasta que i > j

si primero < j

partir(primero,j)

fin si

si i < ultimo

partir(i, ultimo)

fin si

fin funcion qsort

  El ordenamiento por partición (Quick Sort) se puede definir en una forma más conveniente como un procedimiento recursivo.

      Tiene aparentemente la propiedad de trabajar mejor para elementos de entrada desordenados completamente, que para elementos semiordenados. Esta situación es precisamente la opuesta al ordenamiento de burbuja.

      Este tipo de algoritmos se basa en la técnica "divide y vencerás", o sea es más rápido y fácil ordenar dos arreglos o listas de datos pequeños , que un arreglo o lista grande.

Page 8: Ordenamiento Rápido o

      Normalmente al inicio de la ordenación se escoge un elemento aproximadamente en la mitad del arreglo, así al empezar a ordenar, se debe llegar a que el arreglo este ordenado respecto al punto de división o la mitad del arreglo.

      Se podrá garantizar que los elementos a la izquierda de la mitad son los menores y los elementos a la derecha son los mayores.

      Los siguientes pasos son llamados recursivos con el propósito de efectuar la ordenación por partición al arreglo izquierdo y al arreglo derecho, que se obtienen de la primera fase. El tamaño de esos arreglos en promedio se reduce a la mitad.

      Así se continúa hasta que el tamaño de los arreglos a ordenar es 1, es decir, todos los elementos   ya están ordenados.

      En promedio para todos los elementos de entrada de tamaño n, el método hace O(n log n) comparaciones, el cual es relativamente eficiente.

Explicación abstracta del funcionamiento de QuickSort1. Se elige un elemento v de la lista L de elementos al que

se le llama pivote.

2. Se particiona la lista L en tres listas:

1. L1 - que contiene todos los elementos de L menos v que sean menores o iguales que v

2. L2 - que contiene a v

3. L3 - que contiene todos los elementos de L menos v que sean mayores o iguales que v

3. Se aplica la recursión sobre L1 y L3

Page 9: Ordenamiento Rápido o

4. Se unen todas las soluciones que darán forma final a la lista L finalmente ordenada. Como L1 y L3 están ya ordenados, lo único que tenemos que hacer es concatenar L1, L2 y L3

Aunque este algoritmo parece sencillo, hay que implementar los pasos 1 y 3 de forma que se favorezca la velocidad de ejecución del algoritmo.

Eligiendo el PivoteLa velocidad de ejecución del algoritmo depende en gran medida de como se implementa este mecanismo, una mala implementación puede suponer que el algoritmo se ejecute a una velocidad mediocre o incluso pésima. La elección del pivote determina las particiones de la lista de datos, por lo tanto, huelga decir que esta es la parte más crítica de la implementación del algoritmo QuickSort. Es importante intentar que al seleccionar el pivote v las particiones L1 y L3 tengan un tamaño idéntico dentro de lo posible.

Elegir el primero o el último de la lista nunca es una buena idea ya que los elementos de la lista no están uniformemente distribuidos. Por otro lado, si contamos con un buen generador de números aleatorios, podemos elegir un pivote al azar de entre todos los elementos de la lista. Esta estrategia es segura puesto que es improbable que un pivote al azar de como resultado una partición mala, pero tiene como contrapartida que en algunas ocasiones si

Page 10: Ordenamiento Rápido o

puede arrojar un resultado de O(n2), además, la elección de números aleatorios puede incrementar el tiempo de ejecución del algoritmo.

Una buena estrategia para solucionar la selección del pivote ámpliamente extendida es la conocida como "a tres bandas". En esta estrategia lo que se persigue es hacer una media con los valores de tres de los elementos de la lista. Por ejemplo si nuestra lista es [ 8, 4, 9, 3, 5, 7, 1, 6, 2 ] la media sería ( 8 + 2 + 5 ) / 3 = 5 lo que daría lugar a las siguientes particiones:

L1 = [ 8, 9, 7, 6 ]

L2 = [ 5 ]

L3 = [ 1, 2, 4, 3 ]

Esta estrategia no nos asegura que siempre nos dará la mejor selección del pivote, sino que estadísticamente, la elección del pivote sea buena.

Implementando la estrategia de ParticionadoRecordemos que el fin de esta implementación de QuickSort es la de crear un algoritmo de ordenación eficiente y rápido, por lo que las listas "auxiliares" que creamos al particionar no son listas reales, es decir, no creamos nuevos elementos de lista para albergar los elementos, sino que situamos el

Page 11: Ordenamiento Rápido o

pivote en una posición determinada dentro de la lista para simular las particiones. La mejor opción a la hora de crear las listas que contendrán a L1 y L3 es reordenar los elementos de forma que los elementos que aparecen antes del pivote sean menores o iguales a él, y los que aparecen después sean mayores o iguales.

Eso puede implementarse en Python de la siguiente forma, que además es muy elegante: