If you're seeing this message, it means we're having trouble loading external resources on our website.

Si estás detrás de un filtro de páginas web, por favor asegúrate de que los dominios *.kastatic.org y *.kasandbox.org estén desbloqueados.

Contenido principal

Análisis del ordenamiento rápido

¿Cómo es que el tiempo de ejecución del peor caso y del caso promedio del ordenamiento rápido difieren? Vamos a empezar por ver el tiempo de ejecución del peor caso. Supón que en realidad somos muy desafortunados y que los tamaños de las particiones están muy desbalanceados. En particular, supón que el pivote escogido por la función partition siempre es el elemento más pequeño o el más grande en el subarreglo de n elementos. Entonces una de las particiones no va a contener elementos y la otra partición contendrá n1 elementos (todos menos el pivote). Así que las llamadas recursivas serán sobre subarreglos de tamaños 0 y n1.
Como en el ordenamiento por mezcla, el tiempo para una llamada recursiva dada en un subarreglo de n elementos es Θ(n). En el ordenamiento por mezcla, ese era el tiempo para mezclar, pero en el ordenamiento rápido es el tiempo para hacer las particiones.

Tiempo de ejecución del peor caso

Cuando el ordenamiento rápido siempre tiene las particiones más desbalanceadas posibles, entonces la llamada original tarda un tiempo cn para alguna constante c, la llamada recursiva sobre n1 elementos tarda un tiempo c(n1), la llamada recursiva sobre n2 elementos tarda un tiempo c(n2), y así sucesivamente. Aquí está un árbol de los tamaños de los subproblemas con sus tiempos para hacer las particiones:
Un diagrama del rendimiento para el peor caso para el ordenamiento rápido, con un árbol a la izquierda y los tiempos de partición a la derecha. El árbol está etiquetado como "Tamaños de los subproblemas" y la derecha está etiquetada como "Tiempo total de partición para todos los subproblemas de este tamaño". El primer nivel del árbol muestra un solo nodo n y el tiempo de partición correspondiente de c por n. El segundo nivel del árbol muestra dos nodos, 0 y n menos 1, y un tiempo de partición de c por n menos 1. El tercer nivel del árbol muestra dos nodos, 0 y n menos 2, y un tiempo de partición de c por n menos 2. El cuarto nivel del árbol muestra dos nodos, 0 y n menos 3, y un tiempo de partición de c por n menos 3. Debajo de ese nivel, hay unos puntos que indican que el árbol continúa de esa manera. El penúltimo nivel en el árbol tiene un solo nodo de 2 con un tiempo de partición de 2 por c y el último nivel tiene dos nodos de 0 y 1 con un tiempo de partición de 0.
Cuando sumamos los tiempos para hacer las particiones para cada nivel, obtenemos
cn+c(n1)+c(n2)++2c=c(n+(n1)+(n2)++2)=c((n+1)(n/2)1) .
La última línea es porque 1+2+3++n es la serie aritmética, como vimos cuando analizamos el ordenamiento por selección. (Le restamos 1 porque para el ordenamiento rápido, la suma empieza en 2, no en 1). Tenemos algunos términos de orden inferior y coeficientes constantes, pero los ignoramos cuando usamos notación Θ grande. En notación Θ grande, el tiempo de ejecución del peor caso del ordenamiento rápido es Θ(n2).

Tiempo de ejecución del mejor caso

El mejor caso del ordenamiento rápido ocurre cuando las particiones están balanceadas tan uniformemente como sea posible: sus tamaños son iguales o difieren en 1. El primer caso ocurre si el subarreglo tiene un número impar de elementos, el pivote está justo en medio después de hacer la partición y cada partición tiene (n1)/2 elementos. El otro caso ocurre si el subarreglo tiene un número par n de elementos y una partición tiene n/2 elementos mientras que la otra tiene n/21. En cualquiera de estos casos, cada partición tiene a lo más n/2 elementos, y el árbol de los tamaños de los subproblemas se parece mucho al árbol de los tamaños de los subproblemas para el ordenamiento por mezcla, con los tiempos para hacer las particiones que se ven como los tiempos de mezcla:
Un diagrama del rendimiento para el mejor caso para el ordenamiento rápido, con un árbol a la izquierda y tiempos de partición a la derecha. El árbol está etiquetado como "Tamaño del subproblema" y la derecha está etiquetada como "Tiempo total de partición para todos los subproblemas de este tamaño". El primer nivel del árbol muestra un solo nodo n y el tiempo de partición correspondiente de c por n. El segundo nivel del árbol muestra dos nodos, cada uno menor o igual a 1/2 n, y un tiempo de partición menor o igual a 2 por c por 1/2 n, que es lo mismo que c por n. El tercer nivel del árbol muestra cuatro nodos, cada uno menor o igual a 1/4 n, y un tiempo de partición menor o igual a 4 por c por 1/4 n, que es lo mismo que c por n. El cuarto nivel del árbol muestra ocho nodos, cada uno menor o igual que 1/8 n, y un tiempo de partición menor o igual a 8 por c por 1/8 n, que es lo mismo que c por n. Debajo de ese nivel, se muestran puntos para indicar que el árbol continúa de esa manera. Se muestra un nivel final con n nodos de 1 y un tiempo de partición menor o igual a n por c, que es lo mismo que c por n.
Al usar notación Θ grande, obtenemos el mismo resultado que para un ordenamiento por mezcla: Θ(nlog2n).

