Cap. 9. Ordenarprofesores.elo.utfsm.cl/~tarredondo/info/datos-algoritmos/c9.pdf · de complejidad...

42
1 Profesor Leopoldo Silva Bijit 30-06-2008 Capítulo 9 Ordenar Se desea ordenar una serie de estructuras, que contienen información, de acuerdo al valor de un campo de la estructura denominado clave. Se denomina ordenamiento interno si se ordena un arreglo de estructuras; y es externo si se ordena un archivo de estructuras. Se denomina ordenamiento interno in situ, a aquel que no requiere espacio adicional. Es decir se ordena en el mismo arreglo. Suelen tenerse dos medidas de eficiencia: C(n) el número de comparaciones de claves, y M(n) el número de movimientos de datos, necesarios para lograr el ordenamiento. Se denomina métodos directos a aquellos algoritmos primitivos de ordenamiento, que suelen ser fáciles de entender, y que tienen buen comportamiento para n pequeños. Sin embargo suelen ser de complejidad O(n 2 ). Suelen ser el punto de partida de los métodos más elaborados que suelen ser O(n*log 2 (n) ). En los diversos algoritmos de ordenamiento es importante considerar su complejidad en el peor caso y en promedio. Si se requiere un ordenamiento ascendente, cada vez que en el arreglo se tenga: i < j con a[i] > a[j] se tiene una inversión. Los algoritmos de ordenamiento eliminan todas las inversiones. Figura 9.1. Inversiones. i j

Transcript of Cap. 9. Ordenarprofesores.elo.utfsm.cl/~tarredondo/info/datos-algoritmos/c9.pdf · de complejidad...

Page 1: Cap. 9. Ordenarprofesores.elo.utfsm.cl/~tarredondo/info/datos-algoritmos/c9.pdf · de complejidad O(n2). Suelen ser el punto de partida de los métodos más elaborados que suelen

1

Profesor Leopoldo Silva Bijit 30-06-2008

Capítulo 9

Ordenar

Se desea ordenar una serie de estructuras, que contienen información, de acuerdo al valor de un

campo de la estructura denominado clave.

Se denomina ordenamiento interno si se ordena un arreglo de estructuras; y es externo si se

ordena un archivo de estructuras.

Se denomina ordenamiento interno in situ, a aquel que no requiere espacio adicional. Es decir se

ordena en el mismo arreglo.

Suelen tenerse dos medidas de eficiencia: C(n) el número de comparaciones de claves, y M(n) el

número de movimientos de datos, necesarios para lograr el ordenamiento.

Se denomina métodos directos a aquellos algoritmos primitivos de ordenamiento, que suelen ser

fáciles de entender, y que tienen buen comportamiento para n pequeños. Sin embargo suelen ser

de complejidad O(n2). Suelen ser el punto de partida de los métodos más elaborados que suelen

ser O(n*log2(n) ).

En los diversos algoritmos de ordenamiento es importante considerar su complejidad en el peor

caso y en promedio.

Si se requiere un ordenamiento ascendente, cada vez que en el arreglo se tenga:

i < j con a[i] > a[j]

se tiene una inversión.

Los algoritmos de ordenamiento eliminan todas las inversiones.

Figura 9.1. Inversiones.

i j

Page 2: Cap. 9. Ordenarprofesores.elo.utfsm.cl/~tarredondo/info/datos-algoritmos/c9.pdf · de complejidad O(n2). Suelen ser el punto de partida de los métodos más elaborados que suelen

2 Estructuras de Datos y Algoritmos

Profesor Leopoldo Silva Bijit 30-06-2008

9.1. Métodos directos.

9.1.1. Selección.

9.1.1.1. Algoritmo:

Repetir hasta que quede un arreglo de un elemento:

Seleccionar el elemento de la última posición del arreglo. Sea j su cursor.

Buscar el elemento con la mayor clave en el resto del arreglo

Intercambiar el elemento de la última posición del arreglo con el de mayor clave.

Disminuir el rango del arreglo en uno.

Buscar el elemento con la mayor clave en el resto del arreglo puede traducirse por:

for (i=j-1; i>=0; i--)

{ Seleccione el mayor entre a[j-1] y a[0]; }

Se definen los siguientes tipos que serán empleados en las funciones:

typedef int Tipo; /* tipo de item del arreglo */

typedef int Indice; /* tipo del índice */

9.1.1.2. Operación.

/* Ordena ascendente. Desde Inf hasta Sup */

int selectsort(Tipo a[], Indice Inf, Indice Sup)

{ int op=0;

Indice i, j, max;

Tipo temp;

for (j=Sup; j>Inf; j--) {

max=j; temp=a[j]; //selecciona a[j]

for(i=j-1; i>=Inf; i--) {

if (a[i]>temp)

{ max=i; temp=a[i];} //busca el mayor

op++;

}

a[max]=a[j]; //intercambia el mayor con a[j]

a[j]=temp;

op++;

}

return op;

}

Se ha agregado una variable global, op, para contar las operaciones.

9.1.1.3. Ejemplo.

Se selecciona el mayor, se lo intercambia con el mayor del subarreglo.

Page 3: Cap. 9. Ordenarprofesores.elo.utfsm.cl/~tarredondo/info/datos-algoritmos/c9.pdf · de complejidad O(n2). Suelen ser el punto de partida de los métodos más elaborados que suelen

Ordenar 3

Profesor Leopoldo Silva Bijit 30-06-2008

En el ejemplo a la izquierda de la figura 9.2 se muestra el peor caso para este algoritmo, el

arreglo de entrada está ordenado en forma descendente. La variable op, cuenta las operaciones

considerando: O(1) la complejidad realizada por el for interno, y también O(1) la realización del

intercambio, al final del for externo.

La figura a la derecha ilustra un caso con un arreglo de entrada ordenado en forma aleatoria.

Una variante es seleccionar el menor e intercambiarlo con el de la posición menor, del

subarreglo.

Figura 9.2. Ejemplos de ordenamiento por selección.

El subarreglo ya ordenado, va creciendo desde la última posición, hasta llegar a la primera.

9.1.1.4. Análisis de complejidad.

El lazo for interno se realiza: (n-1)+(n-2) +…+ 1 veces. Ya que la primera vez hay que buscar el

mayor en un subarreglo de (n-1) componentes; la segunda vez se busca el mayor en un

subarreglo de (n-2) componentes; y así sucesivamente.

Suma que tiene por resultado: n*(n-1)/2. A esta suma debe agregarse la selección, que es O(1),

y el intercambio (que también es O(1)); acciones que se repiten (n-1) veces. La complejidad es

T(n) = n*(n-1)/2 +(n-1). Entonces T(n) es de complejidad O(n2).

Otra forma de calcular la complejidad es la observación de que el algoritmo se rige por la

relación de recurrencia: T(n) = T(n-1) + (n-1) con T(0) = 0, ya que cada vez se reduce el tamaño

del arreglo en uno, pero con un costo para reducirlo de (n-1) operaciones O(1). Se logra igual

resultado que el anterior.

9 8 7 6 5 4 3 2 1 0

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

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

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

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

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

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

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

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

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

op= 54

44 55 12 42 94 18 6 67 33 72

44 55 12 42 72 18 6 67 33 94 j= 9

44 55 12 42 33 18 6 67 72 94 j= 8

44 55 12 42 33 18 6 67 72 94 j= 7

44 6 12 42 33 18 55 67 72 94 j= 6

18 6 12 42 33 44 55 67 72 94 j= 5

18 6 12 33 42 44 55 67 72 94 j= 4

18 6 12 33 42 44 55 67 72 94 j= 3

12 6 18 33 42 44 55 67 72 94 j= 2

6 12 18 33 42 44 55 67 72 94 j= 1

op= 54

Page 4: Cap. 9. Ordenarprofesores.elo.utfsm.cl/~tarredondo/info/datos-algoritmos/c9.pdf · de complejidad O(n2). Suelen ser el punto de partida de los métodos más elaborados que suelen

4 Estructuras de Datos y Algoritmos

Profesor Leopoldo Silva Bijit 30-06-2008

9.1.2. Intercambio.

Se comparan e intercambian pares adyacentes de ítems, hasta que todos estén ordenados.

Los más conocidos son: bubblesort y shakesort.

9.1.2.1. Algoritmo bubblesort de una pasada.

Comenzar con i=0;

Repetir hasta que quede arreglo de tamaño uno:

Efectúa una pasada hacia arriba, hasta la última posición:

if (a[i] > a[i+1]) swap (a[i], a[i+1]); i++; (más pesado en última posición)

Disminuye en uno el tamaño del arreglo (por arriba)

9.1.2.2. Operación.

#define SWAP(a, b) { t=a; a=b; b=t; }

#define compGT(a, b) (a > b)

/* Ordena ascendente. Desde Inf hasta Sup */

void bubbleSort( Tipo a[], Indice Inf, Indice Sup )

{ Indice i, j;

Tipo t; //temporal

/* Recorre con i el arreglo desde Inf hasta Sup */

for(i=Inf ; i<= Sup; i++)

{

/* Recorre con j, desde el siguiente a Inf hasta el final de la zona no ordenada */

for(j=Inf+1; j<=(Sup-(i-Inf)); j++)

{ /* Ordena elementos adyacentes, intercambiándolos */

if( compGT( a[j-1], a[j] ) ) SWAP(a[j-1], a[j]);

}

}

}

9.1.2.3. Análisis de complejidad.

El for interno se realiza (n-1)+(n-2) +…1. Entonces es O(n2).

9.1.2.4. Algoritmo shakesort o bubblesort de dos pasadas:

Comenzar con i=0;

Repetir hasta que quede arreglo de tamaño uno:

Efectúa una pasada hacia arriba, hasta la última posición:

if (a[i] > a[i+1]) swap (a[i], a[i+1]); i++; (más pesado en última posición)

Disminuye en uno el tamaño del arreglo (por arriba)

Efectúa una pasada hacia abajo, hasta el inicio del arreglo:

if (a[i-1] > a[i]) swap (a[i], a[i-1]); i--; (más liviano en primera posición)

Acorta en uno el tamaño del arreglo (por abajo).

Este algoritmo no tiene mejor comportamiento que bubblesort.

Page 5: Cap. 9. Ordenarprofesores.elo.utfsm.cl/~tarredondo/info/datos-algoritmos/c9.pdf · de complejidad O(n2). Suelen ser el punto de partida de los métodos más elaborados que suelen

Ordenar 5

Profesor Leopoldo Silva Bijit 30-06-2008

9.1.3. Inserción.

