Anda di halaman 1dari 23

Ordenamiento

Francisco Claude Faust


fclaude@dcc.uchile.cl
31 de marzo de 2007

1. Introducción
En este cápitulo veremos los algoritmos de ordenamiento más conocidos.
Partiremos planteando el modelo sobre el cuál trabajaremos y las restriccio-
nes que éste impone. Luego veremos algoritmos de ordenamiento propiamente
tales, partiendo por los métodos más básicos, normalmente de O(n2 ), has-
ta llegar a métodos que tengan una complejidad temporal de O(n log2 (n)).
Finalmente, como cierre del cápitulo veremos algunos métodos de ordena-
miento que no caen dentro del esquema común y por lo tanto no tienen las
mismas limitaciones.

2. Definiciones y el modelo
Para comenzar a trabajar en este cápitulo definiremos formalmente el
problema de ordenamiento. Éste corresponde a tomar un conjunto de llaves
X de tamaño n y entregar una n-tupla que contiene los elementos de X or-
denados según algún criterio. Asumiremos la existencia de una relación de
orden total sobre el conjunto X denotada por 0 <0 (su opuesto será 0 >0 ) y
que las llaves no se repiten. Simétricamente se puede definir el problema de
ordenar un conjunto X descendentemente.

Trabajaremos sobre el supuesto de que las preguntas que podemos hacer


para comparar dos elementos x, y ∈ X son del tipo:

¿Es x < y?

1
¿Es x > y?

Frente a esto, notamos que sólo podemos hacer preguntas binarias, es de-
cir, con respuestas SI o NO (bajo este modelo). Esto induce una cota inferior
para el problema de ordenamiento. Notemos que la n-tupla que esperamos
obtener tras ordenar es una permutación del conjunto. Tenemos en total n!
permutaciones distintas y mediante preguntas binarias queremos determinar
cuál de todas las permutaciones corresponde a aquella en la que el conjunto
está ordenado. Es claro que si representáramos la información sobre cuál per-
mutación es la buscada como una secuencia de bits, podrı́amos determinar
máximo un bit de esta secuencia con cada pregunta binaria. De aquı́ nace
la cota, pues en el mejor caso cada pregunta aporta un bit y la cantidad de
bits necesarios para distinguir una permutación de las n! existentes corres-
ponde a log2 (n!), que aproximado usando Stirling corresponde a O(n log2 (n)).

Ésta es una limitación que estará presente durante la mayor parte del
capı́tulo. No existe un algoritmo que mediante comparaciones bi-
narias de llaves pueda ordenar en menos de O(n log2 (n)) compara-
ciones. Al final de este cápitulo veremos algunos modelos que rompen este
esquema y permiten alcanzar mejores tiempos.

3. Algoritmos básicos de ordenamiento


3.1. Selección
El algoritmo de ordenamiento por selección corresponde al algoritmo de
ordenamiento posiblemente más intuitivo. La idea general corresponde a bus-
car el mı́nimo valor de los elementos, almacenarlo como el primer elemento
de la n-tupla ordenada y volver a ejecutar este proceso sobre los elementos
restantes. Por ejemplo, para el arreglo [2, 4, 3, 8, 7, 6], en la primera iteración
(con respecto a i) tendrı́amos como mı́nimo el elemento de la posición 1.
Lo mantendremos ahı́. En la siguiente iteración, el mı́nimo estará ubicado
en la posición 3, haremos el intercambio con la posición 2 y tendremos los
dos primeros elementos del arreglo ordenado. Cuando el algoritmo termine,
tendremos todo el arreglo ordenado.

2
Seleccion (D[1 . . . n])
1. For i ← 1 to n Do
2. min ← i
3. For j ← i + 1 to n Do
4. If D[min] > D[j] Then
5. min ← j
6. aux ← D[min]
7. D[min] ← D[i]
8. D[i] ← aux

Figura 1: Ordenamiento por selección

La Figura 1 muestra el algoritmo y la Figura 2 muestra un arreglo que


ya tiene las primeras k posiciones ordenadas y n − k elementos aún desorde-
nados, pero que sabemos que son mayores que los que se encuentran dentro
de los k primeros.

Figura 2: Estado del arreglo luego de k iteraciones

No es difı́cil notar que este algoritmo es de orden cuadrático, pues se


puede plantear la siguiente ecuación para representar su costo.

