Está en la página 1de 14

ANÁLISIS Y DISEÑO DE ALGORITMOS

3. ALGORITMOS DE ORDENAMIENTO

Estos algoritmos nos permiten ordenar información basándonos en un criterio de


ordenamiento. En ciencia de la computación el ordenamiento de datos cumple un rol muy
importante por sí mismo o como parte de otros procedimientos más complejos.

Se han desarrollado varias técnicas de ordenamiento y cada una con características específicas
y con ventajas y desventajas.

 ALGORITMOS ESTABLES

a. ORDENACIÓN DE BURBUJA

La Ordenación de burbuja funciona revisando cada elemento de la lista que va a ser


ordenada con el siguiente, intercambiándolos de posición si están en el orden
equivocado. Es necesario revisar varias veces toda la lista hasta que no se necesiten más
intercambios, lo cual significa que la lista está ordenada.

 En el caso óptimo, con los datos ya ordenados, el algoritmo sólo


efectuará n comparaciones. Por lo tanto, la complejidad en el caso óptimo es en
Θ(n).
 En el caso medio, la complejidad de este algoritmo es también en Θ(n2).
 En el peor caso, con los datos ordenados a la inversa, la complejidad es en Θ(n2).

Hay variantes interesantes del ordenamiento de burbuja como el Shaker sort, el


ordenamiento de Oyelami o el Comb sort.

Pseudocódigo

PROCEDIMIENTO bubble_sort ( vector a[1:n])


iteración ← 0
REPETIR
permut ← FALSO
PARA i VARIANDO DE 1 HASTA n - 1 - iteración HACER
SI a[i] > a[i+1] ENTONCES
intercambiar a[i] Y a[i+1]
permut ← VERDADERO
FIN SI
FIN PARA
iteración ← iteración + 1
MIENTRAS QUE permut = VERDADERO

En C

void bubble_sort(int* vector)

1
{
int iteración = 0;
bool permutation = true;
int actual;

while ( permutation) {
permutation = false;
iteración ++;
for (actual=0;actual<20-iteración;actual++) {
if (vector[actual]>vector[actual+1]){
permutation = true;
// Intercambiamos los dos elementos
int temp = vector[actual];
vector[actual] = vector[actual+1];
vector[actual+1] = temp;
}
}
}
}

En Python

def bubble_sort(vector):
permutation = True
iteración = 0
while permutation == True:
permutation = False
iteración = iteración + 1
for actual in range(0, len(vector) - iteración):
if vector[actual] > vector[actual + 1]:
permutation = True
# Intercambiamos los dos elementos
vector[actual], vector[actual + 1] = \
vector[actual + 1],vector[actual]
return vector

b. ORDENAMIENTO POR INSERCIÓN

El ordenamiento por inserción es una manera muy natural de ordenar para un ser
humano, y puede usarse fácilmente para ordenar un mazo de cartas numeradas en
forma arbitraria.

La idea de este algoritmo de ordenación consiste en ir insertando un elemento de la lista


ó un arreglo en la parte ordenada de la misma, asumiendo que el primer elemento es la
parte ordenada, el algoritmo ira comparando un elemento de la parte desordenada de
la lista con los elementos de la parte ordenada, insertando el elemento en la posición

2
correcta dentro de la parte ordenada, y así sucesivamente hasta obtener la lista
ordenada.

 En el caso óptimo, con los datos ya ordenados, el algoritmo sólo efectuará n


comparaciones. Por lo tanto, la complejidad en el caso óptimo es en Θ(n).
 En el caso medio, la complejidad de este algoritmo es también en Θ(n2).
 En el peor caso, con los datos ordenados a la inversa, se necesita realizar (n-1)
+ (n-2) + (n-3) .. + 1 comparaciones e intercambios, o (n2-n)/2. Por lo tanto, la
complejidad es en Θ(n2).

Pseudocódigo

PROCEDIMIENTO Insertion_sort ( Vector a[1:n])


PARA i VARIANDO DE 2 HASTA n HACER
INSERTAR a[i] EN SU LUGAR EN a[1:i-1];
FIN PROCEDIMIENTO;

En C

void Insertion_sort(int* t)
{
int i, j;
int actual;

for (i = 1; i < 20; i++) {


actual = t[i];
for (j = i; j > 0 && t[j - 1] > actual; j--) {
t[j] = t[j - 1];
}
t[j] = actual;
}
}