En la etapa i-ésima se inserta a[i] en su lugar correcto entre: a[0], a[1],…, a[i-1] que están

previamente ordenados.

Hay que desplazar los elementos previamente ordenados, uno a uno, hasta encontrar la posición

donde será insertado a[i].

En el peor caso: si a[0] es mayor que a[i] hay que efectuar i copias.

En el mejor caso si a[i-1] es menor que a[i], no hay desplazamientos.

Si existen claves repetidas, éstas conservan el orden original, se dice que el algoritmo de

ordenamiento es estable.

Puede entenderse como el procedimiento usado para ordenar una mano de naipes.

Dentro de éstos se encuentran la inserción directa y la inserción binaria.

9.1.3.1. Declaraciones de tipos y macros.

typedef int Tipo; /* tipo de item del arreglo */

typedef int Indice; /* tipo del índice */

#define compGT(a, b) (a > b)

9.1.3.2. Operación.

void InsertSort(Tipo *a, Indice inf, Indice sup)

{ Tipo t;

Indice i, j;

/*Ordena ascendentemente */

for (i = inf + 1; i <= sup; i++)

{ t = a[i]; //se marca el elemento que será insertado.

/* Desplaza elementos hasta encontrar punto de inserción */

for (j = i-1; j >= inf && compGT(a[j], t); j--) a[j+1] = a[j];

a[j+1] = t; /* lo inserta */

}

}

9.1.3.3. Ejemplo de inserción directa.

Se muestra a la izquierda de la Figura 9.3, un arreglo de 10 componentes, en orden descendente,

que es el peor caso. A la derecha se tiene un caso cualquiera.

Se muestra el elemento que es insertado en un subarreglo ya ordenado que lo precede.

Page 6: Cap. 9. Ordenarprofesores.elo.utfsm.cl/~tarredondo/info/datos-algoritmos/c9.pdf · de complejidad O(n2). Suelen ser el punto de partida de los métodos más elaborados que suelen

6 Estructuras de Datos y Algoritmos

Profesor Leopoldo Silva Bijit 30-06-2008

Figura 9.3. Ejemplos ordenamiento por inserción.

9.1.3.4. Análisis de complejidad.

En el peor caso, el for interno se efectúa: 1 + 2 + 3 +…+(n-1). Esto en caso que el arreglo esté

previamente ordenado en forma descendente. En el primer recorrido del for interno hay que

mover un elemento; en la segunda pasada, hay que mover dos elementos; y así sucesivamente,

hasta mover (n-1) para dejar espacio en la primera posición, para insertar el último.

Esto tiene complejidad T(n) = n*(n-1)/2 + (n-1) = O(n2).

Se agregan (n-1) movimientos por el almacenamiento del elemento que será insertado, más la

escritura en la posición de inserción; ambas son O(1).

En el mejor de los casos con un arreglo previamente ordenado en forma ascendente, el for

interno no se realiza nunca; ya que no son necesarios los desplazamientos que éste efectúa; el

lazo externo debe efectuarse (n-1) vez. Este caso es de complejidad: T(n) = O(n).

Mientras más ordenado parcialmente esté el arreglo, mejor será el comportamiento del

algoritmo de inserción.

9 8 7 6 5 4 3 2 1 0

8 9 7 6 5 4 3 2 1 0 i= 1

7 8 9 6 5 4 3 2 1 0 i= 2

6 7 8 9 5 4 3 2 1 0 i= 3

5 6 7 8 9 4 3 2 1 0 i= 4

4 5 6 7 8 9 3 2 1 0 i= 5

3 4 5 6 7 8 9 2 1 0 i= 6

2 3 4 5 6 7 8 9 1 0 i= 7

1 2 3 4 5 6 7 8 9 0 i= 8

0 1 2 3 4 5 6 7 8 9 i= 9

op= 54

44 55 12 42 94 18 6 67 33 72

44 55 12 42 94 18 6 67 33 72 i= 1

12 44 55 42 94 18 6 67 33 72 i= 2

12 42 44 55 94 18 6 67 33 72 i= 3

12 42 44 55 94 18 6 67 33 72 i= 4

12 18 42 44 55 94 6 67 33 72 i= 5

6 12 18 42 44 55 94 67 33 72 i= 6

6 12 18 42 44 55 67 94 33 72 i= 7

6 12 18 33 42 44 55 67 94 72 i= 8

6 12 18 33 42 44 55 67 72 94 i= 9

op= 30

Page 7: Cap. 9. Ordenarprofesores.elo.utfsm.cl/~tarredondo/info/datos-algoritmos/c9.pdf · de complejidad O(n2). Suelen ser el punto de partida de los métodos más elaborados que suelen

Ordenar 7

Profesor Leopoldo Silva Bijit 30-06-2008

9.1.3.5. Inserción binaria.

Una posible mejora del algoritmo es la consideración de que el subarreglo en el cual se insertará

el elemento está previamente ordenado. En este caso, en lugar de efectuar una búsqueda

secuencial de la posición de inserción, se puede realizar una búsqueda binaria.

Sin embargo esto disminuye el número de comparaciones y no el de movimiento de los datos

(que suele ser de mayor costo). Además si el arreglo original está ordenado la búsqueda binaria

toma mayor tiempo que la secuencial.

int BinaryInsertSort(Tipo *a, Indice inf, Indice sup)

{ int op=0;

Tipo t;

Indice i, j, right, left, m;

for (i = inf + 1; i <= sup; i++) {

t = a[i]; left=inf ; right=i-1;

while(left<=right)

{ m=(left+right)/2;

if( t <a[m]) right=m-1; else left=m+1;

}

/* Desplace elementos sobre punto de inserción */

for (j = i-1; j >= left ; j--) {a[j+1] = a[j]; op++;}

/* inserte */

a[left] = t; op++;

}

return(op);

}

Este ejemplo ilustra que no siempre se logra mayor eficiencia en un algoritmo efectuando

modificaciones que parecen convenientes. Éstas deben ser evaluadas, determinando su

complejidad.

9.1.4. Shellsort. (1959)

Es uno de los algoritmos más rápidos para ordenar arreglos de algunas decenas de componentes.

Es un método adaptivo que funciona mejor si los arreglos están casi ordenados. El análisis de su

complejidad es difícil.

Se ordenan elementos que están a una distancia h entre ellos en pasadas consecutivas.

Al inicio h es un valor elevado y disminuye con cada pasada.

Sea kh cuando la distancia de comparación es k. La pasada se denomina fase kh .

El algoritmo termina con 1h .

Se emplea el algoritmo de inserción directa para ordenar cada pasada. Este algoritmo funciona

bien ya que los subarreglos son pequeños o están casi ordenados.

Page 8: Cap. 9. Ordenarprofesores.elo.utfsm.cl/~tarredondo/info/datos-algoritmos/c9.pdf · de complejidad O(n2). Suelen ser el punto de partida de los métodos más elaborados que suelen

8 Estructuras de Datos y Algoritmos

Profesor Leopoldo Silva Bijit 30-06-2008

Después de la fase kh los elementos que están a distancia kh entre ellos están ordenados, esto

quiere decir que: a[i] < a[i+ kh ] < a[i+ 2 kh ] < a[i+ 3 kh ] <…para todo i.

El ordenamiento burbuja ordena en fase 1h . Necesariamente shellsort tiene que llegar a ordenar

la fase 1h , pero su ventaja es que el número de intercambios en la fase 1h es menor que los

intercambios necesarios en inserción o por burbuja. La razón de esto es que si a una lista

ordenada de fase kh se la somete a un ordenamiento de fase 1kh sigue kh ordenada.

9.1.4.1. Shell Sort original.

int shellsortShell(Tipo a[], Indice n)

{ int op=0;

Indice h, i, j;

Tipo temp;

for(h=n/2; h>0; h=h/2) {

for (i=h; i<n; i++) { //aplica inserción a partir de h

temp=a[i];

for (j=i-h; j>=0 && a[j]>temp; j-=h) { a[j+h]=a[j]; op++;}

a[j+h]=temp; //inserta

op++;

}

}

return op;

}

El algoritmo original emplea que el nuevo valor de kh sea el anterior dividido por dos.

9.1.4.2. Selección de la secuencia de valores para h.

La selección adecuada de la secuencia kh genera un menor costo. A través de métodos de ensayo

y error se han efectuado una serie de elecciones de la secuencia de pasadas, logrando una

estimación de la complejidad.

La secuencia original de Shell (1959): 1 2 4 8 16… tiene costo O(n2).

La secuencia ih = 14i+3* 2i

+1 con i>0 y 1h =1 y 2h = 8 genera:

1 8 23 77 281 1073 4193 16577…con complejidad O(n4/3

)

La secuencia propuesta por Knuth (1998 ): 1 3 1i ih h , con h1=1

1 4 13 40… tiene complejidad O(n (log n)2).

Luego nuevas optimizaciones de Knuth permiten complejidades de O(n1.25

) y

O(n 1+1/sqrt(log n)

).

Page 9: Cap. 9. Ordenarprofesores.elo.utfsm.cl/~tarredondo/info/datos-algoritmos/c9.pdf · de complejidad O(n2). Suelen ser el punto de partida de los métodos más elaborados que suelen

Ordenar 9

Profesor Leopoldo Silva Bijit 30-06-2008

9.1.4.3. Shell sort. Knuth.

int shellsortKnuth(Tipo a[], Indice n)

{ int op=0;

Indice h,i,j;

Tipo temp;

for(h=1; h<=n/9; h=h*3+1); //inicia h

for( ; h>0; h=h/3) {

/* h = 1, 4, 13, 40, 121, 364... 3*h+1 */

for (i=h; i<n; i++) { //pasada por inserción

temp=a[i];

for (j=i-h; j>=0 && a[j]>temp; j-=h) { a[j+h]=a[j]; op++;}

a[j+h]=temp;

op++;

}

}

return op;

}

9.1.4.4. Shell sort Ciura.

Ciura (2001) conjetura que su secuencia es óptima para grandes valores de n. Se almacenan los

números de la secuencia en un arreglo local denominado incs. Se escoge el valor inicial de la

secuencia tal que sea menor que 0,44n. Por ejemplo si n=1000, el primer h será 301 generando

7 fases de ordenamiento.

int shellsortCiura(Tipo a[], Indice n)