T (n) = T (n − 1) + n − 1

Esto se debe a que para ordenar n elementos debemos encontrar el mı́nimo


del conjunto (cuesta n − 1 comparaciones) y luego ordenar un conjunto de
n − 1 elementos, es decir, T (n − 1). Si pasamos el término T (n − 1) restando
en la ecuación obtenemos una telescópica.

3
T (n) − T (n − 1) = n − 1
n
X n
X
T (i) − T (i − 1) = i−1
i=1 i=1
n(n + 1)
T (n) − T (0) = −n
2
n(n − 1)
T (n) = + T (0)
2
El único término faltante es T (0), que corresponde a la cantidad de com-
paraciones entre elementos que hacemos para ordenar un conjunto de 0 ele-
mentos. Esto corresponde a 0 comparaciones de llaves, por lo que finalmente
sabemos que la cantidad de comparaciones T (n) corresponde a n(n−1) 2
.

Notamos además que este análisis de peor caso es la única medida de


interés que podemos obtener de este algoritmo, pues su caso promedio es
el mismo. Sı́ podrı́amos ver la cantidad de modificaciones que se hace a la
variable min. Esta es igual al resultado obtenido en su peor caso, pero en el
caso promedio tenemos una pequeña variación, la condición expresada en la
lı́nea 4 de la Figura 1 no se debiese cumplir siempre, por lo que la asignación
podrı́a omitirse. En este caso, si asumimos que los elementos se distribuyen
uniformemente en el arreglo, tendrı́amos que la cantidad promedio de veces
que modificamos la variable min en una iteración cumple la siguiente ecua-
ción de recurrencia C(n) = 1/n + C(n − 1). Pues la probabilidad de que el
último elemento sea el mı́nimo es 1/n, y esto implica un intercambio, hay
que sumarle los que hay en promedio en un conjunto de tamaño n − 1. El
resultado de resolver esta recurrencia es:

C(n) = C(n − 1) + 1/n


C(n) − C(n − 1) = 1/n

Nuevamente sumando hasta n y reemplazando C(0) = 0 se obtiene:

n
X n
X
C(i) − C(i − 1) = 1/i
i=1 i=1
C(n) = Hn

4
Luego C(n) = O(log n), si contamos las n iteraciones tenemos O(n log n)
intercambios en total.

3.2. Inserción
Este algoritmo trabaja bajo un enfoque similar al anterior, vale decir,
nuevamente mantendremos el comienzo del arreglo ordenado. La diferencia
es que no impondremos que ese sea el orden definitivo de la primera porción.
Es decir, tenemos las primeras k posiciones del arreglo ordenadas, pero cada
elemento que revisemos fuera de esas posiciones puede corresponder a un
elemento que en el arreglo ordenado irı́a en una de las primeras k posicio-
nes. Para aumentar el tamaño de la porción ordenada llevaremos ese nuevo
elemento a la posición que les corresponde, pero sólo tomando en cuenta los
primeros k elementos. El algoritmo se muestra en la figura 3. Por ejemplo,
para el arreglo [2, 4, 3, 8, 7, 6], el arreglo se ordenarı́a de la siguiente forma
(siguiendo el algoritmo de la Figura 3):

i = 2, j =2 2 4 3 8 7 6
i = 3, j =3 2 3 4 8 7 6
i = 4, j =4 2 3 4 8 7 6
i = 5, j =5 2 3 4 7 8 6
i = 6, j =6 2 3 4 7 6 8
i = 6, j =5 2 3 4 6 7 8

En otras palabras, lo que hacemos es tener una parte del arreglo ordenado
(las primeras i posiciones), cada vez que aumentamos i debemos mantener
el orden. El único elemento que puede violar nuestra regla de mantener los
primeros i ordenados es el i-ésimo elemento. Lo que hacemos es tomar ese
elemento y buscar donde insertarlo para que los elementos hasta su posición
queden ordenados. Esto es sin importar lo que se encuentra hacia la derecha
en el arreglo.

5
Inserción (D[1 . . . n])
1. For i ← 2 to n Do
2. j←i
3. While j > 1 And D[j] < D[j − 1] Do
4. aux ← D[j]
5. D[j] ← D[j − 1]
6. D[j − 1] ← aux
7. j ←j−1

Figura 3: Ordenamiento por inserción

