Sie sind auf Seite 1von 9

Ordenacin por el mtodo de Shell (ShellSort)

Detalles Siempre me ha fascinado el algoritmo de ordenacion de Shell. Debe su nombre al ingeniero y matemtico estadounidense Donald Shell, que lo public en la revista Communications of the ACM en 1959. Es un algoritmo de ordenacin interna muy sencillo pero muy ingenioso, basado en comparaciones e intercambios, y con unos resultados radicalmente mejores que los que se pueden obtener con el mtodo de la burbuja, el de seleccin directa o el de insercin directa. Aunque a menudo, es un algoritmo un tanto olvidado por dos motivos: en primer lugar, en los cursos bsicos de programacin se suele pasar por alto o se pasa "de puntillas" por este algoritmo, dado que su comprensin requiere un cierto esfuerzo y algo de tiempo, y se suele centrar la atencin en los tres algoritmos ms bsicos (burbuja, seleccin e insercin); y en segundo lugar, el algoritmo QuickSort, desarrollado por Hoare en 1962 puede dar mejores resultados an que ste, con lo cual, le suele hacer bastante sombra en los temarios de los cursos de programacin bsicos. Sin embargo, es necesario romper una lanza a favor del algoritmo ShellSort, ya que es el mejor algoritmo de ordenacin in-situ... es decir, el mejor de aquellos en los que la cantidad de memoria adicional que necesita -aparte de los propios datos a ordenar, claro est- es constante, sea cual sea la cantidad de datos a ordenar. El algortimo de la burbuja, el de seleccin directa, el de insercin directa y el de Shell son todos in-situ, y ste ltimo, el de Shell, es el que mejor resultados da, sin ninguna duda de todos ellos y sus posibles variantes. Por supuesto que otros mtodos de ordenacin, como QuickSort, BinSort, HeapSort o RadixSort pueden pueden superar a ShellSort en cuanto al tiempo de ejecucin, pero ninguno de ellos es ya un algoritmo in-situ. En todos ellos es necesario gestionar una cantidad adicional de memoria proporcional al tamao de los datos a ordenar... pero de ellos hablaremos en otros artculos. En este artculo nos centraremos en ShellSort. Describiremos la idea que subyace detrs del algoritmo, la k-ordenacin, e intentaremos llegar de manera intuitiva al cdigo del algorimo. Finalmente, intentaremos hablar de alguna simplificacin y optimizacin del algoritmo. Todo ello, sin meternos en el berenjenal de demostrar su correccin. Intentaremos que en la medida de lo posible no sea demasiado pesado, utilizando ejemplos y animaciones.

CARACTERSTICAS
Bueno... ya hemos comentado algunas de ellas en la introduccin.

Se trata de un algoritmo de ordenacin interna. Al igual que cualquier otro de ordenacin interna (los datos estn en memoria principal) podra utilizarse para ordenacin externa (en memoria secundaria) siempre y cuando se disponga de acceso aleatorio, pero el algoritmo no fue ideado para eso. Se basa en comparaciones e intercambios. Necesita que el tiempo de acceso a cualquier dato sea constante (es decir, fue ideado para trabajar con arrays, arrays de referencias o punteros, etc...). Ojo a otras estructuras, como listas enlazadas, etc... ya que en ese caso, el tiempo de acceso a un elemento no es constante, depende de la posicin del elemento. No es estable: dados dos elementos que al compararlos sean "iguales" no mantienen necesariamente el orden relativo inicial entre ellos El estudio de su complejidad no es trivial, sino todo lo contrario. La implementacin original de Shell tiene una complejidad en el peor caso de O(n2), aunque en un caso promedio o en casos tpicos comprobados empricamente, los resultados son mucho mejores que con la burbuja, seleccin directa o insercin directa, cuya complejidad en el peor caso tambin es del orden de O(n2). Sin embargo, optimizaciones posteriores han logrado reducir esa cota... Por ejemplo, con la optimizacin de Robert Sedgewick se llega a O(n4/3), y con la propuesta por Vaughan Pratt se llega al orden de O(n log2n). En 1992, Greg Plaxton, Bjorn Poonen y Torsten Suel prueban que es posible incluso rebajar an ms esas cotas. En cierto modo, puede considerarse una ampliacin del algortimo de insercin directa, con lo cual, conviene tenerlo claro antes de meterse con el de Shell. No es un algoritmo en-lnea.