En Python

def Insertion_sort(Vector):
for i in range(1,len(Vector)):
actual = Vector[i]
j=i
#Desplazamiento de los elementos de la matriz }
while j>0 and Vector[j-1]>actual:
Vector[j]=Vector[j-1]
j = j-1
#insertar el elemento en su lugar
Vector[j]=actual

c. EL ORDENAMIENTO DE BURBUJA BIDIRECCIONAL

3
El ordenamiento de burbuja bidireccional (también llamado "método de la sacudida" o
"coctail sort" o "shaker sort") es un algoritmo de ordenamiento que surge como una
mejora del algoritmo ordenamiento de burbuja.

Como se ve el algoritmo de ordenación por burbuja, los números grandes se están


moviendo rápidamente hasta al final de la lista (estas son las "liebres"), pero que los
números pequeños (las "tortugas") se mueven sólo muy lentamente al inicio de la lista.

Una solución es de ordenar con el método de burbuja y cuando llegamos al final de la


primera iteración, no volver a realizar el cálculo desde el principio, sino que
empezaremos desde el final hasta al inicio. De esta manera siempre se consigue que
tanto los números pequeños como los números grandes se desplacen a los extremos de
la lista lo más rápido posible.

 En el caso óptimo, con los datos ya ordenados, el algoritmo sólo


efectuará n comparaciones. Por lo tanto, la complejidad en el caso óptimo es en
Θ(n).
 En el caso medio, la complejidad de este algoritmo es también en Θ(n2).
 En el caso desfavorable, con los datos ordenados a la inversa, la complejidad es en
Θ(n2).

Seudocódigo

PROCEDIMIENTO cocktail_sort ( VECTOR a[1:n])

dirección ← 'frontal', comienzo ← 1, fin ← n-1, actual ← 1