La Figura 4 muestra un arreglo que ya tiene las primeras k posiciones


ordenadas y n − k elementos aún desordenados, los cuales pueden ser tanto
mayores como menores que los elementos ya posicionados.

Figura 4: Estado del arreglo luego de k iteraciones

En este caso el análisis se puede extender un poco, debido a que en rea-


lidad el caso promedio si afecta en este caso. No es difı́cil ver que el análisis
de peor caso es similar al del algoritmo de selección. De hehco, se plantea la
misma ecuación de recurrencia anterior para el peor caso:

T (n) = T (n − 1) + n − 1

La solución es por lo tanto T (n) = n(n−1)


2
. Como ya dijimos antes, el caso
promedio nos interesará más, pues pretende medir como se comportará nues-
tro algoritmo en la práctica. Nuevamente trabajaremos sobre el supuesto que

6
los elementos se encuentran uniformemente distribuidos a lo largo del arreglo.
La ecuación que describe el costo esperado del algoritmo es:

1
T (n) = T (n − 1) + (n − 1)
2
Ya que puedo caer en cualquier posición dentro de los primeros k, por lo
que en promedio caeré en la mitad.

Solucionamos la ecuación de la misma forma que antes y obtenemos lo


siguiente:

1
T (n) = T (n − 1) + (n − 1)
2
1
T (n) − T (n − 1) = (n − 1)
2
De esta ecuación es fácil ver que el resultado será como en la sección
anterior, T (n) = n(n−1)
4
. Lo importante es que en este caso, nuestro análisis
de caso promedio sı́ nos da la cantidad promedio de veces que el algoritmo
itera y ésta es diferente al peor caso.

3.3. Burbuja
El ordenamiento por burbuja es bastante iteresante (sı́, iteresante), la
idea es recorrer el arreglo de izquierda a derecha intercambiando el elemento
de la posición actual con la siguiente si es que el siguiente es menor. El
algoritmo se muestra en la figura 5. Por ejemplo, para el arreglo [2, 4, 3, 8, 7, 6],
tendrı́amos el siguiente resultado tras la primera pasada, j indica la posición
de la burbuja:

j =1 2 4 3 8 7 6
j =2 2 3 4 8 7 6
j =3 2 3 4 8 7 6
j =4 2 3 4 7 8 6
j =5 2 3 4 7 6 8

7
Burbuja (D[1 . . . n])
1. For i ← 1 to n Do
2. For j ← 1 to n − i Do
3. If D[j] > D[j + 1] Then
4. aux ← D[j]
5. D[j] ← D[j + 1]
6. D[j − 1] ← aux

Figura 5: Ordenamiento por burbuja

La Figura 6 muestra un arreglo que ya tiene las últimas k posiciones or-


denadas y n − k elementos aún desordenados, los cuales son menores que los
elementos ya posicionados y además tienden a estar pseudo-ordenados por
las pasadas anteriores.

Figura 6: Estado del arreglo luego de k iteraciones

Este algoritmo puede ser modificado para mejorar su tiempo de ejecución.


Sin embargo, esto no afectará su eficiencia en términos asintóticos. La idea es
verificar si para un cierto i no hubo ninguna modificación al arreglo. Si esto
ocurrió sabemos que el arreglo está ordenado y por lo tanto podemos termi-
nar 1 . De todas formas esta modificación puede ser útil al tratar problemas
particulares- Para un ejemplo ver el Problema 2 en la sección de problemas.

No es difı́cil ver que el método de ordenamiento por burbuja tiene la


misma complejidad que los dos algoritmos anteriores.
1
En la práctica esto no introduce una mejora, sino un pequeño sobrecosto al agregar
un verificación más.

8
3.4. Resultados experimentales
A continuación se muestran los resultados experimentales obtenidos a
partir de la implementación de los tres algoritmos anteriores. Para cada uno
se midió el tiempo de ejecución y las comparaciones de llaves. La Figura 8
muestra los tiempos de ejecución y la Figura 7 muestra la cantida de com-
paraciones de llaves.

Figura 7: Comparaciones de llaves de algoritmos básicos de ordenamiento

n selección inserción burbuja


10000 49995000,0 25083819,8 49995000,0
20000 199990000,0 100047139,4 199990000,0
30000 449985000,0 225369069,0 449985000,0
40000 799980000,0 399704096,8 799980000,0
50000 1249975000,0 625728800,9 1249975000,0
60000 1799970000,0 901423728,1 1799970000,0

