Transcript Ordenación

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).
llaves
Otros campos
Registro 1
4
DDD
1
AAA
Registro 2
2
BBB
2
BBB
Registro 3
1
AAA
3
CCC
Registro 4
5
EEE
4
DDD
Registro 5
3
CCC
5
EEE
Archivo original
Archivo ordenado
Tabla original de apuntadores
Registro 1
Tabla ordenada de apuntadores
4
DDD
2
BBB
Registro 3
1
AAA
Registro 4
5
EEE
Registro 5
3
CCC
Registro 2
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
Se selecciona la llave más pequeña y se inserta en el lugar adecuado.
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
Algoritmo en C
void 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;
}
}
Rendimiento
El número de comparaciones y movimientos es el siguiente:
Cmin = n – 1
Cmed = (n2 + n – 2)/4
Cmax = (n2 + n) – 1
Mmin = 2(n – 1)
Mmed = (n2 + 9n – 10)/4
Mmax = (n2 + 3n – 4)
Selección directa
Se selecciona el elemento con menor clave y se intercambia con
el primero, luego por el segundo, y así sucesivamente.
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
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 + g)
Mmax = trunc(n2/4) + 3(n - 1)
Intercambio directo (burbuja)
Se intercambian los valores consecutivos del archivo
comenzando por el final y se repite hasta haber
intercambiado todos los elementos
44
55
12
42
94
18
06
67
i=2
06
44
55
12
42
94
18
67
i=3
06
12
44
55
18
42
94
67
i=4
06
12
18
44
55
42
67
94
i=5
06
12
18
42
44
55
67
94
i=6
06
12
18
42
44
55
67
94
i=7
06
12
18
42
44
55
67
94
i=8
06
12
18
42
44
55
67
94
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 sacudida
Una mejora de la burbuja es la sacudida. Consiste en cambiar
el orden de recorrido en cada paso de la burbuja.
iz=2
de=8
44
55
12
42
94
18
06
67
3
8
06
44
55
12
42
94
18
67
3
7
06
44
12
42
55
18
67
94
4
7
06
12
44
18
42
55
67
94
4
4
06
12
18
42
44
55
67
94
Eficiencia
El número de comparaciones es:
C = 3/4(n2 – n)
El número de movimientos es:
Mmin = 0
Mmed = 3(n2 – n)/4
Mmax = 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;
iz = k+1;
k = n;
for(j = iz; j<=de;j++)
do{
if(a[j-1]>a[j]){
for(j=de;j>=iz;j--)
x = a[j-1];
if(a[j-1]>a[j]){
a[j-1] = a[j];
x = a[j-1];
a[j] = x;
a[j-1] = a[j];
k = j;
a[j] = x;
}
k = j;
de = k-1;
}
}while(iz<=de);
}
Ordenamiento de Shell
El 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
Eficiencia
La 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 C
void 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ápido
El 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 C
void 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)
if(iz<j)
i++;
rapido(a,iz,j);
while(x<a[j])
if(i<de)
j--;
rapido(a,i,de);
if(i<=j){
}
w = a[i];
a[i] = a[j];
a[j] = w;
i++;
j--;
}
}while(i<=j);
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.