Tiempo de ejecución del caso promedio

Mostrar que el tiempo de ejecución del caso promedio también es Θ(nlog2n) requiere unas matemáticas bastante complicadas, así que no lo vamos a hacer. Pero podemos obtener un poco de intuición al ver un par de otros casos para entender por qué podría ser O(nlog2n) (una vez que tengamos O(nlog2n), la cota de Θ(nlog2n) se sigue porque el tiempo de ejecución del caso promedio no puede ser mejor que el tiempo de ejecución del mejor caso). Primero, imaginemos que no siempre obtenemos particiones igualmente balanceadas, pero que siempre obtenemos una razón de 3 a 1. Es decir, imagina que cada vez que hacemos una partición, un lado obtiene 3n/4 elementos y el otro lado obtiene n/4 (para mantener limpias las matemáticas, no vamos a preocuparnos por el pivote). Entonces, el árbol de los tamaños de los subproblemas y los tiempos para hacer las particiones se vería así:
Diagrama del desempeño del caso promedio para el ordenamiento rápido
El hijo izquierdo de cada nodo representa un subproblema cuyo tamaño es un 1/4 del tamaño del padre, y el hijo derecho representa un subproblema cuyo tamaño es 3/4 del tamaño del padre. Como los subproblemas más pequeños están del lado izquierdo, al seguir el camino de hijos izquierdos llegamos de la raíz hasta un problema de tamaño 1 más rápido que por cualquier otro camino. Como se muestra en la figura, después de log4n niveles, llegamos a un subproblema de tamaño 1. ¿Por qué log4n niveles? Podría ser más fácil pensar en términos de empezar con un subproblema de tamaño 1 y multiplicarlo por 4 hasta llegar a n. En otras palabras, estamos preguntando: ¿para qué valor de x se cumple que 4x=n? La respuesta es log4n. ¿Qué pasa si vamos por un camino de hijos derechos? La figura muestra que se necesitan log4/3n niveles para llegar a un subproblema de tamaño 1. ¿Por qué log4/3n niveles? Como el tamaño de cada hijo derecho es 3/4 del tamaño del nodo arriba de él (su nodo padre), el tamaño de cada padre es 4/3 el tamaño de su hijo derecho. Volvamos a pensar en empezar con un subproblema de tamaño 1 y multiplicar su tamaño por 4/3 hasta llegar a n. ¿Para qué valor de x se cumple que (4/3)x=n? La respuesta es log4/3n.
En cada uno de los primeros log4n niveles hay n nodos (de nuevo, incluyendo pivotes que en realidad ya no se están particionando), así que el tiempo total de hacer particiones para cada uno de estos niveles es de cn. ¿Qué pasa con el resto de los niveles? Cada uno tiene menos de n nodos, así que el tiempo para hacer particiones para cada nivel es a lo más cn. Todo junto, hay log4/3n niveles, así que el tiermpo total para hacer particiones es O(nlog4/3n). Ahora, hay un hecho matenático que dice que
logan=logbnlogba
para todos los números positivos a, b y n. Al hacer a=4/3 y b=2, obtenemos que
log4/3n=log2nlog2(4/3) ,
así que log4/3n y log2n solo difieren por un factor de log2(4/3), que es una constante. Como los factores constantes no importan cuando usamos la notación O grande, podemos decir que si todas las divisiones tienen una razón de 3 a 1, entonces el tiempo de ejecución del ordenamiento rápido es O(nlog2n), aunque con un factor constante más grande escondido que el tiempo de ejecución del mejor caso.
¿Qué tan seguido esperaríamos ver una razón de divisiones que sea 3 a 1 o mejor? Depende de cómo escojamos el pivote. Imaginemos que el pivote es igualmente probable de acabar en cualquier lado en un subarreglo de n elementos después de hacer las particiones. Para obtener una división que tenga una razón de 3 a 1 o mejor, el pivote debería estar en algún lado en "la mitad de en medio":
Así que si el pivotes es igualmente probable de acabar en cualquier lado en el subarreglo después de hacer las particiones, hay una probabilidad del 50% de obtener una división de 3 a 1 en el peor de los casos. En otras palabras, esperamos tener una división de 3 a 1 o mejor alrededor de la mitad del tiempo.
El otro caso que vamos a ver para entender por qué el tiempo de ejecución del caso promedio del ordenamiento rápido es O(nlog2n), es qué pasaría si en la mitad de veces que no obtenemos una división de 3 a 1, obtenemos el peor caso de la división. Vamos a suponer que las divisiones de 3 a 1 y del peor caso alternan, y piensa en un nodo en el árbol con k elementos en su subarreglo. Entonces veríamos una parte del árbol como esta:
en vez de así:
Por lo tanto, incluso si la mitad de las veces tuviéramos el peor caso de la división y la otra mitad una división de 3 a 1, el tiempo de ejecución sería alrededor del doble del tiempo de ejecución de siempre obtener una división de 3 a 1. De nuevo, eso es solo un factor constante y se absorbe en la notación O grande. Así que en este caso, en donde se alterna entre el peor caso para las divisiones y el caso de 3 a 1, el tiempo de ejecución es O(nlog2n).
Ten en cuenta que este análisis no es matemáticamente riguroso, pero te da una idea intuitiva de por qué el tiempo de ejecución del caso promedio podría ser O(nlog2n).