Cuadro 1: Comparaciones de llaves de algoritmos básicos de ordenamiento

9
Figura 8: Tiempo de ejecución de algoritmos básicos de ordenamiento

n selección inserción burbuja


10000 0,0856 0,0680 0,3044
20000 0,3436 0,2720 1,2277
30000 0,7792 0,6120 2,7674
40000 1,3841 1,0853 4,9167
50000 2,1549 1,6993 7,6841
60000 3,1034 2,4490 11,0535

Cuadro 2: Tiempos de ejecución (seg) de algoritmos básicos de ordenamiento

Los resultados coinciden con lo estudiado hasta el momento. En el gráfico


de comparaciones, el ordenamiento por burbuja tiene exactamente el mismo
resultado que el ordenamiento por selección, por lo que ambas lı́neas se fu-
sionan en una en el gráfico. En las tablas se aprecia este hecho.

10
4. Algoritmos avanzados de ordenamiento
En esta sección mostraremos algoritmos avanzados de ordenamiento. Por
algoritmo avanzado entenderemos un algoritmo de ordenamiento cuyo caso
promedio es O(n log2 (n)), vale decir, óptimo bajo un modelo de comparacio-
nes binarias. Veremos principalmente dos casos, MergeSort y QuickSort. Otro
algoritmo interesante es HeapSort pero lo veremos en un próximo cápitulo
junto con la descripción de un Heap binario.

4.1. MergeSort
MergeSort es un algoritmo de ordenamiento óptimo, pues toma O(n log2 (n))
en el peor caso y el caso promedio. La principal gracia de este algoritmo se
encuentra en el método merge, el cuál toma dos arreglos ordenados y los mez-
cla en un solo arreglo ordenado. Por ejemplo para los dos arreglos [2, 4, 6, 9] y
[3, 7, 8, 10], merge retornarı́a el siguiente arreglo [2, 3, 4, 6, 7, 8, 9, 10]. La idea
general es utilizar divide y venceras, separando el arreglo en 2 mitades, or-
denando cada una de ellas y luego llevando a cabo un merge sobre los dos
trozos ordenados. Cuando llamamos a ordenar una mitad, lo hacemos con el
mismo procedimiento de forma recursiva, es decir, volvemos a dividir, hace-
mos merge y ası́ sucesivamente. El caso base lo encontramos cuando tenemos
que ordenar un arreglo de tamaño 0 ó 1, que ya está ordenado. La Figura 9
muestra el algoritmo MergeSort y la figura 10 muestra el método merge. La
Figura 11 muestra graficamente la lógica del algoritmo.

MergeSort (D[1 . . . n])


1. If n = 1 Then return D
2. lef t ← M ergeSort(D[1..n/2])
3. right ← M ergeSort(D[n/2 + 1..n])
4. return merge(lef t, right)

Figura 9: MergeSort

11
Merge (lef t[1..nl ], right[1..nr ])
1. ret ← array[1..nl + nr ]
2. i ← 1, j ← 1
3. While i ≤ nl And j ≤ nr Do
4. If lef t[i] < right[j] Then
5. ret[i + j − 1] ← lef t[i]
6. i←i+1
7. Else
8. ret[i + j − 1] ← right[j]
9. j ←j+1
10. While i ≤ nl Do
11. ret[i + j − 1] ← lef t[i]
12. i←i+1
13. While j ≤ nr Do
14. ret[i + j − 1] ← right[j]
15. j ←j+1
16. return ret

Figura 10: Merge

Figura 11: Mergesort

El análisis de MergeSort es bastante simple, pues se comporta siempre de


la misma manera y por lo tanto su caso promedio es identico al peor caso, la
ecuación de recurrencia es:

n
 
T (n) = 2T +n
2
Pues para ordenar un arreglo de tamaño n ordenamos dos arreglos de
tamaño n/2 y luego hacemos merge de las dos mitades, lo que cuesta n. La

12
solución de esta ecuación se puede obtener asumiendo n de la forma 2k con
k ≥ 0 y usando la función auxiliar G(k) = T (2k ), con esto, la recurrencia
puede reescribirse de la siguiente forma:

G(k) = 2G(k − 1) + 2k

Esta ecuación puede escribirse como una telescópica si dividimos por 2k :