{ int op=0;

Indice h,i,j,t;

Tipo temp;

int incs[18] = {

2331004, 1036002, 460445, 204643, 90952, 40423, 17965, 7985, 3549,

1577, 701, 301, 132, 57, 23, 10, 4, 1

};

for (t=0; t<18; t++) {

h=incs[t];

if (h > n*4/9) continue;

for (i=h; i<n; i++) { //Inserción.

temp=a[i];

for (j=i-h; j>=0 && p[j]>temp; j-=h) { a[j+h]=a[j]; op++; }

a[j+h]=temp; op++;

}

}

return op;

}

Page 10: Cap. 9. Ordenarprofesores.elo.utfsm.cl/~tarredondo/info/datos-algoritmos/c9.pdf · de complejidad O(n2). Suelen ser el punto de partida de los métodos más elaborados que suelen

10 Estructuras de Datos y Algoritmos

Profesor Leopoldo Silva Bijit 30-06-2008

67

42 94 06

44

55

18

12

last

9.2. Métodos avanzados.

Debido al gran número de algoritmos para ordenar, se estudiarán en detalle dos de los más

conocidos y eficientes.

Heapsort que en peor caso es O(n*log2(n)), y que una refinación del método de selección.

Quicksort que es O(n*log2(n)) en promedio; es una elaboración sofisticada del método de

intercambio.

9.2.1. Heapsort (1964).

Los primeros en exponer este algoritmo fueron: J.W.J. Williams y Robert Floyd.

Se forma un heap, a partir del arreglo desordenado.

Luego, repetidamente se intercambia la raíz con la posición del último, y se hace descender la

nueva raíz en un heap de largo disminuido en uno. El arreglo queda ordenado en forma

descendente.

Se analiza primero la formación de un heap, la figura siguiente ilustra un arreglo no ordenado

visualizado como un heap.

Figura 9.4. Visualización de un arreglo desordenado como un árbol binario.

Existen dos procedimientos para la formación de un heap a partir de un arreglo desordenado.

9.2.1.1. Formación del heap. Revisión de subárboles.

La formación del heap, a partir del arreglo desordenado, se logra con:

for(i=last/2; i>0; i--) siftdown(i, last);

Se modifica levemente la rutina vista en colas de prioridad, se pasa i de variable local, a

argumento de la función.

Page 11: Cap. 9. Ordenarprofesores.elo.utfsm.cl/~tarredondo/info/datos-algoritmos/c9.pdf · de complejidad O(n2). Suelen ser el punto de partida de los métodos más elaborados que suelen

Ordenar 11

Profesor Leopoldo Silva Bijit 30-06-2008

67

42 94 12

44

55

18

06

last

i =3

67

42 94 12

44

55

18

06

last

i =4

void siftdown(int i, int n)

{ int j;

registro temp; /* al inicio i apunta a la subraíz; j al hijo izquierdo */

while ( (j=(i<<1)) <= n ) /* j = 2*i */

{ if ( ( (j+1)<=n ) && ( r[j+1].prioridad < r[j].prioridad)) j++;

/*Si existe hijo derecho y es menor que el hijo izquierdo, j apunta al derecho */

if (r[i].prioridad <= r[j].prioridad) break;

/*Intercambia si el hijo menor es menor que el padre */

temp=r[j], r[j]= r[i], r[i]=temp;

i=j; /* Nueva raíz en el descenso */

}

}

De esta forma la función forma un heap(i, n).

En la primera iteración con n=8 e i=4, no se producen cambios. Se revisa el subárbol del padre

del último.

Figura 9.5. Revisión del primer subárbol (i=4).

Luego cuando i=3, se apunta con j al elemento con valor 12.

Luego de la ejecución de siftdown(3,8), no hay cambios y el diagrama queda:

Figura 9.6. Revisión del segundo subárbol (i=3).

Page 12: Cap. 9. Ordenarprofesores.elo.utfsm.cl/~tarredondo/info/datos-algoritmos/c9.pdf · de complejidad O(n2). Suelen ser el punto de partida de los métodos más elaborados que suelen

12 Estructuras de Datos y Algoritmos

Profesor Leopoldo Silva Bijit 30-06-2008

67

55 94 12

44

42

18

06

last

i =2

67

55 94 44

06

42

18

12

last

i =1

Se revisa el subárbol cuya raíz es apuntada por i=2, con j se apunta al hijo menor (el nodo con

valor 42, en la Figura 9.6). Se intercambian el 42 con el 52, y j queda apuntando al nodo con

valor 55, y sigue la revisión en descenso. En el caso del ejemplo, no hay nuevos cambios.

Luego de la iteración con i=2 se obtiene:

Figura 9.7. Revisión del tercer subárbol (i=2).

Luego de esto se tiene que el subárbol cuya raíz está apuntada por i=2, es un heap.

Finalmente la ejecución de siftdown(1,8) hace descender la raíz. Primero la intercambia con 06,

y luego con 12, resultando:

Figura 9.8. Revisión del cuarto subárbol (i=1).

Lo cual se logra con un costo: (n/2)*log2(n) es decir O(n*log2(n))

Entonces: for(i=last/2; i>0; i--) siftdown(i, last); revisa todos los subárboles desde el padre

del último.

9.2.1.2. Formación del heap. Revisión de trayectorias desde hojas hasta la raíz.

La formación del heap también puede lograrse con:

Page 13: Cap. 9. Ordenarprofesores.elo.utfsm.cl/~tarredondo/info/datos-algoritmos/c9.pdf · de complejidad O(n2). Suelen ser el punto de partida de los métodos más elaborados que suelen

Ordenar 13

Profesor Leopoldo Silva Bijit 30-06-2008

67

55 94 06

42

44

18

12

last

i al inicio

for(i=last; i>last/2; i--) siftup(i);

El cual revisa todas las rutas desde las hojas hasta la raíz.

Se agrega el texto de siftup, con una pequeña modificación. El ascenso debe seguir hasta la raíz

si el arreglo no es un heap. Se cambia la sentencia break por un continue.

void siftuph(int n) //inserta en posición n-ésima y lo hace ascender

{ int i, j;

registro temp;

for (j=n; j>1; j=i){ /*Al inicio j apunta al último ( n ) */

i=(j>>1) ; /* i= j/2 donde i es el cursor del padre de j*/

if ( r[i].prioridad <= r[j].prioridad ) continue;

//Si el ascenso no es en un heap, debe llegar hasta la raíz.

temp=r[j], r[j]= r[i], r[i]=temp; /* intercambio sólo si el hijo es menor que el padre*/

/* La condición de reinicio hace que j apunte a su padre. j=i */

}

}

A partir de la Figura 9.4. comienza el ascenso de la hoja con mayor índice. Luego de ejecutado

siftuph(8), el diagrama queda:

Figura 9.9. Revisión de la primera ruta(i=8).

Al disminuir i en uno, se apunta a la siguiente hoja. Luego de ejecutado siftuph(7), la situación

queda:

Page 14: Cap. 9. Ordenarprofesores.elo.utfsm.cl/~tarredondo/info/datos-algoritmos/c9.pdf · de complejidad O(n2). Suelen ser el punto de partida de los métodos más elaborados que suelen

14 Estructuras de Datos y Algoritmos

Profesor Leopoldo Silva Bijit 30-06-2008

67

55 94 42

06

44

18

12

last i = 6

67

55 94 42

06

44

18

12

last i = 5

67

55 94 42

06

44

18

12

last i = 7

Figura 9.10. Revisión de la segunda ruta hacia la raíz(i=7).

Luego de ejecutado siftuph(6) se tiene:

Figura 9.11. Revisión de la tercera ruta hacia la raíz(i=6).

Luego de ejecutado la revisión de la trayectoria de la última hoja hasta la raíz, con siftuph(5) se

obtiene un heap.

Figura 9.12. Revisión de la última ruta hacia la raíz(i=5).

Lo cual se logra con un costo: (n/2)*log2(n) es decir O(n*log2(n))

Page 15: Cap. 9. Ordenarprofesores.elo.utfsm.cl/~tarredondo/info/datos-algoritmos/c9.pdf · de complejidad O(n2). Suelen ser el punto de partida de los métodos más elaborados que suelen

Ordenar 15

Profesor Leopoldo Silva Bijit 30-06-2008

Debe observarse que el número de subárboles de un heap es igual al número de hojas de un heap

de n elementos.

Se revisan las trayectorias en los subárboles desde su inicio hasta las hojas en siftdown; y desde

las hojas hasta la raíz en siftuph.

9.2.1.3. Ordenamiento del heap.

El ordenamiento del heap se logra con:

Repetidamente se intercambia la raíz con la posición del último, y se hace descender la nueva

raíz en un heap de largo disminuido en uno. El arreglo queda ordenado en forma descendente.

for(i=last; i>1; i--)

{ temp=r[1], r[1]= r[i], r[i]=temp; /*swap(1, i) */

siftdown(1, i-1);

}

Como se realiza n veces el lazo for, si se asume costo constante para el intercambio, se tiene un

costo: (n)*(log2(n) +1) que es O(n*log2(n)).

El algoritmo ordena en forma descendente.

Se agregan argumentos al segmento analizado antes: Se pasa un puntero a al arreglo que será

oredenado, y los índices inicial (ini debe ser siempre 1, salvo que se modifiquen las rutinas de

ascenso y descenso) y final (last) entre los cuales debe ordenar.

void heapsort(preg a, Indice ini, Indice last)

{

int i;

registro temp;

//for(i=last; i>last/2; i--) siftuph(a, ini, i); //ordena trayectos desde las hojas hasta la raiz

for(i=last/2; i>ini-1; i--) siftdown(a, i, last); //ordena subárboles desde padre del último

for(i=last; i>ini; i--)

{

temp=a[ini], a[ini]= a[i], a[i]=temp; /*swap(ini, i) */

siftdown(a, ini, i-1);

}

}

9.2.2. Quicksort (1961).

Se parte el arreglo a ordenar en dos subarreglos, dejando los elementos mayores que uno

denominado pivote en una parte, y los menores en la otra. Si los subarreglos no están ordenados,

se los vuelve a partir, hasta lograr el ordenamiento.

Es del tipo:

Page 16: Cap. 9. Ordenarprofesores.elo.utfsm.cl/~tarredondo/info/datos-algoritmos/c9.pdf · de complejidad O(n2). Suelen ser el punto de partida de los métodos más elaborados que suelen

16 Estructuras de Datos y Algoritmos

Profesor Leopoldo Silva Bijit 30-06-2008

T(n) = 2*T(n/2) +c*n

donde c es el costo de dividir en dos partes. La solución de esta relación, ver Capítulo 4,

Ejemplo 4.14, es:

c*n*( log2(2)+log2(n) ) la que es O(n*log2(n)).

