Ordenamiento
Estructuras de datos
Antecedentes
Un archivo de tamaño n es una secuencia de elementos r[0], r[1], …, r[n-1], a cada elemento del archivo se le llama registro.
A cada registro r[i] se le asocia una clave o llave k[i].
Se dice que el archivo está ordenado de acuerdo a la llave, si i<j implica que k[i] precede a k[j] para algún ordenamiento de las llaves.
Un ordenamiento es interno si los registros están en la memoria principal, o externo si se encuentran en almacenamiento auxiliar.
Una técnica de ordenamiento es estable si para todos los registros i y j tales que k[i] = k[j], si r[i] precede a r[j] en el archivo original, entonces r[i] precede a r[j] en el archivo ordenado.
Un ordenamiento ocurre ya sea sobre los mismos registros o sobre una tabla auxiliar de apuntadores (ordenamiento por dirección).
4 DDD
2 BBB
1 AAA
5 EEE
3 CCC
llaves Otros campos
Registro 1
Registro 2
Registro 3
Registro 4
Registro 5
4 DDD
2 BBB
1 AAA
5 EEE
3 CCC
Archivo original Archivo ordenado
4 DDD
2 BBB
1 AAA
5 EEE
3 CCC
Tabla original de apuntadores
Registro 1
Registro 2
Registro 3
Registro 4
Registro 5
Tabla ordenada de apuntadores
Eficiencia
Para elegir el método de ordenación se deben considerar los siguientes aspectos
Tiempo que se debe invertir para codificar el programa
La cantidad de tiempo de ejecución
El espacio necesario en memoria para ejecutar el programa
Orden de ejecución
n a=0.01n^2 b=10n a+b (a+b)/n^2
10 1 100 101 1.01
50 25 500 525 0.21
100 100 1000 1100 0.11
500 2500 5000 7500 0.03
1000 10000 10000 20000 0.02
5000 250000 50000 300000 0.012
10000 1000000 100000 1100000 0.011
50000 25000000 500000 25500000 0.0102
100000 100000000 1000000 101000000 0.0101
500000 2500000000 5000000 2505000000 0.01002
Comparación de orden de ejecución de funciones típicas
n n log n n^2
10 10 100
50 85 2500
100 200 10000
500 1349 250000
1000 3000 1000000
5000 18495 25000000
10000 40000 100000000
50000 234949 2500000000
100000 500000 1E+10
500000 2849485 2.5E+11
1000000 6000000 1E+12
5000000 33494850 2.5E+13
10000000 70000000 1E+14
Métodos de ordenación
Existen tres formas básicas:
1. Ordenación por inserción. 2. Ordenación por selección. 3. Ordenación por intercambio.
Estudiaremos una algoritmo simple y otro complejo de cada técnica.
Inserción directa
44 55 12 42 94 18 06 67
i=2 44 55 12 42 94 18 06 67
i=3 12 44 55 42 94 18 06 67
i=4 12 42 44 55 94 18 06 67
i=5 12 42 44 55 94 18 06 67
i=6 12 18 42 44 55 94 06 67
i=7 06 12 18 42 44 55 94 67
i=8 06 12 18 42 44 55 67 94
Se selecciona la llave más pequeña y se inserta en el lugar adecuado.
Algoritmo en Cvoid InsercionDirecta(){ int i,j:indice; item x, y; for(i = 1; i<n; i++){ x = a[i]; y = x; j = i-1; while(j>=0&&x<a[j]){ a[j+1] = j>=0?a[j]:y; j = j-1; } a[j+1] = x; }}
RendimientoEl número de comparaciones y movimientos es el siguiente:
Cmin = n – 1 Mmin = 2(n – 1) Cmed = (n2 + n – 2)/4 Mmed = (n2 + 9n – 10)/4 Cmax = (n2 + n) – 1 Mmax = (n2 + 3n – 4)
Selección directa
44 55 12 42 94 18 06 67
06 55 12 42 94 18 44 67
06 12 55 42 94 18 44 67
06 12 18 42 94 55 44 67
06 12 18 42 94 55 44 67
06 12 18 42 44 55 94 67
06 12 18 42 44 55 94 67
06 12 18 42 44 55 67 94
Se selecciona el elemento con menor clave y se intercambia con el primero, luego por el segundo, y así sucesivamente.
Código en C
void SeleccionDirecta(int a[],int n){ int i,j,k; int x; for(i= 0;i<n-1;i++){ k = i; x = a[i]; for(j = i+1;j<n;j++) if(a[j]<x){ k = j; x = a[j]; } a[k] = a[i]; a[i] = x; }}
Rendimiento Número de comparaciones:
C = (n*n - n)/2
Número de movimientos:
Mmin = 3(n - 1) Mmed = n(ln n + ) Mmax = trunc(n2/4) + 3(n - 1)
Intercambio directo (burbuja)
i=2 i=3 i=4 i=5 i=6 i=7 i=844 06 06 06 06 06 06 0655 44 12 12 12 12 12 1212 55 44 18 18 18 18 1842 12 55 44 42 42 42 4294 42 18 55 44 44 44 4418 94 42 42 55 55 55 5506 18 94 67 67 67 67 6767 67 67 94 94 94 94 94
Se intercambian los valores consecutivos del archivo comenzando por el final y se repite hasta haber intercambiado todos los elementos
Código en C
void Burbuja(int a[],int n){ int i,j,x; for(i = 1;i<n;i++){ for(j = n-1;j>=i;j--) if(a[j-1]>a[j]){ x = a[j-1]; a[j-1] = a[j]; a[j] = x; } }}
La sacudidaUna mejora de la burbuja es la sacudida. Consiste en cambiar el orden de recorrido en cada paso de la burbuja.
iz=2 3 3 4 4 de=8 8 7 7 4 44 06 06 06 06 55 44 44 12 12 12 55 12 44 18 42 12 42 18 42 94 42 55 42 44 18 94 18 55 55 06 18 67 67 67 67 67 94 94 94
Eficiencia
El número de comparaciones es:
C = 3/4(n2 – n)
El número de movimientos es:
Mmin = 0Mmed = 3(n2 – n)/4Mmax = 3(n2 – n)/2
Código en C
void Sacudida(int a[],int n){ int j,k,iz,de,x; iz = 1; de = n-1; k = n; do{ for(j=de;j>=iz;j--) if(a[j-1]>a[j]){ x = a[j-1]; a[j-1] = a[j]; a[j] = x; k = j; }
iz = k+1; for(j = iz; j<=de;j++) if(a[j-1]>a[j]){ x = a[j-1]; a[j-1] = a[j]; a[j] = x; k = j; } de = k-1; }while(iz<=de);}
Ordenamiento de ShellEl método es similar al de inserción directa, la variante es que se realiza con intervalos decresientes hasta hacerlo de 1 en 1.
44 55 12 42 94 18 06 67
44 18 06 42 94 55 12 67
06 18 12 42 44 55 94 67
06 12 18 42 44 55 67 94
EficienciaLa eficiencia del algoritmo depende en gran medida de la elección de los incrementos utilizados.
Knuth recomienda la secuencia: 1, 4, 13, 40, 121, … donde
hk–1= 3hk + 1, ht = 1 y t = log3 n – 1
También la secuencia: 1, 3, 7, 15, 31, … donde
hk–1= 2hk + 1, ht = 1 y t = log2 n – 1
El algoritmo tiene un comportamiento proporcional a n1.2.
Código en Cvoid Shell(int a[],int n){ int incr,i,j,k,span,y,ninc,*inc; ninc = (int)(log(n)/log(3)-1); inc = (int*)malloc(ninc*sizeof(int)); inc[ninc-1] = 1; for(i=ninc-1;i>0;i--) inc[i-1] = 3*inc[i]+1; for(incr=0;incr<ninc;incr++){ span = inc[incr]; for(j = span;j<n;j++){ y = a[j]; for(k = j-span;k>=0 and y<a[k];k -= span) a[k+span] = a[k]; a[k+span] = y; } }}
Ordenamiento del peine
El ordenamiento del peine es una mejora respecto al de la burbuja.
Se ejecuta la burbuja con incrementos decrecientes.
El incremento inicial se sugiere que sea n/1.3, y a cada paso se divide entre 1.3 hasta tener incrementos iguales a 1.
A cada paso se ordenan los elementos separados por el incremento reduciendo el total de trabajo a realizar.
El último paso se ejecuta con incremento de una unidad asegurando que se ordenen los elementos que no estén en orden todavía,
44 55 12 42 94 18 06 67
06 55 12 42 94 18 44 67
06 18 12 42 94 55 44 67
06 18 12 42 67 55 44 94
06 18 12 42 44 55 67 94
06 12 18 42 44 55 67 94
Código en C
void CombSort(int a[],int n){ int gap,i,j,h; bool swap; gap = n; do{ gap = (int)(gap/1.3); if(gap<1) gap = 1; swap = false;
for(i = 1;i<n-gap;i++){ j = i+gap; if(a[i]>a[j]){ h = a[i]; a[i] = a[j]; a[j] = h; swap = swap+1; } } }while((swap)and(gap>1));}
EL rápidoEl método rápido (quick Sort) se basa en la partición de un arreglo, esta consiste en elegir el elemento de la mitad y colocar todos los elementos más pequeños que él a la izquierda y los más grandes a la derecha. 44 55 12 42 94 06 18 67
18 06 12 42 94 55 44 67 El proceso se repite para la parte izquierda y derecha de la partición, y re repite recursivamente hasta tener el arreglo ordenado.
Código en Cvoid rapido(int a[],int iz, int de){ int i,j,x,w; i = iz; j = de; x = a[(iz+de)/2]; do{ while(a[i]<x) i++; while(x<a[j]) j--; if(i<=j){ w = a[i]; a[i] = a[j]; a[j] = w; i++; j--; } }while(i<=j);
if(iz<j) rapido(a,iz,j); if(i<de) rapido(a,i,de);}
Eficiencia
El rápido tiene un comportamiento proporcional a n log n.
Desgraciadamente en el peor de los casos pude tener un comportamiento proporcional a n2.