G(k)/2k − G(k − 1)/2k−1 = 1


k k
G(i)/2i − G(i − 1)/2i−1 =
X X
1
i=1 i=1
G(k)/2k = G(0) + k

Asumiendo que el costo de ordenar un arreglo de tamaño 0 es 0. Tenemos


que G(k) = 2k k, si volvemos a plantear la ecuación en terminos de T (n)
tenemos:

T (n) = k2k
T (n) = n log2 (n)

4.2. QuickSort
QuickSort es un algoritmo que en promedio se comporta de manera ópti-
ma, pero en su peor caso es O(n2 ). Al igual que MergeSort, QuickSort va
dividiendo el problema en dos mitades cada vez. La diferencia principal es
que la manera en que QuickSort divide el problema. Para lograr esto, lo que
se hace es pivotear. Explicaremos primero el método pivotear y luego, ha-
biendo entendido éste, será más simple entender QuickSort.

El método pivotear, descrito en la Figura 12, toma un elemento de un


arreglo, denominado pivote, y lo coloca en la posición que debiese ir en el
arreglo si es que éste estuviese ordenado. Además se impone que todos los
elementos a la izquierda del pivote son menores o iguales a éste y los que están
a la derecha sólo pueden ser mayores que éste. Este proceso puede realizarse
en O(n), basta llevar dos ı́ndices auxiliares, uno apuntando al comienzo de

13
los elementos mayores que el pivote, denominado max y otro al final de los
elementos menores o iguales al pivote, denominado min. Supondremos que el
pivote está al principio del arreglo y que iniciamos con min = 2, max = n.
Siempre verificaremos el elemento en la posición min. Si éste es menor o
igual al pivote, aumentaremos min, si no, intercambiaremos los elementos
en las posiciones min y max y decrementaremos max. Al finalizar esto, es
decir, cuando min y max se inviertan (min > max) bastará intercambiar los
elementos en las posiciones max y 1 para obtener un pivoteo completo.

Pivotear (D[1..n], p)
1. aux ← D[p]
2. D[p] ← D[1]
3. D[1] ← aux
4. min ← 2
5. max ← n
6. While min ≤ max Do
7. If D[min] ≤ D[1] Then
8. min ← min + 1
9. Else
10. aux ← D[min]
11. D[min] ← D[max]
12. D[max] ← aux
13. max ← max + 1
14. aux ← D[max]
15. D[max] ← D[1]
16. D[1] ← aux
17. return max

Figura 12: Pivotear

Teniendo el método pivotear, QuickSort se resume a pivotear el arreglo


con algún pivote al azar y luego ordenar con QuickSort la mitad a la izquierda
del pivote y lo mismo con la mitad a la derecha de éste. Si QuickSort recibe un
arreglo de largo menor o igual a uno, retorna directamente, pues ese arreglo
ya está ordenado. La Figura 13 muestra el algoritmo QuickSort. No es difı́cil

14
ver que el método pivotear toma O(n). La Figura 14 muestra graficamente
la idea general de QuickSort.

QuickSort (D[1 . . . n])


1. If n ≤ 1 Then return D
2. p ← random(1 . . . n)
3. p ← pivotear(D, p)
4. QuickSort(D[1 . . . p − 1])
5. QuickSort(D[p + 1 . . . n])

Figura 13: QuickSort

Figura 14: Quicksort

Para analizar QuickSort veremos rápidamente que ocurre en el peor caso,


luego veremos el caso promedio con más detalle.

Para el peor caso debemos notar que si QuickSort siempre elige como
pivote el mayor elemento del arreglo 2 , la mitad derecha estará de por sı́ or-
denada. La mitad izquierda por otro lado será de tamaño n − 1, por lo que
si escribimos una ecuación de recurrencia para T (n) obtendremos T (n) =
T (n − 1) + n − 1, ecuación que ya hemos estudiado anteriormente y sabemos
que cumple T (n) = n(n − 1)/2.

Para el caso promedio el análisis se vuelve más interesante. Para modelar


el comportamiento de la función de costos debemos establecer un supuesto
sobre el comportamiento de los pivotes. Debido a que no tenemos información
a priori sobre éstos, asumiremos que la probabilidad de que el pivote caiga
2
Si siempre elige el menor es lo mismo, son casos simétricos.

15
en cualquier posición del arreglo es uniforme, es decir, n1 . La recurrencia para
el caso promedio queda entonces como:

