1.1 TIPOS Y FUNCIONES AUXILIARES //@ pre: (0<=i) && (i<a.length) && (0<=j) && (j<a.length) static void intercambia (int[] a, int i, int j) { int temp; temp = a[i]; a[i] =a [j]; a[j] = temp; } //@ pre: (0<=prim) && (prim<=ult) && (ult<a.length) static int posMinimo (int[] a, int prim, int ult) { int k; int pmin = prim; for ( k = prim+1; k <= ult; k++ ) if ( a[k] < a[pmin] ) pmin = k; return pmin; } //@ pre: (0<=prim) && (prim<=ult) && (ult<a.length) static int posMaximo (int[] a, int prim, int ult) { int k; int pmax = prim; for ( k = prim+1; k <= ult; k++ ) if ( a[k] > a[pmax] ) pmax = k; return pmax; } 1 1.2 ORDENACIÓN POR INSERCIÓN //@ pre: (0<= prim) && (prim<= ult) && (ult<a.length) public static void insercion (int[] a, int prim, int ult) { int p, temp, j; for ( p = prim+1; p <= ult; p++ ) { temp = a[p]; j = p-1; while ( ( prim <= j ) && ( temp < a[j] ) ) { a[j+1] = a[j]; j--; } a[j+1] = temp; } } 1.3 ORDENACIÓN POR SELECCIÓN //@ pre: (0<= prim) && (prim<= ult) && (ult<a.length) public static void seleccion (int[] a, int prim, int ult) { int i; for ( i = prim; i < ult; i++ ) { intercambia (a, i, posMinimo (a, i, ult)); } } 1.4 ORDENACIÓN POR BURBUJA //@ pre: (0<= prim) && (prim<= ult) && (ult<a.length) public static void burbuja (int[] a, int prim, int ult) { int i, j; for ( i = prim; i<ult; i++) for (j = ult; j > i; j--) if ( a[j-1] > a[j] ) intercambia(a,j-1,j); } 2 1.5 ORDENACIÓN POR MEZCLA Este método utiliza la técnica de divide y vencerás para realizar la ordenación del vector a. Así divide a a en dos subvectores, que son ordenados por llamadas recursivas a este método, y luego mezcla los dos subvectores ya ordenados: //@ pre: (0<=prim) && (prim<=ult) && (ult<a.length) && (a.length = b.length) public static void mezcla (int [] a,int [] b, int prim, int ult) { int mitad; if ( prim < ult ) { mitad = (prim + ult) / 2; mezcla (a, b, prim, mitad); mezcla (a, b, mitad+1, ult); combinar (a, b, prim, mitad, mitad+1, ult); } } //@ pre: (0<=p1) && (p1<=u1) && (u1=p2-1) && (p2<=u2) && (u2<a.length) // && (a.length= b.length) static void combinar (int[] a, int[] b, int p1, int u1, int p2, int u2) { int k, i1, i2; for ( k = p1; k <= u2; k++ ) b[k] = a[k]; // copiamos a en b i1 = p1; i2 = p2; for ( k = p1; k <= u2; k++ ) { if ( b[i1] <= b[i2] ) { a[k] = b[i1]; if ( i1 < u1 ) i1++; else b[i1] = Integer.MAX_VALUE; } else { a[k] = b[i2]; if ( i2 < u2 ) i2++; else b[i2] = Integer.MAX_VALUE; } } } Complejidad Este método ordena n elementos en tiempo Θ(nlogn) en cualquiera de los casos peor, mejor o medio. Sin embargo tiene una complejidad espacial (en cuanto a memoria) mayor que los demás (del orden de n). 3 1.6 ORDENACIÓN HEAPSORT //@ pre: (0<= prim) && (prim<= ult) && (ult<a.length) public static void monticulos (int [] a, int prim, int ult) { int i; hacerMonticulo (a, prim, ult); for ( i = ult; i >= prim; i-- ) { intercambia (a, prim, i); empujar (a, prim, i-1, prim); } } //@ pre: (0<= prim) && (prim<= ult) && (ult<a.length) // construye un heap a partir de a[prim..ult]. Complejidad O(n) static void hacerMonticulo (int [] a, int prim, int ult) { int cal = ( ult - prim+1 ) / 2; int i; for ( i = cal; i >= 0; i-- ) empujar (a, prim, ult, prim+i); } //@ pre: (0<= prim) && (prim<= ult) && (ult<a.length) && (prim<=i<=ult) // empuja el elemento de a en posicion i, hasta su posicion final en un heap static void empujar (int [] a, int prim, int ult, int i) { int k = i - prim; int j = k; if( ( 2*j+1 <= ult-prim ) && ( a[2*j+1+prim] > a[k+prim] ) ) k = 2*j+1; if( ( 2*j+1 < ult-prim ) && ( a[2*j+2+prim] > a[k+prim] ) ) k = 2*j+2; intercambia (a, j+prim, k+prim ); while ( k != j ) { j = k; if ( ( 2*j+1 <= ult-prim ) && ( a[2*j+1+prim] > a[k+prim] ) ) k = 2*j+1; if ( ( 2*j+1 < ult-prim ) && ( a[2*j+2+prim] > a[k+prim] ) ) k = 2*j+2; intercambia (a, j+prim, k+prim); } } Complejidad La complejidad temporal de este método es Θ(nlogn) independientemente de la entrada (del caso), y no usa memoria extra. Es unas dos veces más lento que el método Quicksort en el caso medio. 4 1.7 ORDENACIÓN QUICKSORT //@ pre: (0<= prim) && (prim<= ult) && (ult<a.length) public static void quicksort (int [] a, int prim, int ult) { int l; if ( prim < ult ) { l = pivote (a, a[prim], prim, ult ); quicksort (a, prim, l-1); quicksort (a, l+1, ult); } } La función Pivote parte de un elemento especial (llamado pivote) y permuta los elementos del vector de forma que al finalizar la función, todos los elementos menores o iguales que el pivote estén a su izquierda, y los elementos mayores que él a su derecha. Devuelve la posición en la que ha quedado situado el pivote p: //@ pre: (0<=prim) && (prim<=ult) && (ult<a.length) static int pivote (int [] a, int p, int prim, int ult) { int i = prim+1; int l = ult; while ( (i <= ult) && ( a[i] <= p) ) i++; while ( a[l] > p ) l--; while ( i < l ) { intercambia (a, i, l); while ( a[++i] <= p ); while ( a[--l] > p ); } intercambia (a, prim, l); return l; } Complejidad El procedimiento Quicksort rompe la filosofía de caso mejor, peor y medio de los algoritmos clásicos de ordenación, pues aquí tales casos no dependen de la ordenación inicial del vector, sino de la elección del pivote. Como mejor pivote debe escogerse la mediana del vector, lo que ocurre es que encontrarla hace que el algoritmo se vuelva más ineficiente en la mayoría de los casos. Por esa razón como pivote suele escogerse un elemento cualquiera, a menos que se conozca la naturaleza de los elementos a ordenar. En nuestro caso, como a priori suponemos equiprobable cualquier ordenación inicial del vector, hemos escogido el primer elemento del vector, que es el que se le pasa como segundo argumento a la función “pivote”. Esta elección conlleva a tres casos desfavorables para el algoritmo, en donde la partición se realiza de forma totalmente descompensada, lo que hace que la complejidad sea cuadrática: cuando los elementos son todos iguales, y cuando el vector está inicialmente ordenado en orden creciente o decreciente. 5 1.8 ORDENACIÓN SHELLSORT //@ pre: (0<= prim) && (prim<= ult) && (ult<a.length) public static void incrementos (int [] a, int prim, int ult) { int n = ult-prim; int h = 4; int i, j, v; while ( h <= n ) h = 3*h +1; // construimos la secuencia while ( h >= 1 ) { for ( i = h; i <= n; i++ ) { v = a[i]; j = i; while ( ( j >= h ) && ( a[j-h+prim] > v ) ) { a[j + prim] = a[j-h+prim]; j = j-h; } a[j + prim] = v; } h = h/3; } } Este programa utiliza la secuencia: ..., 1093, 364, 121, 40, 13, 1. Complejidad Este método es diferente al resto de los procedimientos vistos en este capítulo. Su complejidad es difícil de calcular y depende mucho de la secuencia de incrementos que utilice. Por ejemplo, para la secuencia dada existen dos conjeturas en cuanto a su orden de complejidad: nlog2n y n1,25. Además, este algoritmo no es muy sensible a la ordenación inicial del vector. 6