Este costo es en el mejor caso, en el cual siempre se puede efectuar la división en mitades.

Es un tipo de ordenamiento por intercambio, en éstos se compara e intercambia los ítems hasta

ordenar.

El algoritmo burbuja es del tipo por intercambio, y requiere n2 comparaciones entre ítems

adyacentes.

En quicksort los intercambios se efectúan sobre distancias mayores y relativas a un pivote.

Fue publicado por primera vez por Hoare, en 1961, y aún se mantiene vigente.

9.2.2.1. Partición.

El núcleo del algoritmo es el ordenamiento de una partición respecto de un pivote.

i j

piv

Figura 9.13. Pivote en partición.

Se asume que las direcciones de las celdas contiguas del arreglo aumentan hacia la derecha.

Se desea tener que los elementos sobre el pivote sean mayores o iguales que el valor asociado

al pivote, y los elementos bajo el pivote sean menores o iguales que el valor de éste.

Se analiza el código que realiza la partición mediante el ordenamiento por intercambio respecto

al pivote. Es un código complejo que muestra genialidad.

do {

while ( a[i].clave < piv.clave) i++; //encuentra uno mayor o igual que el valor del pivote

while( piv.clave < a[j].clave) j--; //al salir apunta a uno menor o igual que el pivote

if( i<=j {swap(i, j) ; i++; j--;}

} while( i<=j );

Se ha usado el pivote como una estructura de igual tipo que las celdas del arreglo. Y se abrevia

el intercambio con el llamado a swap, que realiza:

temp=a[i]; a[i]=a[j]; a[j]=temp;

Page 17: Cap. 9. Ordenarprofesores.elo.utfsm.cl/~tarredondo/info/datos-algoritmos/c9.pdf · de complejidad O(n2). Suelen ser el punto de partida de los métodos más elaborados que suelen

Ordenar 17

Profesor Leopoldo Silva Bijit 30-06-2008

Donde temp es una estructura igual a la de las componentes del arreglo.

Traza de la ejecución del segmento partición.

Si al inicio tenemos la siguiente situación:

44 55 12 42 94 06 18 67

i j

42 piv

Figura 9.14. Elementos al inicio.

Después de los dos primeros while internos se tiene:

44 55 12 42 94 06 18 67

i j

42 piv

Figura 9.15. Elementos después de los primeros dos while.

En el lado izquierdo se busca un elemento mayor o igual que el pivote, y en el derecho uno

menor o igual que éste.

El if produce el intercambio y mueve los cursores, quedando:

18 55 12 42 94 06 44 67

i j

42 piv

Figura 9.16. Elementos después del if.

Como i es menor que j, se vuelve a iterar.

Ninguno de los while, cambia i ni j. Después de realizado el intercambio queda:

18 06 12 42 94 55 44 67

i j

42

piv

Figura 9.17. Elementos después de la segunda iteración.

Se vuelve a iterar, ya que i es menor que j.

El primer while interno cambia i, el segundo disminuye j (queda apuntando a 42).

Page 18: Cap. 9. Ordenarprofesores.elo.utfsm.cl/~tarredondo/info/datos-algoritmos/c9.pdf · de complejidad O(n2). Suelen ser el punto de partida de los métodos más elaborados que suelen

18 Estructuras de Datos y Algoritmos

Profesor Leopoldo Silva Bijit 30-06-2008

18 06 12 42 94 55 44 67

i j

42

piv

Figura 9.18. Tercera iteración, después de los dos while.

El if efectúa el intercambio, e incrementa los punteros. Resultando con esto:

18 06 12 42 94 55 44 67

i j

42

piv

Figura 9.19. Tercera iteración, después del if.

Con lo cual termina el repeat.

El resultado es que los elementos sobre el pivote son mayores o iguales que el valor asociado al

pivote, y los elementos bajo el pivote son menores o iguales que éste.

En este caso se efectuó un intercambio demás (lo cual podría eliminarse si el intercambio se

protege con un if, que lo realice sólo si i es diferente de j).

Traza con valores repetidos en la partición.

Veamos un ejemplo con algunos valores iguales en la partición.

1 1 1 2 1 1 1

j

2 piv

i

Figura 9.20. Partición con valores repetidos.

Después de los dos primeros while, queda:

1 1 1 2 1 1 1

j

2 piv

i

Figura 9.21. Primera iteración, después de los while.

Page 19: Cap. 9. Ordenarprofesores.elo.utfsm.cl/~tarredondo/info/datos-algoritmos/c9.pdf · de complejidad O(n2). Suelen ser el punto de partida de los métodos más elaborados que suelen

Ordenar 19

Profesor Leopoldo Silva Bijit 30-06-2008

Y después del if:

1 1 1 2 1 1 1

j

2 piv

i

Figura 9.22. Al final de la primera iteración.

Debe notarse que la función partición cambia el orden original de las componentes con igual

valor de clave, por esto se dice que no es ordenamiento estable.

En la segunda pasada, i queda apuntando al último (recordar que se compara con el valor

almacenado en la estructura pivote); j no cambia.

Y finalmente termina el repeat.

1 1 1 2 1 1 1

j

2 piv

i

Figura 9.23. Después de la segunda iteración.

Traza con valores repetidos del pivote en la partición.

Veamos un ejemplo con algunos valores repetidos del pivote en la partición.

1 2 1 2 6 2 5

j

2 piv

i

Figura 9.23.a. Pivote con repeticiones.

Después de los dos primeros while, queda:

1 2 1 2 6 2 5

j

2 piv

i

Figura 9.23.b. Luego de los dos while.

Page 20: Cap. 9. Ordenarprofesores.elo.utfsm.cl/~tarredondo/info/datos-algoritmos/c9.pdf · de complejidad O(n2). Suelen ser el punto de partida de los métodos más elaborados que suelen

20 Estructuras de Datos y Algoritmos

Profesor Leopoldo Silva Bijit 30-06-2008

Y después del if, ha realizado un swap innecesario, quedando:

1 2 1 2 6 2 5

j

2 piv

i

Figura 9.23.c. Después del if.

Vuelve a iterar, después de los dos while, resulta:

1 2 1 2 6 2 5

j

2 piv

i

Figura 9.23.d. Segunda iteración.

Con lo cual vuelve a intercambiar consigo mismo, para finalmente dejar:

1 2 1 2 6 2 5

j

2 piv

i

Figura 9.23.e. Al terminar la iteración.

De este modo se acortan los subarreglos.

Luego del análisis de estos casos, puede refinarse el segmento que realiza la partición. No

realiza el intercambio si: i es igual a j o si las claves son iguales.

do {

while ( a[i].clave < piv.clave) i++; //encuentra uno mayor o igual que el valor del pivote

while( piv.clave < a[j].clave) j--; //al salir apunta a uno menor o igual que el pivote

if (i<=j)

{ if ( (i!=j) && (a[i].clave!=a[j].clave) ) swap(i, j) ;

i++; j--;

}

} while( i<=j );

Page 21: Cap. 9. Ordenarprofesores.elo.utfsm.cl/~tarredondo/info/datos-algoritmos/c9.pdf · de complejidad O(n2). Suelen ser el punto de partida de los métodos más elaborados que suelen

Ordenar 21

Profesor Leopoldo Silva Bijit 30-06-2008

Sobre la elección del pivote.

Si se elige como pivote el primer elemento o el último, lo cual es fácil de realizar, se tendrá mal

funcionamiento si el arreglo está ordenado en forma inversa. Sin embargo el comportamiento es

bueno, si el orden original es aleatorio.

Si el cursor del pivote se elige en forma aleatoria, existirá buen comportamiento; sin embargo la

generación repetida de los números aleatorios puede requerir un tiempo no despreciable.

Encontrar la mediana de los números, que sería una mejor opción, también aumenta los costos,

debido a la complejidad de encontrar la mediana.

Una alternativa es escoger la mediana de tres números. Si l y r son los cursores del primer y

último elemento del arreglo, para formar el tercer número se toma el valor central. Es decir, se

toma la mediana de los tres números con cursores: l, r y (l+r)/2.

En el algoritmo a continuación se escoge como pivote, el valor central del arreglo.

9.2.2.2. Ordenamiento usando recursión.

El siguiente código muestra la forma en que se logra el ordenamiento.

void qsort(int l, int r)

{

int i=l, j=r;

registro piv=a[(l+r)/2)];

do {

while ( a[i].clave < piv.clave) i++;

while( piv.clave < a[j].clave) j--;

if( i<=j {swap(i, j) ; i++; j--;}

} while(i<=j);

if( l < j) qsort( l, j);

if( i< r ) qsort( i, r);

}

Se inicia una partición luego se aplica el mismo proceso a las dos particiones.

Las particiones menores, vuelven a generar dos particiones y así sucesivamente hasta que la

partición tiene un solo ítem, en este caso no se generan nuevas recursiones.

Para ordenar un arreglo se invoca: qsort(0, N-1) para un arreglo de N ítems.

Produce un ordenamiento ascendente.

9.2.2.3. Complejidad en Peor caso.

Si resulta que el pivote es el mayor elemento de la partición, ésta genera un nuevo llamado con

una partición de un elemento menos que la original; no la divide en dos. Ver el segundo

Page 22: Cap. 9. Ordenarprofesores.elo.utfsm.cl/~tarredondo/info/datos-algoritmos/c9.pdf · de complejidad O(n2). Suelen ser el punto de partida de los métodos más elaborados que suelen

22 Estructuras de Datos y Algoritmos

Profesor Leopoldo Silva Bijit 30-06-2008

ejemplo, Figura 9.23; en este caso no se cumple que i<r. Se tendrán entonces: la primera

pasada con n, la segunda con (n-1), y así sucesivamente.

Sólo se llama recursivamente por un lado. Esto sucede siempre que la elección del pivote apunte

al elemento mayor de la subpartición. Este peor caso, de baja probabilidad daría origen a un

costo O( n2).

En este particular caso, se tiene: T(n) = T(n-1) +n con T(1)=1, cuya solución es:

2( 1)( ) ( )

2

n nT n n

Se ilustra un ejemplo, de peor caso, en que sólo se invoca por la izquierda.

2 4 6 8 1 5 3

j

8 piv

i

7

Figura 9.24. Peor caso, sólo se invoca por la izquierda.

9.2.2.4. Complejidad en Caso promedio.

Complejidad de Quicksort en caso promedio.

Se asume que el tamaño de la primera partición puede ser cualquier valor entre 1 y (n-1), y que

cada tamaño es igualmente probable que ocurra.

Es decir, la probabilidad de que ocurra cualquiera de los tamaños es: 1

n

Si al formar dos particiones, la primera tiene i elementos, la segunda tendrá (n-i) elementos.

Entonces la complejidad temporal, puede expresarse por:

( ) ( ) ( )T n T i T n i cn

Valor promedio para la primera partición: 1

1

1( ) ( )

n

j

T i T jn

Debido a que una partición es la imagen de la otra, también el valor promedio, de la otra

partición será: 1

1

1( ) ( )

n

j

T n i T jn

Entonces:

Page 23: Cap. 9. Ordenarprofesores.elo.utfsm.cl/~tarredondo/info/datos-algoritmos/c9.pdf · de complejidad O(n2). Suelen ser el punto de partida de los métodos más elaborados que suelen

Ordenar 23

Profesor Leopoldo Silva Bijit 30-06-2008

1

1

2( ) ( )

n

j

T n T j cnn

Multiplicando por n, se obtiene: 1

2

1

( ) 2 ( )n

j

nT n T j cn

Para pasar a una relación de recurrencia de primer orden, podemos determinar la relación para

(n-1), en la fórmula anterior, resulta: 2

2

1

( 1) ( 1) 2 ( ) ( 1)n

j

n T n T j c n

Restando las dos relaciones : 2 2( ) ( 1) ( 1) 2 ( 1) ( 1)nT n n T n T n cn c n

Despejando T(n):

( ) ( 1) ( 1) 2nT n n T n cn c

La cual puede aproximarse, para valores grandes de n por:

( ) ( 1) ( 1) 2nT n n T n cn con T(0)=0 , es la relación de recurrencia de primer orden.

Para resolver esta relación, al dividir, en ambos lados, por n(n+1) se obtiene:

( ) ( 1) 2

1 1

T n T n c

n n n

Pueden generarse a partir de la anterior, por sucesivos reemplazos de n por n-1, n-2,…2

( 1) ( 2) 2

1

T n T n c

n n n

( 2) ( 3) 2

1 2 1

T n T n c

n n n

…….

(2) (1) 2

3 2 3

T T c

Si se suman, se obtiene:

( ) (1) 1 1 12 ( ... )

1 2 1 3

T n Tc

n n n

Empleando la serie armónica, y eliminando constantes:

( )ln( 1)

1

T nn

n

Más exactamente:

Page 24: Cap. 9. Ordenarprofesores.elo.utfsm.cl/~tarredondo/info/datos-algoritmos/c9.pdf · de complejidad O(n2). Suelen ser el punto de partida de los métodos más elaborados que suelen

24 Estructuras de Datos y Algoritmos

Profesor Leopoldo Silva Bijit 30-06-2008

Que puede obtenerse, mediante el comando Maple: > S:=rsolve( { T(n) = (n+1)*T(n-1)/n + 2*(n-1)/n , T(0) = 0}, T(n));

Figura 9.25. Complejidad en caso promedio de Quicksort.

Entonces se demuestra que en el caso promedio, para n>2:

( ) ( log( ))T n n n

Que es igual a la complejidad del mejor caso, es decir, que cada vez que se efectúa una partición

se divide ésta en dos mitades.

9.2.2.5. Variantes de quicksort.

Existen muchas variantes del algoritmo.

En el libro “Introduction to Algorithms” de Cormen y otros, figura la siguiente versión. Puede

notarse lo sencillo que resulta pasar a C el código que figura en pseudo-código.

Se plantea en rutina aparte la partición; sin embargo es preferible incluirla cómo código dentro

la función, para evitar la sobrecarga de un llamado a función.

#define SWAP(a,b) { temp=(a); (a)=(b); (b)=temp; }

Indice particionCormen(preg a, Indice left, Indice right)

{ Indice i= left -1, j;

registro temp;

int piv=a[r].clave;

for(j= left; j< right; j++)

{

:= ( )T n 2 4 n 2 n 2 ( )n 1 ( )n 1

2n log2(n)

0.5n log2(n)

T(n)

Page 25: Cap. 9. Ordenarprofesores.elo.utfsm.cl/~tarredondo/info/datos-algoritmos/c9.pdf · de complejidad O(n2). Suelen ser el punto de partida de los métodos más elaborados que suelen

Ordenar 25

Profesor Leopoldo Silva Bijit 30-06-2008

if(a[j].clave <= piv)

{ i++; SWAP(a[i], a[j]); }

}

SWAP(a[i+1], a[right]);

return(i+1);

}

Nótese que en piv, se guarda ahora el valor de la clave. Y que el valor se escoge, no en el centro

sino en un extremo de la partición.

El macro SAWP requiere la variable local temp.

La función que ordena, de tipo recursiva, se muestra a continuación:

void qsortCormen(preg a, Indice left, Indice right)

{ int i;

if(left < right) //Detiene la recursión

{

i=particionCormen(a, left, right);

qsortCormen(a, left, i-1); //particion izquierda

qsortCormen(a, i+1, right); //particion derecha

}

}

El texto “Algorithms in C” de Robert Sedgewick plantea levemente diferente la función de

partición, la función tiene sólo una modificación menor, respecto de la anterior.

Indice particionSedgewick(preg a, Indice left, Indice right)

{ Indice i= left -1, j= right;

registro temp;

int piv=a[right].clave;

for(;;)

{while(a[++i].clave < piv);

while( piv < a[--j].clave) if (j==left) break;

if(i>=j) break;

SWAP(a[i], a[j]);

}

SWAP(a[i], a[right]);

return(i);

}

void qsortSedgewick(preg a, Indice left, Indice right)

{ int i;

if(right <=left) return;

i=particionSedgewick(a, left, right);

qsortSedgewick(a, left, i-1); //particion izquierda

qsortSedgewick(a, i+1, right); //particion derecha

}

Page 26: Cap. 9. Ordenarprofesores.elo.utfsm.cl/~tarredondo/info/datos-algoritmos/c9.pdf · de complejidad O(n2). Suelen ser el punto de partida de los métodos más elaborados que suelen

26 Estructuras de Datos y Algoritmos

Profesor Leopoldo Silva Bijit 30-06-2008

9.2.3. Quicksort. No recursivo.

Una alternativa frecuente es desarrollar una función no recursiva. La cual en lugar de invocarse

a sí misma, almacena en un stack solamente los argumentos. Luego mientras el stack no esté

vacío los va sacando.

Se reemplaza el almacenamiento de los frames y la dirección de retorno y los valores de los

registros que deben ser salvados, sólo por lo estrictamente necesario. Para una mayor

efectividad, las funciones push y pop, deberían definirse como macros. Obviamente se ocupa

menos espacio del stack del programa; pero el programador debe estimar el tamaño del stack del

usuario.

La siguiente función ilustra la técnica.

#include "datos.h"

#include "stack.h"

#define Maxitems 10

//tamaño del stack = x. Con 2^x =N Sedgewick 7.3 pág. 314

#define SIZE 4

void qsortNoRec(preg a, Indice left, Indice right)

{ Indice i;

StackInit(SIZE);

push2(left, right); //inicia stack con los argumentos originales

while (!StackEmpty())

{ left=StackPop(); right =StackPop(); //los saca

if (right <=left) continue;

i = particionSedgewick(a, left, right);

if( i-left > right -i)

{ push2(left, i-1); push2(i+1, right);} //empuja en lugar de invocarse a sí misma.

else {push2(i+1, right); push2(left, i-1);} //empuja primero la partición con más elementos.

}

StackDestroy();

}

9.2.3.1. Stack de usuario.

Se ilustra una metodología de programación, para desarrollar herramientas, que podrán ser

usadas por otros programas.

Para poder emplear la función qsortNoRec deben tenerse las funciones que manejan el stack.

Esto se realiza en stack.c. Para hacer más flexible el diseño se efectúan algunas definiciones

adicionales.

Conviene definir un archivo con algunos tipos de datos que emplearán las aplicaciones.

/*datos.h> */

#ifndef __DATOS_H__

#define __DATOS_H__

Page 27: Cap. 9. Ordenarprofesores.elo.utfsm.cl/~tarredondo/info/datos-algoritmos/c9.pdf · de complejidad O(n2). Suelen ser el punto de partida de los métodos más elaborados que suelen

Ordenar 27

Profesor Leopoldo Silva Bijit 30-06-2008

typedef int Tipo; /* tipo de item del arreglo */

typedef int Indice; /* tipo del índice */

typedef Indice ElementoStack;

typedef struct nn1{

Tipo clave;

int ntarea;

} registro, *preg;

#endif /* __DATOS_H__ */

La compilación condicional asegura que este archivo sólo se incluirá una vez.

La funciones que emplean el stack, suelen describirse en un archivo, mostrando los prototipos.

/*stack.h> */

#ifndef __STACK_H__

#define __STACK_H__

#define push2(A,B) StackPush((B)); StackPush((A));

void StackInit(int);

int StackEmpty(void);

int StackFull(void);

void StackPush(ElementoStack);

ElementoStack StackPop(void);

void StackDestroy(void);

#endif /* __STACK_H__ */

Luego de los archivos anteriores se describe la implementación del stack.

/*stack.c Implementación basada en arreglos dinámicos. */

#include <stdlib.h>

#include <stdio.h>

#include "datos.h"

#include "stack.h"

static ElementoStack * s; //puntero al tope

static int N; //elementos en el stack

static int MAXN;

void StackInit(int max)

{ s = malloc(max*sizeof(ElementoStack) ); // Se define el arreglo dinámico

if(s== NULL) exit(1);

N=0; MAXN=max; //Se inician .

}

int StackEmpty(void)

{ return( N == 0) ; //Retorna verdadero si stack vacío

Page 28: Cap. 9. Ordenarprofesores.elo.utfsm.cl/~tarredondo/info/datos-algoritmos/c9.pdf · de complejidad O(n2). Suelen ser el punto de partida de los métodos más elaborados que suelen

28 Estructuras de Datos y Algoritmos

Profesor Leopoldo Silva Bijit 30-06-2008

}

int StackFull(void)

{

return( N == MAXN) ; //Retorna verdadero si stack lleno

}

//se puede empujar algo al stack si no está lleno.

void StackPush(ElementoStack cursor)

{ s[N++]= cursor; }

//se puede sacar algo del stack si no está vacío

ElementoStack StackPop(void)

{

if( StackEmpty() ) {printf("error. extracción stack vacio\n"); exit(1); return;}

else return ( s[--N] ) ;

}

void StackDestroy(void)

{

free(s);

}

9.2.3.2. Observaciones finales.

Una refinación muy empleada es detener la recursión cuando el tamaño de las particiones es

cercano a 10. Se llama a la función quicksort, y ésta entrega un arreglo parcialmente ordenado.

Luego se invoca a un método tipo n2, pero con n acotado.

Por ejemplo:

void ordene(preg a, Indice left, Indice right)

{

qsort(a, left, right);

insercionSort(a, left, right);

}

En este caso la primera acción de qsort es retornar si el tamaño de la partición es menor que un

valor dado.

Existen numerosas implementaciones de quicksort. Suele estar en las bibliotecas que

acompañan a los lenguajes. Ver qsort en stdlib.h.

9.2.4. Merging y Mergesort (1945)

La acción de unir (merging) es combinar dos estructuras ordenadas en una sola. Suele emplearse

para ordenamiento externo; es decir para ordenar archivos, pero también puede emplearse para

ordenamiento interno. Presenta ventajas en el ordenamiento de listas.

Page 29: Cap. 9. Ordenarprofesores.elo.utfsm.cl/~tarredondo/info/datos-algoritmos/c9.pdf · de complejidad O(n2). Suelen ser el punto de partida de los métodos más elaborados que suelen

Ordenar 29

Profesor Leopoldo Silva Bijit 30-06-2008

Fue presentado por John Von Neumann en 1945.

En quicksort se selecciona un pivote, y se efectúa una partición respecto del valor de éste, luego

en forma recursiva se ordena la partición izquierda y derecha.

En mergesort se parte la estructura en dos; luego en forma recursiva se ordena la parte izquierda

y derecha, y finalmente se mezclan (merge) las dos partes en una sola ordenada y mayor.

Sus ventajas son: es O(nlog(n)) en peor caso, y es estable; es decir mantiene el orden original

de los elementos repetidos. Los algoritmos heapsort y quicksort no son estables. Sus desventajas

son que requiere un espacio adicional proporcional a n y que su complejidad de mejor caso

también es O(nlog(n)).

Conviene emplearlo cuando se requiere velocidad y cuando no puede tolerarse el

comportamiento O(n2) en peor caso; también cuando se dispone de espacio o cuando se requiere

un ordenamiento estable. Es el método que suele emplearse si sólo se dispone de acceso

secuencial a las componentes, ya sea que se esté ordenando un archivo o una lista simplemente

enlazada.

Su principal tradicional aplicación es mezclar un archivo ordenado de grandes dimensiones (el

maestro) con uno pequeño (transacciones diarias), pero desordenado. Se ordena el pequeño por

alguno de los métodos avanzados, y luego se efectúa la mezcla.

9.2.4.1. Mezcla de dos arreglos. Ordenamiento estable.

Se unen dos arreglos a y b, ordenados en forma ascendente, en un arreglo c. Se va pasando al

arreglo c, el menor de los arreglos a y b, a medida que éstos se recorren ascendentemente.

2 4 5 7 8 1 2 38

6

b

1 2 2 3 4 5 6 7 8

c

a

Figura 9.26. Mezcla de arreglos.

Si uno de los arreglos se agota, insertar los elementos restantes en c; en caso contrario: escoger

el menor valor entre a[i] y b[j] e insertarlo en c[k], ajustando los índices.

Se emplean tres índices: i para arreglo a, j para arreglo b, y k para el arreglo c.

Se usan como argumentos el número de elementos de los arreglos, n el de a; y m el de b. El

direccionamiento de los arreglos es relativo a la dirección inicial.

Page 30: Cap. 9. Ordenarprofesores.elo.utfsm.cl/~tarredondo/info/datos-algoritmos/c9.pdf · de complejidad O(n2). Suelen ser el punto de partida de los métodos más elaborados que suelen

30 Estructuras de Datos y Algoritmos

Profesor Leopoldo Silva Bijit 30-06-2008

a

b

c

m

n

n+m

i

j

k

Figura 9.27. Argumentos y variables en merge.

void merge(Item c[], Item a[], int n, Item b[], int m )

{ int i, j, k;

for (i = 0, j = 0, k = 0; k < n+m; k++)

{

if (i == n) { c[k] = b[j++]; continue; }

if (j == m) { c[k] = a[i++]; continue; }

if (a[i] <= b[j]) c[k] = a[i++] ; else c[k] = b[j++];

}

}

Complejidad de la operación de mezclar: O(n+m).

9.2.4.2. Algoritmo básico estable.

Debido a la gran cantidad de invocaciones en los llamados recursivos, éstos suelen detenerse

cuando los arreglos sean de tamaños menores o iguales a 10. Se elimina el tiempo empleado en

copiar a los arreglos auxiliares conmutando entre poner la salida mezclada en el arreglo auxiliar

o en el arreglo de entrada.

El diseño permite ordenar una parte del arreglo a, desde left a right.

void MergeSort(Item a[], int left, int right)

{ int i;

Item * aux;

aux=(Item *) calloc(right-left+1, sizeof(Item)); if (aux==NULL) exit (1);

for (i = 0; i <= right-left; i++) aux[i] = a[i+left]; //se copia fuente al auxiliar.

mergesortR(a+left, aux, 0, right-left); //destino =a fuente= aux

free(aux);

}

El tamaño del auxiliar es igual al del subarreglo a.

void mergesortR(Item a[], Item b[], int left, int right)

{ int m;

if (right-left <= 10) { InsertSort(a, left, right); return; }

m = (left+right)/2;

mergesortR(b, a, left, m); //alterna arreglo con auxiliar

Page 31: Cap. 9. Ordenarprofesores.elo.utfsm.cl/~tarredondo/info/datos-algoritmos/c9.pdf · de complejidad O(n2). Suelen ser el punto de partida de los métodos más elaborados que suelen

Ordenar 31

Profesor Leopoldo Silva Bijit 30-06-2008

mergesortR(b, a, m+1, right);

merge(a+left, b+left, m-left+1, b+m+1, right-m); //operación de mezcla estable

}

Se ilustran los punteros y los tamaños al efectuar el llamado a la mezcla.

b+left

b+m+1

a+left

right-m

m-left+1

right-left+1

Figura 9.28. Argumentos en invocación a merge.

Se ilustra el proceso de subdivisión descendente de los arreglos mediante los llamados

recursivos. Como se desea ver cómo se forman los subarreglos, se cambia la línea que efectúa el

algoritmo cuadrático de ordenamiento para detener la recursión, mediante:

if (right-left <= 0) { InsertSort(a, left, right); return; }

Para el arreglo de la Figura 9.29, con left igual a 0 y right igual a 4, la división entera de (0+4)/2

resulta 2, entonces la primera división del arreglo es entre índices 0 y 2, y entre 3 y 4. Esta

división se muestra en el primer nivel bajo el arreglo original.

7 2 8 5 4

7 2 8 5 4

8 7 2 5 4

2 7

Figura 9.29. Formación subarreglos.

Luego se van mezclando los subarreglos, en forma ascendente.

Page 32: Cap. 9. Ordenarprofesores.elo.utfsm.cl/~tarredondo/info/datos-algoritmos/c9.pdf · de complejidad O(n2). Suelen ser el punto de partida de los métodos más elaborados que suelen

32 Estructuras de Datos y Algoritmos

Profesor Leopoldo Silva Bijit 30-06-2008

2 4 5 7 8

2 7 8 4 5

8 2 7 5 4

2 7

Figura 9.29. Mezclas subarreglos.

Complejidad.

La complejidad de la mezcla o merge es O(n).

La complejidad de mergesort es la suma de los dos llamados recursivos más la operación de

mezclar, es decir:

T(n) = 2T(n/2) + n con T(1)=0.

Resolviendo esta ecuación de recurrencia, resulta complejidad temporal O(n log(n)).

Se emplea un espacio adicional proporcional a n. El algoritmo no depende del orden inicial de la

entrada y es estable si la función que efectúa la mezcla es estable.

9.2.4.3. Mezcla in situ. ( In-Place Merge. Ordenamiento no estable)

El algoritmo anterior asume tres arreglos disjuntos lo cual requiere el espacio adicional del

arreglo c. Es difícil realizar la mezcla empleando solamente el espacio de los arreglos a y b

(mezcla in situ), por esto suele emplearse un arreglo auxiliar.

Supongamos que deseamos efectuar la mezcla desde a[left] hasta a[mitad] con el subarreglo

a[mitad+1] hasta a[right].

a[left], a[left+1], …., a[mitad] y a[mitad+1], a[mitad+2], …., a[right]

Y luego dejar el resultado en el mismo arreglo a.

En estas condiciones es más costoso discernir cuando se han agotado los arreglos izquierdo y

derecho, suelen usarse centinelas con este propósito. Una alternativa es copiar los valores a un

arreglo auxiliar, pero el segundo arreglo en orden inverso.

Los valores en el arreglo auxiliar quedan en el siguiente orden:

a[left], a[left+1], …., a[mitad] , a[right], …a[mitad+2], a[mitad+1]

Page 33: Cap. 9. Ordenarprofesores.elo.utfsm.cl/~tarredondo/info/datos-algoritmos/c9.pdf · de complejidad O(n2). Suelen ser el punto de partida de los métodos más elaborados que suelen

Ordenar 33

Profesor Leopoldo Silva Bijit 30-06-2008

De esta forma, como se verá, no se requieren centinelas y se evitan los test para ver si se llegó o

no al final de los arreglos, mediante los cursores i (progresivo) y j (regresivo). La dificultad es

que este método deja no estable al algoritmo.

void mergeI(Item a[], int left, int mitad, int right)

{ int i, j, k;

static Item aux[maxN];

for (i = mitad+1; i > left; i--) aux[i-1] = a[i-1]; //copia parte izquierda desde m a left

for (j = mitad; j < right; j++) aux[right+mitad-j] = a[j+1]; // copia en orden inverso

//se tienen: i=left, j= right

for (k = left; k <= right; k++) //se mezcla en arreglo a

if (aux[j] < aux[i]) a[k] = aux[j--];

else a[k] = aux[i++];

}

9.2.4.4. Top-Down Mergesort

Se divide el arreglo en mitades, se ordenan recursivamente las mitades, y luego se las mezcla.

void mergesort(Item a[], int left, int right)

{ int m = (right+left)/2;

if (right <= left) return;

mergesort(a, left, m);

mergesort(a, m+1, right);

mergeI(a, left, m, right);

}

9.2.4.5. Bottom-Up Mergesort

Se pasa iterativamente a través del arreglo, mezclando subarreglos adyacentes; el tamaño de los

subarreglos se dobla en cada pasada.

void mergesortBU(Item a[], int left, int right)

{ int m, i;

for (m = 1; m <= right-left; m = m+m)

for (i = left; i <= right-m; i += m+m)

mergeI(a, i, i+m-1, min(i+m+m-1, right));

}

9.2.4.6. Comportamiento de Mergesort

A través de mediciones puede comprobarse que quicksort es casi el doble más rápido que

mergesort. El detener las iteraciones o recursiones para subarreglos menores que un valor,

reduce el tiempo en casi un 15 %. El algoritmo top-down es más eficiente que el bottom-up.

9.2.4.7. Implementación de Merging para listas enlazadas.

Se mezclan dos listas apuntadas por a y b. Retorna un puntero a la nueva lista ordenada.

Page 34: Cap. 9. Ordenarprofesores.elo.utfsm.cl/~tarredondo/info/datos-algoritmos/c9.pdf · de complejidad O(n2). Suelen ser el punto de partida de los métodos más elaborados que suelen

34 Estructuras de Datos y Algoritmos

Profesor Leopoldo Silva Bijit 30-06-2008

pnodo merge(pnodo a, pnodo b)

{ nodo cabecera;

pnodo head = &cabecera, c = head;

while ((a != NULL) && (b != NULL))

if (a->item < b->item)

{ c->next = a; c = a; a = a->next; }

else { c->next = b; c = b; b = b->next; }

c->next = (a == NULL) ? b : a;

return head->next;

}

9.2.5. Ordenamientos por cuenta. CountSort.

Para ordenar arreglos formados por enteros positivos entre 0 y k, se han obtenido algoritmos

de complejidad O(n). Uno de ellos está basado en contar las ocurrencias de las claves, y

mediante éstas llevar los números a sus posiciones. Es un algoritmo estable.

Analizaremos la estrategia, mediante un ejemplo. Se tiene un arreglo a, de 5 elementos, el

mayor elemento tiene valor 8:

0 1 2 3 4

7 2 8 5 4 a

Figura 9.30. Arreglo a.

Se cuentan las ocurrencias de las claves en un arreglo auxiliar c de (k+1) posiciones:

0 1 2 3 4 5 6 7 8

0 0 1 0 1 1 0 1 1 c

Figura 9.31. Ocurrencias de claves.

Se modifica el arreglo c, de tal modo que el contenido sea el número de claves menores que

el índice. De este modo el contenido determina la posición del valor del índice.

0 1 2 3 4 5 6 7 8

0 0 0 1 1 2 3 3 4 c

Figura 9.32. Posiciones de índices.

Se copian los datos del arreglo a, a su posición en otro arreglo auxiliar b, mediante:

b[ c[ a[i] ] ] = a[i]

Page 35: Cap. 9. Ordenarprofesores.elo.utfsm.cl/~tarredondo/info/datos-algoritmos/c9.pdf · de complejidad O(n2). Suelen ser el punto de partida de los métodos más elaborados que suelen

Ordenar 35

Profesor Leopoldo Silva Bijit 30-06-2008

0 1 2 3 4

7 2 8 5 4

a

b

Figura 9.33. Copia al arreglo b.

Como pueden existir claves repetidas, se va incrementando en uno la posición en el arreglo c;

esto se logra con: b[ c[ a[i] ] ++] = a[i];

Luego se copia el arreglo b, en el arreglo original. Las claves repetidas mantienen las posiciones

que tenían en el arreglo original, por lo cual es un ordenamiento estable.

Se requieren dos arreglos auxiliares con un total de (n+k+1) posiciones.

// Las n claves deben tener valores entre 0 y k.

// Requiere n+k+1 posiciones adicionales.

// Operaciones = 3n+k = O(n+k)

void countSort(int a[], int k, int n)

{ int i, tmp, cuenta = 0;

int *b;

int *c;

b = (int*)calloc(n, sizeof(int)); if (b==NULL) exit(1);

c = (int*)calloc(k+1, sizeof(int)); if (c==NULL) exit(1);

for (i = 0; i < n; i++) // cuenta número de claves

c[ a[i] ]++;

//mostrar(c, 0, k);

for (i = 0; i <= k; i++) {// calcula número de claves < valor del índice i

tmp = cuenta + c[i];

c[i] = cuenta;

cuenta = tmp;

}

//mostrar(c,0,k);

for (i = 0; i < n; i++)

b[ c[ a[i] ]++] = a[i]; // mueve claves a su localización.

//Postincrementa c[a[i]] para tratar caso de claves iguales.

//mostrar(b, 0, n-1);

for (i = 0; i < n ; i++) // copia arreglo ordenado hacia a

a[i] = b[i];

Page 36: Cap. 9. Ordenarprofesores.elo.utfsm.cl/~tarredondo/info/datos-algoritmos/c9.pdf · de complejidad O(n2). Suelen ser el punto de partida de los métodos más elaborados que suelen

36 Estructuras de Datos y Algoritmos

Profesor Leopoldo Silva Bijit 30-06-2008

free(c);

free(b);

}

Problemas resueltos.

P9.1.

Se tiene un arreglo de registros con la siguiente estructura.

typedef struct nn1{

unsigned int clavep;

unsigned int claves;

} registro, *pregistro;

Las claves primaria y secundaria son enteros menores que 100.

Modificar la rutina quicksort, tanto en sus tipos como en el algoritmo, para que ordene un

arreglo de registros ordenando por la clave primaria en forma ascendente y por la clave

secundaria en forma descendente.

El siguiente ejemplo muestra a la izquierda antes de ordenar, y a la derecha después de ordenar.

Clavep Claves

2 3

1 3

2 4

1 5

3 2

Solución.

Como los elementos son menores que 100, puede formarse una clave compuesta del siguiente

modo:

#define aclave(i) a[(i)].clavep*100 + (100-a[(i)].claves)

#define pivclave piv.clavep*100 + (100-piv.claves)

Agregando una columna con la clave compuesta:

Clavep Claves

1 5

1 3

2 4

2 3

3 2

Page 37: Cap. 9. Ordenarprofesores.elo.utfsm.cl/~tarredondo/info/datos-algoritmos/c9.pdf · de complejidad O(n2). Suelen ser el punto de partida de los métodos más elaborados que suelen

Ordenar 37

Profesor Leopoldo Silva Bijit 30-06-2008

Clavep Claves Clave compuesta

2 3 297

1 3 197

2 4 296

1 5 195

3 2 398

Al ordenar en forma ascendente por la clave compuesta resulta lo que se pide.

Clavep Claves Clave compuesta

1 5 195

1 3 197

2 4 296

2 3 297

3 2 398

Sólo se requiere modificar la sección que efectúa la comparación.

void qsort(int l, int r)

{

int i=l, j=r;

registro piv=a[(l+r)/2)];

do {

while ( aclave(i) < pi.clave ) i++;

while( pivclave < aclave(j) ) j--;

if( i<=j {swap(i, j) ; i++; j--;}

} while(i<=j);

if( l < j) qsort( l, j);

if( i< r ) qsort( i, r);

}

Otra solución.

Si se definen dos funciones, que ordenen en forma ascendente y descendente:

void qsorta(preg a, Indice l, Indice r)

{ Indice i=l, j=r;

registro temp;

registro piv=a[(l+r)/2];

do {

while ( a[i].clavep < piv.clavep) i++; ////ordena ascendente por clavep

while( piv.clavep < a[j].clavep) j--;

if( i<=j)

{ if (i!=j) {temp=a[i], a[i]= a[j], a[j]=temp;}

i++; j--;

}

} while(i<=j);

if( l < j) {qsorta( a, l, j);izq++;};

if( i < r ) {qsorta( a, i, r);der++;};

}

Page 38: Cap. 9. Ordenarprofesores.elo.utfsm.cl/~tarredondo/info/datos-algoritmos/c9.pdf · de complejidad O(n2). Suelen ser el punto de partida de los métodos más elaborados que suelen

38 Estructuras de Datos y Algoritmos

Profesor Leopoldo Silva Bijit 30-06-2008

void qsortd(preg a, Indice l, Indice r)

{ Indice i=l, j=r;

registro temp;

registro piv=a[(l+r)/2];

do {

while ( a[i].claves > piv.claves) i++; //ordena descendente por claves

while( piv.claves > a[j].claves) j--;

if( i<=j)

{ if (i!=j) {temp=a[i], a[i]= a[j], a[j]=temp;}

i++; j--;

}

} while(i<=j);

if( l < j) {qsortd( a, l, j);izq++;};

if( i < r ) {qsortd( a, i, r);der++;};

}

Se puede implementar el subordenamiento de las claves secundarias, cuando previamente se ha

ordenado por clave primaria.

void qsort(preg a, Indice l, Indice r)

{ Indice i,j;

qsorta(a, l, r);

for(i=l;i<r;i++)

{

j=i;

while(a[i].clavep==a[i+1].clavep) i++;

if (i>j) qsortd(a,j,i);

}

}

Ejercicios propuestos.

E9.1.

Para el siguiente arreglo de enteros:

4 5 1 4 3 1 5 6 2 3

Efectuar una traza de qsort indicando los subarreglos que resultan antes de los llamados

recursivos. Hacia abajo debe reflejarse el tiempo.

E9.2.

Explicar cómo se forma un heap a partir de un arreglo desordenado.

Page 39: Cap. 9. Ordenarprofesores.elo.utfsm.cl/~tarredondo/info/datos-algoritmos/c9.pdf · de complejidad O(n2). Suelen ser el punto de partida de los métodos más elaborados que suelen

Ordenar 39

Profesor Leopoldo Silva Bijit 30-06-2008

E9.3

Diseñar, en forma recursiva, la rutina genheap( A, i, n) , que al ser invocada mantiene la

propiedad de heap, desde el elemento i hacia abajo, hasta llegar a una hoja. Esto en un arreglo A,

cuando el último elemento del heap es n.

La propiedad del heap debe ser:

Para cada nodo del heap, diferente a la raíz, el valor del padre es mayor o igual que el del

nodo.

Indicar cómo se sale de la rutina recursiva.

Referencias.

Shell, D. L. “A high-speed sorting procedure”. Communications of the ACM 2, 30–32. 1959.

Knuth, D. E. “The Art of Computer Programming. Vol. 3: Sorting and Searching.” Addison-

Wesley, Reading, MA, 1998.

Marcin Ciura, “Best Increments for the Average Case of Shellsort”, 13th International

Symposium on Fundamentals of Computation Theory, Riga, Latvia, Aug 22 2001.

J.W.J. Williams. “Algorithm 232”. Communications of the ACM, 7; 347-348; 1964.

Robert Floyd. “Algorithm 245”. Communications of the ACM, 7; 701; 1964.

Hoare, C. A. R. "Partition: Algorithm 63," "Quicksort: Algorithm 64," and "Find: Algorithm

65." Comm. ACM 4, 321-322, 1961.

H. H. Goldstine and J. von Neumann. “Planning and coding of problems for an electronic

computing instrument. Part II Volume 2”. Reprinted in “John von Neumann Collected Works

Volume V. Design of Computers. Theory of Automata and Numerical Analysis”. Pergamon

Press. Oxford. England. pp 152-214. 1963.

Animaciones a algoritmos de ordenamiento.

http://cg.scs.carleton.ca/~morin/misc/sortalg/

Page 40: Cap. 9. Ordenarprofesores.elo.utfsm.cl/~tarredondo/info/datos-algoritmos/c9.pdf · de complejidad O(n2). Suelen ser el punto de partida de los métodos más elaborados que suelen

40 Estructuras de Datos y Algoritmos

Profesor Leopoldo Silva Bijit 30-06-2008

Índice general.

CAPÍTULO 9 .............................................................................................................................................. 1

ORDENAR .................................................................................................................................................. 1

9.1. MÉTODOS DIRECTOS. .......................................................................................................................... 2 9.1.1. Selección. ................................................................................................................................... 2

9.1.1.1. Algoritmo: ........................................................................................................................................... 2 9.1.1.2. Operación. ........................................................................................................................................... 2 9.1.1.3. Ejemplo. .............................................................................................................................................. 2 9.1.1.4. Análisis de complejidad. ..................................................................................................................... 3

9.1.2. Intercambio. ............................................................................................................................... 4 9.1.2.1. Algoritmo bubblesort de una pasada. .................................................................................................. 4 9.1.2.2. Operación. ........................................................................................................................................... 4 9.1.2.3. Análisis de complejidad. ..................................................................................................................... 4 9.1.2.4. Algoritmo shakesort o bubblesort de dos pasadas: .............................................................................. 4

9.1.3. Inserción. ................................................................................................................................... 5 9.1.3.1. Declaraciones de tipos y macros. ........................................................................................................ 5 9.1.3.2. Operación. ........................................................................................................................................... 5 9.1.3.3. Ejemplo de inserción directa. .............................................................................................................. 5 9.1.3.4. Análisis de complejidad. ..................................................................................................................... 6 9.1.3.5. Inserción binaria. ................................................................................................................................. 7

9.1.4. Shellsort. (1959) ......................................................................................................................... 7 9.1.4.1. Shell Sort original. .............................................................................................................................. 8 9.1.4.2. Selección de la secuencia de valores para h. ....................................................................................... 8 9.1.4.3. Shell sort. Knuth.................................................................................................................................. 9 9.1.4.4. Shell sort Ciura. ................................................................................................................................... 9

9.2. MÉTODOS AVANZADOS. ................................................................................................................... 10 9.2.1. Heapsort (1964). ...................................................................................................................... 10

9.2.1.1. Formación del heap. Revisión de subárboles. ................................................................................... 10 9.2.1.2. Formación del heap. Revisión de trayectorias desde hojas hasta la raíz. ........................................... 12 9.2.1.3. Ordenamiento del heap. ..................................................................................................................... 15

9.2.2. Quicksort (1961). ..................................................................................................................... 15 9.2.2.1. Partición. ........................................................................................................................................... 16

Traza de la ejecución del segmento partición. ........................................................................................... 17 Traza con valores repetidos en la partición. .............................................................................................. 18 Traza con valores repetidos del pivote en la partición. .............................................................................. 19 Sobre la elección del pivote....................................................................................................................... 21

9.2.2.2. Ordenamiento usando recursión. ....................................................................................................... 21 9.2.2.3. Complejidad en Peor caso. ................................................................................................................ 21 9.2.2.4. Complejidad en Caso promedio. ....................................................................................................... 22 9.2.2.5. Variantes de quicksort. ...................................................................................................................... 24

9.2.3. Quicksort. No recursivo. .......................................................................................................... 26 9.2.3.1. Stack de usuario. ............................................................................................................................... 26 9.2.3.2. Observaciones finales. ....................................................................................................................... 28

9.2.4. Merging y Mergesort (1945) .................................................................................................... 28 9.2.4.1. Mezcla de dos arreglos. Ordenamiento estable. ................................................................................ 29 9.2.4.2. Algoritmo básico estable. .................................................................................................................. 30

Complejidad. ............................................................................................................................................. 32

Page 41: Cap. 9. Ordenarprofesores.elo.utfsm.cl/~tarredondo/info/datos-algoritmos/c9.pdf · de complejidad O(n2). Suelen ser el punto de partida de los métodos más elaborados que suelen

Ordenar 41

Profesor Leopoldo Silva Bijit 30-06-2008

9.2.4.3. Mezcla in situ. ( In-Place Merge. Ordenamiento no estable) ............................................................ 32 9.2.4.4. Top-Down Mergesort ........................................................................................................................ 33 9.2.4.5. Bottom-Up Mergesort ....................................................................................................................... 33 9.2.4.6. Comportamiento de Mergesort ......................................................................................................... 33 9.2.4.7. Implementación de Merging para listas enlazadas. ........................................................................... 33

9.2.5. Ordenamientos por cuenta. CountSort. ................................................................................... 34 PROBLEMAS RESUELTOS. ........................................................................................................................ 36

P9.1. .................................................................................................................................................. 36 EJERCICIOS PROPUESTOS. ....................................................................................................................... 38

E9.1. .................................................................................................................................................. 38 E9.2. .................................................................................................................................................. 38 E9.3 ................................................................................................................................................... 39

REFERENCIAS. ........................................................................................................................................ 39 ÍNDICE GENERAL. ................................................................................................................................... 40 ÍNDICE DE FIGURAS. ................................................................................................................................ 42

Page 42: Cap. 9. Ordenarprofesores.elo.utfsm.cl/~tarredondo/info/datos-algoritmos/c9.pdf · de complejidad O(n2). Suelen ser el punto de partida de los métodos más elaborados que suelen

42 Estructuras de Datos y Algoritmos

Profesor Leopoldo Silva Bijit 30-06-2008

Índice de figuras.

FIGURA 9.1. INVERSIONES. ............................................................................................................................ 1 FIGURA 9.2. EJEMPLOS DE ORDENAMIENTO POR SELECCIÓN. ........................................................................ 3 FIGURA 9.3. EJEMPLOS ORDENAMIENTO POR INSERCIÓN. .............................................................................. 6 FIGURA 9.4. VISUALIZACIÓN DE UN ARREGLO DESORDENADO COMO UN ÁRBOL BINARIO. .......................... 10 FIGURA 9.5. REVISIÓN DEL PRIMER SUBÁRBOL (I=4). .................................................................................. 11 FIGURA 9.6. REVISIÓN DEL SEGUNDO SUBÁRBOL (I=3)................................................................................ 11 FIGURA 9.7. REVISIÓN DEL TERCER SUBÁRBOL (I=2). .................................................................................. 12 FIGURA 9.8. REVISIÓN DEL CUARTO SUBÁRBOL (I=1). ................................................................................. 12 FIGURA 9.9. REVISIÓN DE LA PRIMERA RUTA(I=8). ...................................................................................... 13 FIGURA 9.10. REVISIÓN DE LA SEGUNDA RUTA HACIA LA RAÍZ(I=7)............................................................ 14 FIGURA 9.11. REVISIÓN DE LA TERCERA RUTA HACIA LA RAÍZ(I=6). ........................................................... 14 FIGURA 9.12. REVISIÓN DE LA ÚLTIMA RUTA HACIA LA RAÍZ(I=5). ............................................................. 14 FIGURA 9.13. PIVOTE EN PARTICIÓN. ........................................................................................................... 16 FIGURA 9.14. ELEMENTOS AL INICIO. .......................................................................................................... 17 FIGURA 9.15. ELEMENTOS DESPUÉS DE LOS PRIMEROS DOS WHILE. ............................................................. 17 FIGURA 9.16. ELEMENTOS DESPUÉS DEL IF. ................................................................................................. 17 FIGURA 9.17. ELEMENTOS DESPUÉS DE LA SEGUNDA ITERACIÓN. ............................................................... 17 FIGURA 9.18. TERCERA ITERACIÓN, DESPUÉS DE LOS DOS WHILE. ............................................................... 18 FIGURA 9.19. TERCERA ITERACIÓN, DESPUÉS DEL IF. .................................................................................. 18 FIGURA 9.20. PARTICIÓN CON VALORES REPETIDOS. ................................................................................... 18 FIGURA 9.21. PRIMERA ITERACIÓN, DESPUÉS DE LOS WHILE. ...................................................................... 18 FIGURA 9.22. AL FINAL DE LA PRIMERA ITERACIÓN. ................................................................................... 19 FIGURA 9.23. DESPUÉS DE LA SEGUNDA ITERACIÓN. ................................................................................... 19 FIGURA 9.23.A. PIVOTE CON REPETICIONES. ................................................................................................ 19 FIGURA 9.23.B. LUEGO DE LOS DOS WHILE. ................................................................................................. 19 FIGURA 9.23.C. DESPUÉS DEL IF. ................................................................................................................. 20 FIGURA 9.23.D. SEGUNDA ITERACIÓN. ........................................................................................................ 20 FIGURA 9.23.E. AL TERMINAR LA ITERACIÓN. ............................................................................................. 20 FIGURA 9.24. PEOR CASO, SÓLO SE INVOCA POR LA IZQUIERDA. .................................................................. 22 FIGURA 9.25. COMPLEJIDAD EN CASO PROMEDIO DE QUICKSORT. .............................................................. 24 FIGURA 9.26. MEZCLA DE ARREGLOS. ......................................................................................................... 29 FIGURA 9.27. ARGUMENTOS Y VARIABLES EN MERGE. ................................................................................ 30 FIGURA 9.28. ARGUMENTOS EN INVOCACIÓN A MERGE. .............................................................................. 31 FIGURA 9.29. FORMACIÓN SUBARREGLOS. .................................................................................................. 31 FIGURA 9.29. MEZCLAS SUBARREGLOS. ...................................................................................................... 32 FIGURA 9.30. ARREGLO A. ........................................................................................................................... 34 FIGURA 9.31. OCURRENCIAS DE CLAVES. .................................................................................................... 34 FIGURA 9.32. POSICIONES DE ÍNDICES. ........................................................................................................ 34 FIGURA 9.33. COPIA AL ARREGLO B. ............................................................................................................ 35