(Alguno de los conceptos de ste apartado te es extrao? Consulta Algoritmos de ordenacin).

EL FUNDAMENTO: la k-ordenacin.
Imaginemos una lista de datos ordenables... por ejemplo, estos enteros: 74, 14, 21, 44, 38, 97, 11, 78, 65, 88, 30 Siempre se utilizan enteros para estos ejemplos porque es un tipo de dato sencillo, cmodo y estamos muy familiarizados con ellos... pero podran ser datos de cualquier otro tipo, siempre y cuando tengamos un criterio de ordenacin para compararlos. Cada uno de estos enteros, mantiene una posicin en la lista (o array). En el algoritmo de Insercin directa bamos uno por uno, desde el segundo (14) al ltimo (30) desplazando hacia la izquierda cada elemento hasta su posicin entre los anteriores.

Bien... ya vimos que eso funcionaba relativamente bien... pero vamos a echar un vistazo intutivo a los datos. Fijmonos, por ejemplo, que los enteros estn comprendidos entre el 11 (el ms pequeo) y el 97 (el ms grande) y que parecen ms o menos uniformemente distribuidos. Intuitivamente, podemos estar casi seguros de que el 97 estar por el final (muy a la derecha)... el 88 tambin... El 14 y el 11 estarn por el principio (muy a la izquierda) y parece sensato pensar que quiz el 44 o el 65 estarn mas o menos por la mitad. Sin embargo, el algoritmo de Insercin, sistemticamente "arrastrar" cada elemento comparndolo de uno en uno con los anteriores hasta encontrar su posicin correcta. Si fueramos capaces de "intutivamente" mover los elementos ms grandes al final del array de un gran salto (con poco esfuerzo, pero tambin con poca precisin), los ms pequeos al principio y los medianos los dejamos por enmedio no habramos conseguido ordenar el array, pero si esa operacin nos cuesta poco esfuerzo, una ordenacin exhaustiva posterior tambin requerira menos esfuerzo, porque cada elemento estara cerca de su ubicacin definitiva. Imagina que te piden que en lugar de ordenar enteros, tienes que ordenar piedras... Si, si... piedras... por tamao, con ayuda de una carretilla.

Si aplicaras el algoritmo de Insercin directa tal cual haras muchas comparaciones y desplazamientos. Sin embargo, si colocas las que son ms o menos pequeas hacia el principio y las que son ms gordas hacia el final, dejando las medianas por la mitad, no habrs conseguido ordenar las piedras todava... pero las habrs dejado mucho ms cerca de su ubicacin definitiva. Ordenarlas ahora exhaustivamente supone menos esfuerzo.

Esta es la idea bsica del ShellSort: si podemos desplazar "a grosso modo" los elementos ms grandes hacia el final y los ms pequeos hacia el principio con poco esfuerzo, estaremos dejando cada elemento ms cerca de su ubicacin definitiva. Si finalmente aplicamos un mtodo como InsertionSort, cada elemento se tendr que desplazar pocos lugares. Pedirle a un ordenador que haga algo intuitivamente es, de momento, bastante complicado, as que sustituiremos la intuicin por un procedimento mecnico ms o menos ingenioso. Volvamos al array del principio. 74, 14, 21, 44, 38, 97, 11, 78, 65, 88, 30