n
1X
T (n) = (T (i − 1) + T (n − i)) + n − 1
n i=1
2 n−1
X
T (n) = T (i) + n − 1
n i=0

Para resolver esta ecuación restaremos (n + 1)T (n + 1) − nT (n), de esto


se obtiene:

n
X n−1
X
(n + 1)T (n + 1) − nT (n) = 2 T (i) − 2 T (i) + 2n
i=0 i=0
(n + 1)T (n + 1) − (n + 2)T (n) = 2n
T (n + 1) T (n) 2n
− =
n+2 n+1 (n + 1)(n + 2)

Reemplazando n por i y sumando hasta n se obtiene:

n−1 n−1
!
X T (i + 1) T (i) X 2i
− =
i=0 i+2 i+1 i=0 (i + 1)(i + 2)
n−1 n−1
T (n) 2 X X 1
− T (0) = 2 −2
n+1 i=0 i + 2 i=0 i + 1
T (n)
= T (0) + 4Hn+1 − 4 − 2Hn
n+1
2
T (n) = (n + 1)(T (0) + 2Hn+1 + − 4)
n+1
T (n) = 2(n + 1)Hn+1 − 4(n + 1) + 2 + T (0)(n + 1)
T (n) = 2(n + 1)Hn+1 − 4n − 2
T (n) = 2(n + 1)Hn − 4n

Como sabemos Hn es O(log(n)), por lo tanto, en promedio QuickSort es


O(n log n).

16
Una forma de hacer menos probable el peor caso en QuickSort es modificar
la forma de elección del pivote. Lo que se utiliza normalmente es tomar la
mediana de k elementos, aumentando ası́ la probabilidad de que el pivote
se acerque a la mitad del arreglo. Es importante notar que QuickSort es un
algoritmo aleatorizado, esto influye en que el peor caso puede suceder pero
un adversario no puede obtener el peor caso de forma sistemática.

4.3. Resultados experimentales


La Figura 15 muestra la cantidad de comparaciones entre llaves. En la
Figura 16 se muestran los tiempos de ejecución para los dos algoritmos de
esta sección. Las Tablas 3 y 4 muestran los datos de comparaciones y tiempo
respectivamente. Se puede apreciar que en promedio QuickSort tiene el mejor
desempeño en tiempo, no ası́ en comparaciones de llaves. En la próxima
sección compararemos los métodos vistos hasta el momento, mostrando que
algoritmo tiene mejor desempeño en la práctica para cada posible valor de
n.

Figura 15: Comparaciones de llaves de algoritmos avanzados de ordenamiento

17
n QuickSort QuickSort med3 Mergesort
10000 156211,58 147534,34 120458,08
20000 339474,34 318846,82 260905,65
30000 533503,62 498816,58 408597,33
40000 732948,99 683826,77 561806,96
50000 942936,56 873323,04 718195,10
60000 1150326,34 1070173,77 877194,59

Cuadro 3: Comparaciones de llaves de algoritmos avanzados de ordenamiento

Figura 16: Tiempo de ejecución de algoritmos avanzados de ordenamiento

18
n QuickSort QuickSort med3 Mergesort
10000 0,0012 0,0012 0,0034
20000 0,0028 0,0027 0,0070
30000 0,0043 0,0042 0,0107
40000 0,0058 0,0058 0,0148
50000 0,0075 0,0073 0,0188
60000 0,0094 0,0092 0,0222

Cuadro 4: Tiempo de ejecución de algoritmos avanzados de ordenamiento

5. Otros modelos
Es importante recalcar que no siempre estaremos afectos a la restricción
de que un algoritmo de ordenamiento no puede realizar menos de O(n log2 n)
comparaciones en promedio. Por ejemplo si tuviesemos n elementos ente-
ros, todos en un rango [1, k] podrı́amos ordenar el conjunto manteniendo un
arreglo de k posiciones y contando cuantas veces aparece cada elemento. El
algoritmo, conocido como CountingSort no realiza comparaciones binarias
entre llaves del conjunto, sino que hace uso de la finitud del universo en el
que estamos trabajando. A continuación mostraremos este algoritmo y luego
veremos como se comporta en teorı́a. La Figura 17 muestra el algoritmo.

No es difı́cil ver que el algoritmo toma tiempo O(n + k) tanto en peor