Ordenamiento rápido aleatorizado

Supón que tu peor enemigo te da un arreglo para ordenarlo con el ordenamiento rápido. Sabe que siempre escoges el elemento de hasta la derecha en cada subarreglo como el pivote y acomodó el arreglo de modo que siempre obtengas el peor caso de la división. ¿Cómo puedes frustrar a tu enemigo?
Podrías no necesariamente escoger el elemento de hasta al derecha en cada subarreglo como el pivote. En lugar de eso, podrías escoger un elemento en el subarreglo de manera aleatoria y usar ese elemento como el pivote. Pero espera, la función partition supone que el pivote está en la posición de hasta la derecha del subarreglo. No hay problema, solo intercambia el elemento que escojas como el pivote con el elemento de hasta la derecha, y después haz la partición como antes. A menos que tu enemigo sepa cómo escoges ubicaciones aleatorias en el subarreglo, ¡tú ganas!
De hecho, con un poco más de esfuerzo, puedes mejorar tu probabilidad de obtener una división que sea, en el peor de los casos, de 3 a 1. Escoge de manera aleatoria no uno, sino tres elementos del subarreglo, y toma la mediana de los tres como el pivote (al intercambiarlo por el elemento de hasta la derecha). Por la mediana, queremos decir el elemento de los tres cuyo valor esté en medio. No te vamos a mostrar por qué, pero si escoges la mediana de tres elementos elegidos de manera aleatoria como el pivote, tienes una probabilidad de 68.75% (11/16) de obtener una división de 3 a 1 o mejor. Incluso puedes ir más allá. Si escoges cinco elementos de manera aleatoria y tomas la mediana como el pivote, tu probabilidad para un peor caso de 3 a 1 mejora a alrededor de 79.3% (203/256). Si tomas la mediana de siete elementos elegidos de manera aleatoria, sube hasta alrededor de 85.9% (1759/2048). ¿Mediana de nueve? Alrededor de 90.2% (59123/65536). ¿Mediana de 11? Alrededor de 93.1% (488293/524288). Entiendes la idea. Por supuesto, no necesariamente funciona mejor escoger un número grande de elementos de manera aleatoria y tomar su mediana, pues el tiempo que se tarda en hacerlo podría contrarrestar el beneficio de obtener buenas divisiones casi todo el tiempo.

Este contenido es una colaboración de los profesores de Dartmouth Computer Science Thomas Cormen y Devin Balkcom, con el equipo de contenidos de computación de Khan Academy. El contenido está bajo licencia CC-BY-NC-SA.

¿Quieres unirte a la conversación?

¿Sabes inglés? Haz clic aquí para ver más discusiones en el sitio en inglés de Khan Academy.