Shell nos propone que hagamos sobre el array una serie de ordenaciones basadas en la insercin directa, pero dividiendo el array original en varios sub-arrays tales que cada elemento est separado k elementos del anterior (a esta separacin a menudo se le llama salto o gap)... Se debe empezar con k=n/2, siendo n el nmero de elementos de array, y utilizando siempre la divisin entera.... despus iremos variando k hacindolo ms pequeo mediante sucesivas divisiones por 2, hasta llegar a k=1. Pero vamos a ello... En nuestro ejemplo, n=11 (porque hay 11 elementos). As que k=n/2=11/2=5 Empezamos con k=5. As pues, vamos a dividir nuestro array original en 5 sub-arrays, en los cuales, sus elementos estarn separados por 5 lugares del array original (el salto o gap es 5). Vamos a hacerlo con colores. Tomamos el primer elemento (el 74) contamos 5 lugares y tomamos tambin otro elemento (el 97) volvemos a contar 5 y tomamos otro (el 30) y acabamos porque se nos acaba el array. El primer sub-array con k=5 es el formado por 74, 97 y 30. Vamos a pintarlos en rojo 74, 14, 21, 44, 38, 97, 11, 78, 65, 88, 30 Ahora, ordenaremos los elementos del sub-array rojo pero slo entre ellos, utilizando el algoritmo de Insercin directa. 30, 14, 21, 44, 38, 74, 11, 78, 65, 88, 97 Fjate qu curioso. El 30, un elemento relativamente pequeo se ha ido hacia el principio y el 97 hacia el final... pero dando saltos (gap) de 5 en 5 lugares! Cada uno ha avanzado en saltos de 5 hacia una posicin cercana a su ubicacin definitiva. Formemos ahora otro sub-array con salto k=5... partiendo del segundo elemento (el 14) y contando 5 (tomamos tambin el 11) y ya est, porque se acaba el array. 30, 14, 21, 44, 38, 74, 11, 78, 65, 88, 97 Vamos a ordenarlos entre ellos con Insercin directa... el 11 primero y el 14 despus. 30, 11, 21, 44, 38, 74, 14, 78, 65, 88, 97 Ahora a por otro... el 21 y el 78 30, 11, 21, 44, 38, 74, 14, 78, 65, 88, 97 Estn en orden entre ellos, as que se quedan como estn. Ahora le toca al sub-array formado por el 44 y el 65

30, 11, 21, 44, 38, 74, 14, 78, 65, 88, 97 Que tambin estn en orden entre ellos... y finalmente el 38 y el 88, que tambin estn en orden. 30, 11, 21, 44, 38, 74, 14, 78, 65, 88, 97 Hemos formado 5 sub-arrays en los cuales los elementos estn separados por 5 lugares (porque k=5). Hemos ordenado cada sub-array por separado utilizando insercin directa, y hemos logrado que cada elemento se dirija hacia su ubicacin definitiva en pasos de 5 lugares. Por supuesto, no hemos terminado todava, pero resulta evidente que algunos elementos, como el 30, el 97 o el 11 han dado un gran salto y que no deben andar muy lejos de su sitio final. Decimos ahora que el array est 5-ordenado. Para continuar con el algortimo, debemos ir reduciendo progresivamente k dividindolo sucesivamente por 2 y k-ordenando los subarrays que nos salgan (recuerda que nos salen k subarrays). Cuando lleguemos a k=1 habremos terminado. Pero de momento, nuestra k vala 5, as que ahora kk/2=5/2=2 Nuestra nueva k vale 2. Repetimos todo el tinglado, pero ahora nos saldrn 2 sub-arrays cuyos elementos estn separados por 2 lugares. El primero (en marrn) y el segundo (en verde): 30, 11, 21, 44, 38, 74, 14, 78, 65, 88, 97 Ordenamos por un lado los marrones entre ellos y los verdes entre ellos... es decir, 2-ordenamos el array (curiosamente, los verdes ya estn ordenados.... probablemente ha contribuido a ello la 5ordenacion que ya hemos hecho. En ese caso, la ordenacin ha requerido muy poco esfuerzo) 14, 11, 21, 44, 30, 74, 38, 78, 65, 88, 97 Ahora, cada nmero est mucho ms cerca de su posicin definitiva... El array est 2-ordenado... pero sigue tambin 5-ordenado. No hemos perdido el trabajo que hicimos cuando k era 5. Finalmente, calculamos un nuevo k diviendo el que tenemos entre 2. kk/2=2/2=1 Hemos llegado a k=1. Cuando k es 1 slo podemos obtener 1 sub-array cuyos elementos estn separados 1 posicin: el propio array original. Dicho de otra manera... cuando k es 1, el algoritmo de Shell se comporta exactamente igual que el de insercin directa sobre todo el array. Sin embargo, las k-ordenaciones que hemos hecho (con k=5 y k=2) han hecho que cada elemento se aproximase con saltos de 5 y luego de 2 posiciones hacia su ubicacin definitiva. Ahora que k=1 y que vamos a aplicar el algoritmo de insercin directa tal cual, haremos muchas menos