REPETIR
permut ← FALSO
REPETIR
SI a[actual] > a[actual + 1] ENTONCES
intercambiar a[actual] Y a[actual + 1]
permut ← VERDADERO
FIN SI
SI (dirección='frontal') ENTONCES
actual ← actual + 1
SI NO
actual ←actual - 1
FIN SI
MIENTRAS QUE ((dirección='frontal') Y (actual<fin)) OU ((dirección='final) O
(actual>comienzo))
SI (dirección='frontal') ENTONCES
dirección ← 'final'
fin ← fin - 1
SI NO
dirección ← 'frontal'
comienzo ← comienzo + 1
FIN SI

4
MIENTRAS permut = VERDADERO

FIN PROCEDIMIENTO

En C

typedef int bool;


enum { false, true };

void cocktail_sort(int* vector) {


bool permutation;
int actual=0, dirección=1;
int comienzo=1, fin=19;
do {
permutation=false;
while (((dirección==1) && (actual<fin)) || ((dirección==-1) &&
(actual>comienzo))) {
actual += dirección;
if (vector[actual]<vector[actual-1]) {
int temp = vector[actual];
vector[actual]=vector[actual-1];
vector[actual-1]=temp;
permutation=true;
}
}
if (dirección==1) fin--; else comienzo++;
dirección = -dirección;
} while (permutation);
}

En Python

def cocktail_sort(vector):
permutation,dirección,actual = True,1,0
comienzo,fin = 0,len(vector)-2
while permutation == True:
permutation = False
while (actual<fin and dirección==1) or \
(actual>comienzo and dirección==-1) :
# Prueba si intercambio
if vector[actual] > vector[actual + 1]:
permutation = True
# Intercambiamos los dos elementos
vector[actual], vector[actual + 1] = \
vector[actual + 1],vector[actual]
actual = actual + dirección
# Cambiar la dirección de desplazamiento
if dirección==1:

5
fin = fin - 1
else:
comienzo = comienzo + 1
dirección = -dirección
return vector

d. ORDENACIÓN "GNOME"

El algoritmo de ordenación conocido como Gnome_sort fue inventado por Hamid


Sarbazi-Azad, (profesor de la universidad de Sharif, una de las mayores universidades de
Irán) quien lo desarrolló en el año 2000 y al que llamó Stupid sort (Ordenamiento
estúpido).

Cuando Dick Grune lo reinventó y documentó, no halló evidencias de que existiera y en


palabras suyas, dijo de él "the simplest sort algorithm" (es el algoritmo más simple) y
quizás tenga razón, pues lo describió en sólo cuatro líneas de código. Dick Grune se basó
en los gnomos de jardín holandés, en cómo se colocan en los maceteros y de ahí también
el nombre que le dio.

El algoritmo es similar a la ordenación por inserción, excepto que, en lugar de insertar


directamente el elemento a su lugar apropiado, el algoritmo realiza una serie de
permutaciones, como en el ordenamiento de burbuja.

 En el caso óptimo, con los datos ya ordenados, el algoritmo sólo


efectuará n comparaciones. Por lo tanto, la complejidad en el caso óptimo es en
Θ(n).
 En el caso medio, la complejidad de este algoritmo es también en Θ(n2)
 En el peor caso, con los datos ordenados a la inversa, la complejidad es en Θ(n2).

Seudocódigo

PROCEDIMIENTO Gnome_sort(vector[])
pos := 1
MIENTRAS pos < longitud(vector)
SI (vector[pos] >= vector[pos-1])
pos := pos + 1
SI NO
intercambiar vector[pos] Y vector[pos-1]
SI (pos > 1)
pos := pos - 1
FIN SI
FIN SI
FIN MIENTRAS

En C
void burbuja(int* vector, int p) {

6
int i_b = p;
while ((i_b>0) && (vector[i_b]<vector[i_b - 1])) {
int t = vector[i_b -1];
vector[i_b - 1]= vector[i_b];
vector[i_b ] = t;
i_b --;
}
}

void Gnome_sort (int* vector) {


for (int i_i=0;i_i<20;i_i++) burbuja(vector,i_i);
}

En Python

def Gnome_sort(vector):
i_b,i_i,taille = 1,2,len(vector)
while i_b < taille:
if vector[i_b-1] <= vector[i_b]:
i_b,i_i = i_i, i_i+1
else:
vector[i_b-1],vector[i_b] = vector[i_b],vector[i_b-1]
i_b -= 1
if i_b == 0:
i_b,i_i = i_i, i_i+1
return vector

 ALGORITMOS INESTABLES

e. ORDENAMIENTO POR SELECCIÓN

Consiste en encontrar el menor de todos los elementos del vector e intercambiarlo con
el que está en la primera posición. Luego el segundo más pequeño y así sucesivamente
hasta ordenarlo todo.

 Cada búsqueda requiere comparar todos los elementos no clasificados, de


manera que el número de comparaciones C(n) no depende del orden de los
términos, si no del número de términos; por lo que este algoritmo presenta un
comportamiento constante independiente del orden de los datos. C(n)= n(n-
1)/2. Luego la complejidad es del orden Θ(n2).

En Seudocódigo

PROCEDIMIENTO selection_sort ( Vector a[1:n])


PARA i VARIANDO DE 1 HASTA n - 1 HACER
ENCONTRAR [j] EL ELEMENTO MÁS PEQUEÑO DE [i + 1:n];

7
INTERCAMBIAR [j] Y [i];
FIN PROCEDIMIENTO;

En C

void selection_sort(int *vector, int taille)


{
int actual, mas_pequeno, j, temp;

for (actual = 0; actual < taille - 1; actual++)


{
mas_pequeno = actual;
for (j = actual 1; j < taille; j++)
if (vector[j] < vector[mas_pequeno])
mas_pequeno = j;
temp = vector[actual];
vector[actual] = vector[mas_pequeno];
vector[mas_pequeno] = temp;
}
}

En Python

def selection_sort(vector):
nb = len(vector)
for actual in range(0,nb):
mas_pequeno = actual
for j in range(actual+1,nb) :
if vector[j] < vector[mas_pequeno] :
mas_pequeno = j
if min is not actual :
temp = vector[actual]
vector[actual] = vector[mas_pequeno]
vector[mas_pequeno] = temp

f. ORDENACIÓN DE SHELL

La ordenación de Shell pertenece a los métodos de clasificación avanzados, nombrado


así en honor del ingeniero y matemático estadounidense Donald Shell que la propuso
en 1959.

Este método utiliza una segmentación entre los datos. Funciona comparando elementos
que estén distantes; la distancia entre comparaciones decrece conforme el algoritmo se
ejecuta hasta la última fase, en la cual se comparan los elementos adyacentes, por esta
razón se le llama ordenación por disminución de incrementos.

8
La ordenación de Shell usa una secuencia, h1, h2, . . ., ht, conocida como la secuencia de
incrementos. Al principio de todo proceso, se fija una secuencia decreciente de
incrementos. Cualquier secuencia funcionará en tanto que empiece con un incremento
grande, pero menor al tamaño del arreglo de los datos a ordenar, y que el último valor
de dicha secuencia sea 1.

Una elección muy común (pero no tan eficiente) para la secuencia de incrementos es
adoptar la secuencia sugerida por Shell: h1 = 1, hn+1 = 3hn+1.

Existen diversas secuencias de incrementos:

o Shell, 1959
o Frank & Lazarus, 1960
o Hibbard, 1963
o Papernov & Stasevich, 1965
o Pratt (1), 1971
o Pratt (2), 1971
o Incerpi & Sedgewick, 1985
o Sedgewick (1), 1986
o Sedgewick (2), 1986
o Gonnet & Baeza-Yates, 1991
o Tokuda, 1992
o Ciura, 2001
o Pigeon (1), 2000
o Pigeon(2), 2000

 La complejidad depende de la secuencia de incrementos. Las siguientes tablas


muestran la complejidad en términos de diferentes intervalos. N indica el
número de elementos a ordenar, k el número de orden en la secuencia.

Seudocódigo

PROCEDURE tri_Insertion ( Tableau a[1:n],gap,debut)


POUR i VARIANT DE debut A n AVEC UN PAS gap FAIRE
INSERER a[i] à sa place dans a[1:i-1];
FIN PROCEDURE;

PROCEDURE tri_shell ( Tableau a[1:n])


POUR gap DANS (6,4,3,2,1) FAIRE
POUR debut VARIANT DE 0 A gap - 1 FAIRE
tri_Insertion(Tableau,gap,debut);
FIN POUR;
FIN POUR;
FIN PROCEDURE;

En C

void tri_insertion(int* t, int gap, int debut)

9
{
int j,en_cours;
for (int i = gap + debut; i < 20; i+=gap) {
en_cours = t[i];
for (j = i; j >= gap && t[j - gap] > en_cours; j-=gap) {
t[j] = t[j - gap];
}
t[j] = en_cours;
}
}

void tri_shell(int* t) {
int intervalles[5]={6,4,3,2,1};
for (int ngap=0;ngap<5;ngap++) {
for (int i=0;i<intervalles[ngap];i++)
tri_insertion(t,intervalles[ngap],i);
}
}

En Python

def tri_insertion(tableau, gap, debut):


for i in range(gap + debut,len(tableau),gap):
en_cours = tableau[i]
j=i
#décalage des éléments du tableau }
while j>0 and tableau[j-gap]>en_cours:
tableau[j]=tableau[j-gap]
j = j-gap
#on insère l'élément à sa place
tableau[j]=en_cours

def tri_shell (tableau):


for gap in [6,4,3,2,1]:
# Pour chaque sous-tableau ...
for debut in range(0,gap):
#... on fait un tri par insertion
tri_insertion(tableau,gap,debut)

g. ORDENACIÓN DEL PEINE O DE DOBOSIEWICZ

En 1980, Wlodzimierz Dobosiewicz propuso este algoritmo en su breve artículo "An


Efficient Variation of Bubble Sort", Information Processing Letters, vol. 11, num. 1, 1980.
En él escribió literalmente: "Bubble sort can be improved in yet another way, which is
similar to Shell’s version of the insertion sort." ("La ordenación por burbuja se puede
mejorar de otra manera adicional, que es similar a la versión de Shell de la ordenación
por inserción").

10
Posteriormente fue redescubierto y popularizado por Stephen Lacey y Richard Box en
un artículo publicado por la revista Byte en abril de 1991.

En el ordenamiento de burbuja, cuando dos elementos cualesquiera se comparan,


siempre tienen un espacio (distancia entre ellos) de 1. La idea básica del algoritmo
CombSort es que el espacio pueda ser mucho mayor de uno.

El ordenamiento Shell también se basa en esta idea, pero es una modificación del
algoritmo de ordenamiento por inserción más que del algoritmo de ordenamiento de
burbuja.

La elección del factor de descuento es crucial para la eficacia de este algoritmo. En


general, se necesita un factor de reducción de entre 1,25 y 1,33.

La complejidad de este algoritmo es muy difícil de calcular. En general se considera que,


en el mejor de los casos, es lineal (O( n)) , y que, en el caso peor es Θ(n2). En caso medio
sería en O(n log n).

Seudocódigo

PROCEDIMIENTO comb_sort ( VECTOR a[1:n])


gap ← n
REPETIR
permut ← FALSO
gap ← gap / 1.3
SI gap < 1 ENTONCES gap ← 1
PARA i VARIANDO DE 1 HASTA n CON INCRECREMENT DE gap HACER
SI a[i] > a[i+gap] ENTONCES
intercambiar a[i] Y a[i+gap]
permut ← VERDADERO
FIN SI
FIN POUR
MIENTRAS QUE permut = VERDADERO O gap > 1

En C

typedef int bool;


enum { false, true };

void comb_sort(int* vector)


{
int gap = 20;
bool permutación = true;
int actual;

11
while (( permutación) || (gap>1)) {
permutación = false;
gap = gap / 1.3;
if (gap<1) gap=1;
for (actual=0;actual<20-gap;actual++) {
if (vector[actual]>vector[actual+gap]){
permutación = true;
// Intercambiamos los dos elementos
int temp = vector[actual];
vector[actual] = vector[actual+gap];
vector[actual+gap] = temp;
}
}
}
}

En Python

import math
def comb_sort(vector):
permutación = True
gap = len(vector)
while (permutación == True) or (gap>1):
permutación = False
gap = math.floor(gap / 1.3)
if gap<1: gap = 1
for actual in range(0, len(vector) - gap):
if vector[actual] > vector[actual + gap]:
permutación = True
# Intercambiamos los dos elementos
vector[actual], vector[actual + gap] = \
vector[actual + gap],vector[actual]
return vector

h. ORDENAMIENTO RÁPIDO (QUICKSORT)

El ordenamiento rápido (también llamado ordenamiento de Hoare o quicksort en inglés)


es un algoritmo creado por el científico británico en computación Tony Hoare y basado
en la técnica de divide y vencerás. Esta es la técnica quizás la más eficiente y en ella que
en la mayoría de los casos da mejores resultados

El algoritmo fundamental es el siguiente:

Elegir un elemento de la lista de elementos a ordenar, al que llamaremos pivote.

12
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. 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.

Lo más importante del algoritmo es elegir bien el pivote. En el algoritmo, se elige el


pivote que se encuentra al final de la lista. Sin embargo, otras estrategias son posibles.

 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 caso promedio, el orden es O(n log n). Y no es extraño, que la mayoría de
optimizaciones que se aplican al algoritmo se centren en la elección del pivote.
 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, 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.

Seudocódigo

ROCEDIMIENTO quick_sort (vector [1:n], izquierda, derecho )


COMIENZO
(* separación entre los elementos más pequeños y más grandes como pivote *)
pared ← izquierda;
(* el elemento de pivote es la más a la derecha *)
pivote ← vector[derecho];
mover a la izquierda de pared, todos los elementos más pequeños
mover a la derecha de pared, todos los elementos más grandes
(* colocando el pivote *)
colocar el pivote en el lugar de pared
(* Se continúa con la recursividad *)
SI (izquierda<pared-1) ENTONCES quick_sort(vector,izquierda,pared-1);
SI (pared+1<derecho) ENTONCES quick_sort(vector,pared,derecho);
FIN;

13
En C

void quick_sort (int *vector, int tamano) {


int pared, actual, pivote, tmp;
if (tamano < 2) return;
// el elemento de pivote es la más a la derecha
pivote = vector[tamano - 1];
pared = actual = 0;
while (actual<tamano) {
if (vector[actual] <= pivote) {
if (pared != actual) {
tmp=vector[actual];
vector[actual]=vector[pared];
vector[pared]=tmp;
}
pared ++;
}
actual ++;
}
quick_sort(vector, pared - 1);
quick_sort(vector + pared - 1, tamano - pared + 1);
}

En Python

def quick_sort(vector):
if not vector:
return []
else:
pivote = vector[-1]
menor = [x for x in vector if x < pivote]
mas_grande = [x for x in vector[:-1] if x >= pivote]
return quick_sort(menor) + [pivote] + quick_sort(mas_grande)

14