caso como en el caso promedio.

19
CountingSort (A1...n , B1...n , k)
1. For i ← 1 to k Do
2. C[i] ← 0
3. For i ← 1 to n Do
4. C[A[i]] ← C[A[i]] + 1
5. For i ← 2 to k Do
6. C[i] ← C[i] + C[i − 1]
7. For i ← n downto 1 Do
8. B[C[A[i]]] ← A[i]
9. C[A[i]] ← C[A[i]] − 1

Figura 17: CountingSort

Además de éste, existen otros algoritmos que hacen uso de finitud y al-
canzan complejidades temporales mejores que las alcanzables por métodos
basados en comparaciones binarias de llaves.

6. Resumen
Durante este cápitulo vimos que todo algoritmo de ordenamiento basa-
do en comparaciones binarias de llaves está sujeto a una restricción, ésta
es que no podrá realizar menos de O(n log2 n) comparaciones binarias entre
llaves en promedio. Dentro de estos algoritmos estudiamos primero los algo-
ritmos básicos: selección, inserción y burbuja. Luego estudiamos algoritmos
avanzados, que corresponde a aquellos que en promedio alcanzan el óptimo
O(n log2 n) comparaciones binarias.

Por último, mencionamos brevemente un caso en que el modelo de compa-


raciones binarias puede ser dejado de lado y se puede explotar otra propiedad
para obtener una mejor complejidad temporal en un algoritmo de ordena-
miento.

20
7. Problemas
Problema 1
Ordene la siguiente lista utilizando los métodos 5 métodos vistos en este
capı́tulo.

[3, 65, 12, 23, 2]

Problema 2
Un servidor de datos nos permite actualizar la lista de clientes de la
empresa de la siguiente forma:

Se envı́a al servido un arreglo ordenado con los clientes que tenemos en


nuestro sistema.
El servidor borra los clientes que han dejado de comprar a la empresa.
El servido agrega los nuevos clientes al arreglo en cualquier parte.
Se nos envı́a de vuelta la lista de clientes.

Se quiere mantener esta lista siempre ordenada en nuestro sistema por


lo que deberemos ordenar la lista una vez que esta vuelva. Nuestro sistema
actualiza la lista cada t unidades de tiempo y se sabe que en este periodo la
empresa puede perder o ganar a lo más k clientes.

1. Si k es una constante, diseñe un algoritmo de ordenamiento para el


problema.
2. ¿Cuál es la mejor estrategia se k no es constante?

Problema 3
Considere que usted recibe una lista que incluye números y palabras. Se
le solicita que escriba un algoritmo que ordene e imprima esta lista de modo
tal que las palabras està n
c en orden alfabético creciente y los números en
orden creciente. Además, si el n-ésimo elemento en la lista de entrada es un
número, tiene que permanecer siendo un número, y si es una palabra, tiene
que seguir siendo una palabra.

21
Problema 4
Los profesores Moe, Larry y Curly, necesitados de recuperar la admiración
de sus alumnos de Algoritmos, han diseñado un nuevo método de ordena-
miento para impresionarlos. Este se invoca como StoogeSort (A,1,n):

StoogeSort(A[], i, j)
{
if (A[i] > A[j])
swap (A[i], A[j]);
if (i + 1 >= j) return;
k = floor((j - i + 1)/3);
StoogeSort(A, i, j - k); /* Primeros dos tercios */
StoogeSort(A, i + k, j); /* Ultimos dos tercios */
StoogeSort(A, i, j - k); /* Otra vez los primeros dos tercios */
}

Responda las siguientes preguntas:

1. ¿El algoritmo ordena correctamente? Justifique cuidadosamente.

2. ¿Qué complejidad tiene? Calcúlela.

3. ¿Habrı́a que felicitar a los profesores o jubilarlos anticipadamente?


Compare el algoritmo con otros e indique si tiene alguna ventaja o
no.

Problema 5
Debido al buen desempeño de inserción para conjuntos de datos pequeños,
se propone el siguiente hı́brido entre QuickSort y el método de ordenamiento
por inserción:

function QSH(A[], i, j)
{
if(j-i<=M)
insercion(A,i,j)
else
p = pivotear(A, random(i..j))
QSH(A,i,p-1)

22
QSH(A,p+1,j)
}

Encuentre el M óptimo tanto de forma teórica como experimental.

23