comparaciones e intercambios que si lo hubieramos aplicado con en array tal como lo tenamos al empezar. El algoritmo de insercin directa se comporta tanto mejor cuanto ms cerca est cada elemento de su sitio definitivo. Finalmente, el array queda de sta manera: 11, 14, 21, 30, 38, 44, 65, 74, 78, 88, 97 Cada elemento descolocado ha tenido que moverse pocos lugares. Muchos de ellos ni siquiera se han movido. Todava confus@? A ver si sta animacin de baja tecnologa que hemos preparado te puede ayudar. En ella ordenamos 9 naipes de la baraja espaola: del AS al 9 de oros, utilizando el algoritmo de Shell.

Algoritmos de Ordenamiento
Debido a que las estructuras de datos son utilizadas para almacenar informacin, para poder recuperar esa informacin de manera eficiente es deseable que aquella est ordenada. Existen varios mtodos para ordenar las diferentes estructuras de datos bsicas. En general los mtodos de ordenamiento no son utilizados con frecuencia, en algunos casos slo una vez. Hay mtodos muy simples de implementar que son tiles en los casos en dnde el nmero de elementos a ordenar no es muy grande (ej, menos de 500 elementos). Por otro lado hay mtodos sofisticados, ms difciles de implementar pero que son ms eficientes en cuestin de tiempo de ejecucin. Los mtodos sencillos por lo general requieren de aproximadamente n x n pasos para ordenar n elementos. Los mtodos simples son: insertion sort (o por insercin directa) selection sort, bubble sort, y shellsort, en dnde el ltimo es una extensn al insertion sort, siendo ms rpido. Los mtodos ms complejos son el quick-sort, el heap sort, radix y address-calculation sort. El ordenar un grupo de datos significa mover los datos o sus referencias para que queden en una secuencia tal que represente un orden, el cual puede ser numrico, alfabtico o incluso alfanumrico, ascendente o descendente. Se ha dicho que el ordenamiento puede efectuarse moviendo los registros con las claves. El mover un registo completo implica un costo, el cual se incrementa conforme sea mayor el tamao del registro. Es por ello que es deseable evitar al mximo el movimiento de los registros. Una alternativa es el crear una tabla de referencias a los registros y mover las referencias y no los datos. A continuacin se mostrarn los mtodos de ordenamiento empezando por el ms sencillo y avanzando hacia los mas sofisticados La eficiencia de los algoritmos se mide por el nmero de comparaciones e intercambios que tienen que hacer, es decir, se toma n como el nmero de elementos que tiene el arreglo a ordenar y se dice que un algoritmo realiza O(n2) comparaciones cuando compara n veces los n elementos, n x n = n2.

Anlisis de Algoritmos Notacin de Orden Una funcin f(n) se define de orden O(g(n)), es decir, f(n) = O(g(n)) si existen constantes positivas n0 y c tales que:
| f(n) | = c * <= | g(n) | , para toda n > n0

100 n3 => O(n3) 6n2 + 2n + 4 => O(n2) 1024 => O(1) 1+2+3+4+...+n-1+n= n * (n+1)/2 = O(n2)

Insertion Sort

Este es uno de los mtodos ms sencillos. Consta de tomar uno por uno los elementos de un arreglo y recorrerlo hacia su posicin con respecto a los anteriormente ordenados. As empieza con el segundo elemento y lo ordena con respecto al primero. Luego sigue con el tercero y lo coloca en su posicin ordenada con respecto a los dos anteriores, as sucesivamente hasta recorrer todas las posiciones del arreglo. Este es el algoritmo: Procedimiento Insertion Sort Este procedimiento recibe el arreglo de datos a ordenar a[] y altera las posiciones de sus elementos hasta dejarlos ordenados de menor a mayor. N representa el nmero de elementos que contiene a[].
paso 1: [Para cada pos. del arreglo] paso 2: [Inicializa v y j] For i <- 2 to N do v <- a[i] j <- i. paso 3: [Compara v con los anteriores] While a[j-1] > v AND j>1 do paso 4: [Recorre los datos mayores] Set a[j] <- a[j1], paso 5: [Decrementa j] set j <- j-1. paso 5: [Inserta v en su posicin] Set a[j] <- v. paso 6: [Fin] End.

Ejemplo: Si el arreglo a ordenar es a = ['a','s','o','r','t','i','n','g','e','x','a','m','p','l','e'], el algoritmo va a recorrer el arreglo de izquierda a derecha. Primero toma el segundo dato 's' y lo asigna a v y i toma el valor de la posicin actual de v. Luego compara esta 's' con lo que hay en la posicin j-1, es decir, con 'a'. Debido a que 's' no es menor que 'a' no sucede nada y avanza i.

Ahora v toma el valor 'o' y lo compara con 's', como es menor recorre a la 's' a la posicin de la 'o'; decrementa j, la cual ahora tiene la posicin en dnde estaba la 's'; compara a 'o' con a[j-1] , es decir, con 'a'. Como no es menor que la 'a' sale del for y pone la 'o' en la posicin a[j]. El resultado hasta este punto es el arreglo siguiente: a = ['a','o','s','r',....] As se contina y el resultado final es el arreglo ordenado : a = ['a','a','e','e','g','i','l','m','n','o','p','r','s','t','x']

Selection Sort

El mtodo de ordenamiento por seleccin consiste en encontrar el menor de todos los elementos del arreglo e intercambiarlo con el que est en la primera posicin. Luego el segundo mas pequeo, y as sucesivamente hasta ordenar todo el arreglo.

Procedimiento Selection Sort


paso paso paso do paso 1: [Para cada pos. del arreglo] 2: [Inicializa la pos. del menor] 3: [Recorre todo el arreglo] For i <- 1 to N do menor <- i For j <- i+1 to N

4: [Si a[j] es menor] If a[j] < a[menor] then paso 5: [Reasigna el apuntador al menor] = j paso 6: [Intercambia los datos de la pos. min y posicin i] Swap(a, min, j). paso 7: [Fin] End.

min

Ejemplo: El arreglo a ordenar es a = ['a','s','o','r','t','i','n','g','e','x','a','m','p','l','e']. Se empieza por recorrer el arreglo hasta encontrar el menor elemento. En este caso el menor elemento es la primera 'a'. De manera que no ocurre ningn cambio. Luego se procede a buscar el siguiente elemento y se encuentra la segunda 'a'. Esta se intercambia con el dato que est en la segunta posicin, la 's', quedando el arreglo as despus de dos recorridos: a = ['a','a','o','r','t','i','n','g','e','x','s','m','p','l','e']. El siguiente elemento, el tercero en orden de menor mayor es la primera 'e', la cual se intercambia con lo que est en la tercera posicin, o sea, la 'o'. Le sigue la segunda 's', la cual es intercambiada con la 'r'. El arreglo ahora se ve de la siguiente manera: a = ['a','a','e','e','t','i','n','g','o','x','s','m','p','l','r']. De esta manera se va buscando el elemento que debe ir en la siguiente posicin hasta ordenar todo el arreglo.

El nmero de comparaciones que realiza este algoritmo es : para el primer elemento se comparan n-1 datos, en general para el elemento i-simo se hacen n-i comparaciones, por lo tanto, el total de comparaciones es: la sumatoria para i de 1 a n-1 (n-i) = 1/2 n (n-1).

Shellsort

Denominado as por su desarrollador Donald Shell (1959), ordena una estructura de una manera similar a la del Bubble Sort, sin embargo no ordena elementos adyacentes sino que utiliza una segmentacin entre los datos. Esta segmentacin puede ser de cualquier tamao de acuerdo a una secuencia de valores que empiezan con un valor grande (pero menor al tamao total de la estructura) y van disminuyendo hasta llegar al '1'. Una secuencia que se ha comprobado ser de las mejores es: ...1093, 364, 121, 40, 13, 4, 1. En contraste, una secuencia que es mala porque no produce un ordenamiento muy eficiente es ...64, 32, 16, 8, 4, 2, 1. Su complejidad es de O(n1.2) en el mejor caso y de O(n1.25) en el caso promedio.

void shellsort ( int a[], int n) /* Este procedimiento recibe un arreglo a ordenar a[] y el tamao del arreglo n. Utiliza en este caso una serie de t=6 incrementos h=[1,4,13,40,121,364] para el proceso (asumimos que el arreglo no es muy grande). */ { int x,i,j,inc,s; for(s=1; s < t; s++) /* recorre el arreglo de incrementos */ { inc = h[s]; for(i=inc+1; i < n; i++) { x = a[i]; j = i-inc; while( j > 0 && a[j] > x) { a[j+h] = a[j]; j = j-h; } a[j+h] = x; } } }

Das könnte Ihnen auch gefallen