Está en la página 1de 19
212 Estructura de datos en C++ INTRODUCCION Muchas actividades humanas requieren que a diferentes colecciones de elementos utilizados se pongan en un orden especifico. Las oficinas de correo y las empresas de mensajeria ordenan cl correo y los paquetes por c6digos postales con el objeto de conseguir una entrega eficiente; las facturas telefonicas se ordenan por la fecha de las Ilamadas; los anuarios o listines telef6- nicos se ordenan por orden alfabético de apellidos con el fin ultimo de encontrar facilmente el iniimero de teléfono deseado. Los estudiantes de una clase en la universidad se ordenan por sus apellidos 0 por los niimeros de expediente. Por esta circunstancia una de las tareas que realizan ims frecuentemente las computadoras en el procesamiento de datos es la ordenacién, El estudio de diferentes métodos de ordenacién es una tarea intrinsecamente interesante des: de un punto de vista teérico y, naturalmente, prictico. El capitulo estudia los algoritmos y téeni- cas de ordenacién més usuales y su implementacién en C++, Ademés, se analizan los diferentes métodos de ordenacién con el objeto de conseguir la maxima eficiencia en su uso real. 8.1. ORDENACION La ordenacién o clasficacién de datos (sort en inglés) es una operacién consistente en dispo- ner un conjunto —estructura— de datos en algtin determinado orden con respecto a uno de los campos de los elementos del conjunto. Por ejemplo, cada elemento del conjunto de datos de una guia telefonica tiene un campo nombre, un campo direccién y un campo mimeo de telé- ‘ono: la guia telefonica estédispuesta en orden aliabético de nombres. Los elementos numéri- cos se pueden ordenar en orden creciente o decreciente de acuerdo al valor numérico del ele mento, En terminologia de ordenacién,e1 elemento por el cual esté ordenado un conjunto de datos (0 se esté buscando) se denomina clave Una coleccisn de datos (estructura) puede ser almacenada en memoria central o en archivos de datos exteros guardados en unidades de almacenamiento magnético (discos, cintas, CD-ROM, DVD, etc). Cuando los datos se guardan en memoria principal —un array, una lista enlazada ‘un Grbol— se denomina ordenacidn interna; estos datos se almacenan exclusivamente para ‘ratamientos intemos que se utiizan para gestidn masiva de datos y se guardan en arrays de una © varias dimensiones, Si los datos estén almacenados en un archivo, el proceso de ordenacién se lama ordenacidn externa. Este capitulo estudia los métodos de ordenacién interna Una lista estd ordenada por la clave k sila lista esté en orden ascendente 0 descendente con respecto a esta clave, La lista esté en orden ascendente si: kia) <= RIS ica que wil <= ki para todos los elementos de la lista, Por ejemplo, para una gufa telefinica, la lista est clasifi- cada en orden ascendente por el campo clave k, siendo k el nombre del abonado (apellidos, nombre). www.FreeLibros.me Algortmos de ordenacién y bisqueda 213 Los métodos (algoritmos) de ordenacién son numerosos, por ello se debe prestar especial atencién en su eleccién, ,Como se sabe cull es el mejor algoritmo? La eficiencia es el factor {que mide la calidad y rendimiento de un algoritmo. En el caso de la operacién de ordenacién, dos criterios se suelen seguir a la hora de decidir qué algoritmo —de entre los que resuelven la ordenacién— es el mis eficiente: 1) tiempo menor de ejecucién en computadora; 2) menor nimero de instrucciones. Sin embargo, no siempre es facil efectuar estas medidas: puede no 0 6 aux cs , 8.5.3. Complejidad del algoritmo de insercién ‘A Ia hora de analizar este algoritmo se observa que el ntimero de instrucciones que realiza de: ppende del bucle externo que anida al bucle condicional wie. Siendo n el niimero de elemen- tos, el bucle externo realizan - 1 pasadas, por cada una de ellas y en el peor de los casos (aux siempre menor que a { }-1]), el bucle interno whi 1e itera un nidmero creciente de veces «que da lugar a la sucesién: 1, 2, 3, ... n-1 (para i == n-1). La suma de los términos de la sucesién se ha obtenido en el Apartado 8.3.2, y se ha comprobado que el término domi- nante es 7? Como conclusidn, la complejidad del algoritmo de insercién es O(n’). www.FreeLibros.me 220 Estructura de datos en C++ 8.6. ORDENACION POR BURBUJA 51 método de ondenacidn por burbuja es el més conocido y popular entre estudiantes y apren- dives de programaci6n, por su facilidad de comprender y programar, por el contrario, es el menos eficiente y por ello, normalmente, se aprende su técnica pero no sucle utilizars. La técnica utilizada se denomina ordenacién por burbuja u ordenacién por hundimiento debido a que los valores més pequefios “burbujean” gradualmente (suben) hacia la cima o par: te superior del array de modo similar a como suben las burbujas en el agua, mientras que los valores mayores se hunden en la parte inferior del array. 8.6.1. Algoritmo de la burbuja Para un array con 1 elementos, la ordenacién por burbuja requiere hasta n ~ I pasadas. Por cada pasada se comparan elementos adyacentes y se intercambian sus valores cuando el primer ‘elemento es mayor que el segundo elemento, Al final de cada pasada, el elemento mayor ha “burbujeado” hasta la cima de la sublista actual. Por ejemplo, después que la pasada 1 esté completa, la cola de la lista an - 1] esta ordenada y el frente de la lista permanece desorde- nado, Las etapas del algoritmo son: En la pasada 1 se comparan elementos adyacentes. (210) ,aC21), (@L2]¢a2}), (a121/a031) e+. fafa] ,ats-21) Se realizan n~ 1 comparaciones, por cada pareja (a iJ, a[i+1)), se intercambian los valores si a[i+1) < aft] Al final de la pasada, el elemento mayor de Ia lista esté situado en = [1 En la pasada 2 se realizan las mismas comparaciones e intercambios, terminando con el elemento de segundo mayor valor en a [n~2] # EI proceso termina con la pasada n — 1, en la que el elemento més pequefio se almacena en a(0] El algoritmo tiene una mejora inmediata, el proceso de ordenacién puede terminar en la ppasada n— J, o bien antes, si en un una pasada no se produce intercambio alguno entre elemen: tos del array es porque ya esti ordenado, entonces no es necesario més pasadas. A continuacién, se ilustra el funcionamiento del algoritmo realizando las dos primeras pasadas en un array de $ ‘elementos; se introduce la variable intezzuptor para detectar si se ha producido intercambio cen la pasada, Pasada! | so | 20 | 40 | 80 | 30 Intercambio 60 y 20, ts 20 | so | 40 | 80 | 30 Intercambio 50 y 40, ts 20 so | 80 | 30 50 y 80 ordenados ts www.FreeLibros.me Algortmos de ordenacién y bisqueda 224 t+ »[@[>[=[e) Smee tt to to STS w [RR teen yey enn El algoritmo terminaré cuando se termine la dltima pasada (n~ 1), 0 bien cuando el valor del interrupter sea falso, es decir no se haya hecho ningtin intercambio, 8.6.2. Codificacin del algoritmo de la burbuja El algoritmo de ordenacién de burbuja mejorado contempla dos bucles anidados: el bucle externo controla la cantidad de pasadas, el bucle interno controla cada pasada individualmen- te y cuando se produce un intercambio, cambia el valor de interrupter a verdadero void erdsurbaja (lang al], int a) nt pasada, interruptor ~ false; for (j= 0; j a od hos desordenados, se intercanbsan www.FreeLibros.me 222 Estructura de datos en C++ tereamblar (a(S), a(} + 1) 8.6.3. Anilisis del algoritmo de la burbuja {Cul es la eficiencia del algoritmo de ordenacisn de la burbuja? La ordenacién de burbuja hace una sola pasada en el caso de una lista que ya esté orde- nada en orden ascendente y, por tanto, su complejidad es O(n). En el caso peor se requieren nin 1) (n—i— 1) comparaciones y (ni 1) intercambios. La ordenaci6n completa requiere 2 comparaciones y un némero similar de intercambios. La complejidad para el caso peor es O(n") ‘comparaciones y Of) intercambios. De cualquier forma, el andisis del caso general es complicado dado que alguna de las pa- sadas pueden no realizarse, Se podria seftalar que el ntimero medio de pasadas k es O(n) y el indmero total de comparaciones es O(n’). En el mejor de los casos, la otdenacién por burbuja puede terminar en menos de n ~ 1 pasadas pero requiere, normalmente, muchos més intercam: bios que la ordenacién por scleccién y su prestacién media es mucho més lenta, sobre todo cuando los arrays a ordenar son grandes Los algoritmos de ordenacién inter na: intercambio, seleccién, insercién yb urbuja son faciles de entender y de codificar, sin embargo poco eficientes y no recomendables para ‘ordenar listas de muchos elementos. La complejidad de todos ellos es cuadratica, O( 1. 8.7. ORDENACION SHELL La ordenacién Shell debe el nombre a su inventor, D. L. Shell. Se suele denominar también ordenacién por insercién con incrementos decrecientes. Se considera que es una mejora del meétodo de insercién directa En el algoritmo de insercin, cada elemento se compara con los elementos contiguos de st. izquierda, uno tras otto. Si el elemento a insertar es el més pequeiio hay que realizar muchas comparaciones antes de colocarlo en su lugar definitivo. El algoritmo de Shell modifica los saltos contiguos por saltos de mayor tamafio y con ello consigue que la ordenacién sea més rpida, Generalmente, se toma como salto inicial n/2 (siendo n el ntimero de elementos), lue~ {20 en cada iteracién se reduce el salto a la mitad, hasta que el salto es de tamafio 1. El Ejem- plo 8.1 muestra paso a paso cl método de Shell EJEMPLO 8.1. Aplicar el método Shell para ordenar en orden creciente la lista: 6 1523.40 El ngimero de elementos que tiene la lista es 6, por lo que el salto inicial es 6/2 = 3. La si guiente tabla muestra el ntimero de recorridos realizados en Ia lista con los saltos correspon- diente, www.FreeLibros.me Algortmos de ordenacién y bisqueda 223 Recorrido Salto Intercambios 3 (5216 154d, 2k 56 3 29 0 56 3 ingune ° 56 8.7.1. Algoritmo de ordenacion Shell Los pasos a seguir por el algoritmo para una lista de s elementos: 1, Se divide Ia lista original en n/2 grupos de dos, considerando un ineremento 0 salto entre los elementos de 1/2. 2. Se clasifica cada grupo por separado, comparando las parejas de elementos y si no cestin ordenados se intercambian. 3. Se divide ahora la lista en la mitad de grupos (n/4), con un salto entre los elementos también mitad (n/4), y nuevamente se clasifica cada grupo por separado, 4, Asi sucesivamente, se sigue dividiendo la lista en la mitad de grupos que en el recorri do anterior con un salto decreciente en la mitad que el salto anterior, y Iuego clasifi- cando cada grupo por separado. El algoritmo termina cuando el tamafto del salto es 1 Por consiguiente, los recorridos por la lista estén condicionados por el bucle, mientras {salto > 0) hacer Para dividir la lista en grupos y clasificar cada grupo se anida este cédigo: lesde I © (eaite + 1) hasta n hacer si (aj) < alkJ) entonces Intercambio (alj], alk1) fin mientras find Donde se observa que se comparan pares de elementos de indice jy k, separados por un salto de salto. Asi, sin = 8 el primer valor de sa: 4,y los indices i = 5,3 = 3, x = 6. Los siguiente valores que toman son i = 6} = 2,% = 7, y asi hasta recorrer la lista www.FreeLibros.me 224 Estructura de datos en C++ 8.7.2. Codificacién del algoritmo de ordena n Shell Al codificar el algoritmo se considera que el rango de elementos es 0... 5-1 y, por consi ‘guiente, se ha de desplazar una posicién a la izquierda las variables indice respecto a lo ex- puesto en el algoritmo. void erdenacionshell {double a(}, iat al salte- nf 3; ubile (salto > 0) gl J) par de elementos oxdenado jl alsa 8.7.3. Anal is del algoritmo de ordenacién Shell A pesar de que el algoritmo tiene tres bucles anidados {whi le-fox-whie) , es mas eficien- te que el algoritmo de insercién y que cualquiera de los algoritmos simples analizados en los apartados anteriores. El andlisis del tiempo de ejecucién del algoritmo Shel! no es sencillo. Su inventor, Shell, recomienda que el salto inicial sea n/2, y continuar dividiendo el salto por la mitad hasta conseguir un salto 1. Con esta eleccién se puede probar que el tiempo de ejecucién 8 O(n’) en el peor de los casos, y el tiempo medio de ejecucién es O(n") Posteriormente, se han encontrado secuencias de saltos que mejoran el rendimiento del algoritmo. Asi, dividiendo el salto por 2.2 en lugar de la mitad se consigue un tiempo medio de ejecucién de complejidad menor de O(n’) Nota de programacién La codificacién del algoritmo Shell con el salto igual al salto anter jor dividido por 2.2, puede hacer el salto igual a 0. Si esto ocurre, se ha de codificar que el salto sea igual 2 1, en caso contrario no funcionaria el algoritmo. salto ~ (int) salto / 2.25 salto = (salto == 0) 7 1: salto; www.FreeLibros.me Algorimos de ordenaciiny bisqueda 225 8.8. ORDENACION RAPIDA (QUICKSORT) El algoritmo conocido como quicksort (ordenacién répida) recibe el nombre de su autor, Tony Hoare. El fundamento del algoritmo es simple, se basa en la divisién de Ia lista en particiones a ordenar, en definitiva aplica la técnica *divide y vencerds™. El método es, posiblemen- te, el més pequefio de codigo, més rapido de media, mas elegante y mas interesante y eficien- te de los algoritmos conocidos de ordenacién. El algoritmo divide los n elementos de la lista a ordenar en dos partes o particiones sepa- radas por un elemento: una particién izquierda, un elemento central denominado pivote, y una particién derecha, La particién se hace de tal forma que todos los elementos de la primera su: blista (particidn izquierda) son menores que todos los elementos de la segunda sublista (parti- cién derecha). Las dos sublistas se ordenan entonces independientemente. ara dividir la lista en particiones (sublistas) se elige uno de los elementos de la lista como pivote 0 elemento de particién, Si los elementos de la lista estin en orden aleatorio, se puede ¢legir cualquier elemento como pivote, por ejemplo, el primer elemento de la lista. Sila lista tiene algdin orden parcial, que se conoce, se puede tomar otra decisién para el pivote. Idealmen. te, el pivote se debe elegir de modo que se divida la lista por la mitad, de acuerdo al tamafio relativo de las claves. Por ejemplo, si se tiene una lista de enteros de I a 10, 5 0 6 serfan pivo- tes ideales, mientras que 1 o 10 serian clecciones “pobres" de pivotes. Una vez que el pivote ha sido elegido, se utiliza para ordenar el resto de Ia lista en dos sublistas: una tiene todas las claves menores que el pivore y la otra todas las claves mayores 0 iguales que el pivote. Estas dos listas parciales se ordenan recursivamente utilizando el mismo algoritmo; es decir, se llama sucesivamente al propio algoritmo quicksort. La lista final orde- nada se consigue concatenando la primera sublista, el pivore y la segunda sublista, en ese orden, ‘en una dinica lista. La primera etapa de quicksort es la division o “particionado” recursivo de Ia lista hasta que todas las sublistas consten de s6lo un elemento. EJEMPLO 8.2, Se ordena una lista de nimeros enteros aplicando el algor it lige como pivote el primer elemento de la lista. 1, Lista original pivote elegido 5 | sublista ézquierda (elementos menores que 5) 2 1 3 sublista derecha (elementos mayores o iguales a 5) 9 7 www.FreeLibros.me 226 Estructura de datos en C++ 2. ELalgoritmo se aplica a la sublista izquierda sublistaizquieda 213 1 2 E1 algoritmo se aplica a la sublista derecha sublista derecha 98 4 sass EJEMPLO 8.3. Se aplica el algoritmo quicksort para dividir una lista de nimeros enteros en dos sublistas. El pivote es el elemento central de la lista. Lista original: e14963 5270 pivote (elemento central) 6 Una ver elegido el pivore, la segunda etapa requiere mover todos los elementos menores al pivote a la parte izquierda del array y los elementos mayores a la parte derecha. Para ello se recome Ja lista de izquierda a derecha wiilizando un indice 1, que se inicializa a la posicién més baja (inferior), buscando un elemento mayor al pivote. También se recor el array de dere- cha a izquierda buscando un elemento menor. Para esto se utilizar el indice 3, inicializado a la posicién mas alta (superior). EL indice 1 se detiene en el elemento # (mayor que el pivote) y el indice 5 se detiene en el clemento 0 (menor que el pivote) www.FreeLibros.me Algortmos de ordenacién y bisqueds 227 Ahora, se intercambian [1] y [3] para que estos dos elementos se sitien correctamente ‘en cada sublista; y se incrementa el indice y se decrementa 5 para seguir los intercambios. A medida que el algoritmo continia, 4 se detiene en el elemento mayor, 9, y j se detiene cen el elemento menor, 2, Se intercambian los elementos mientras que { y 3 no se erucen, En caso contrario se detic- ne este bucle. En el caso anterior se intercambian 9 y 2 ‘Contintia la exploracién y ahora el contador yy el indice 3 se detiene en el elemento menor 5 Los indices tienen actualmente los valores jeacaba con i = §; 5 que o 1 4 25 3 fe te se detiene en el elemento 6 (que es el pivote) = 5. Continda la exploracién hasta, www.FreeLibros.me 228 Estructura de datos en C++ En esta posicién los indices : yj han cruzado posiciones en el array, se detiene la buisqueda ¥y no se realiza ningtin intercambio ya que el elemento al que accede ; est ya correctamente si- ‘tuado. Las dos sublistas ya han sido creadas, la lista original se ha dividido en dos particiones: Sublista izquierda pivote _Sublista derecha cases] [ | 8.8.1. Algoritmo Quicksort El primer problema a resolver en el diseiio del algoritmo de quicksort es seleccionar el pivote. Aunque la posicién del pivore, en principio puede ser cualquiera, una de las decisiones mas ponderadas es aquella que considera el pivote como el elemento central o préximo al central de la lista, Una vez que se ha seleccionado el pivote, se ha de buscar el sistema para situar en la sublista izquierda todos los elementos menores que el pivore y en la sublista derecha todos los elementos mayores. La Figura 8.2 muestra las operaciones del algoritmo para ordenar la lista a1) de n elementos enteros, ve [as [15560 Jas] 75 [os] 76] eae + as [8] [eave] |r[e0] [0 \ 76 7] [ss ]es]se 4] 79 | 5] [ea is [eee] [75] “ l | lequierda :24, 21, 1, 46 Prvote 165 Derecha #88, 75, 85, 76, 99, 84, 79 Figura 82. Ordenacién répida eliglendo como pivote el elemento central www.FreeLibros.me Algortmos de ordenacién y bisqueda 229 Los pasos que sigue el algoritmo quicksort son: Seleccionar el elemento central de 2 |} como pivote. Dividir los elementos restantes en particiones izguierda y derecha, de modo que ningiin elemento de la izquierda tenga una clave mayor que el pivote y que ningtin elemento a la derecha tenga una clave mas pequefia que la del pivote, Ordenar la partici6n izquierda utilizando quicksort recursivamente. Ordenar la particién derecha wtilizando quicksort recursivamente. La solucién es particién izquierda seguida por el pivote y la particiGn derecha, 8.8.2. Codificacién del algoritmo QuickSort La implementacién, al igual que el algoritmo, es recursiva; la funcién quicksort () tiene como argumentos el array a(} y los indices que le delimitan: primero y uit imo, void quicksort (double a[], int prinero, int ultimo) double pivote; sli seed quicksort (2, i, ultimo); // mlane proceso con sublista 8.8.3. Anilisis del algoritmo Quicksort El andlisis general de Ia eficiencia del quicksort es dificil. La mejor forma de determinar la complejidad del algoritmo es considerar el ntimero de comparaciones realizadas teniendo en ‘cuenta circunstancias ideales. Supongamos que n (miimero de elementos) es una potencia de 2, www.FreeLibros.me 230 Estructura de datos en C++ n= 2 (k= logan). Ademés, supongamos que el pivote es el elemento central de cada lista, de modo que quicksort divide Ia sublista en dos sublistas aproximadamente iguales. En el primer recorrido se realizan n ~ I comparaciones. Este recorrido crea dos sublista, aproximadamente de tamafo n/2. En la siguiente etapa, el proceso de cada sublista requiere aproximadamente n/2 comparaciones. Las comparaciones totales de esta fase son 2"(n/2) = n ‘A continuacién, se procesan cuatro sublistas que requieren un total de 4*(n/4) comparaciones, ‘lcétera, Eventialment, el proceso de division termina después de k pasadas cuando la sublis- ta resultante tenga tamaio 1, El ntimero total de comparaciones es aproximadamente: n+ 2*(n/2) + A¥( N/A) +o. tna) = NEN HME NRA NF logn EI caso ideal que se ha examinado se realiza realmente cuando Ia lista esta ordenada en ‘orden ascendente. En este caso el pivote es siempre el centro de cada sublista y el algoritmo tiene la complejidad Ofnlog n), t pivote El escenario del caso peor de guicksort ocurre cuando el pivote cae consistentemente en. uuna sublista de un elemento y deja el resto de los elementos en la segunda sublista, Esto suce- de cuando el pivote es siempre el elemento més pequefio de su sublista. En el recorrido inicial, hay n comparaciones y la sublista grande contiene n~ 1 elementos. En el siguiente recorrido, la sublista mayor requiere n — / comparaciones y produce una sublista de » ~ 2 elementos, etc. EI niimero total de comparaciones es: nen 1tn-24..42= (nD) * nt 2/2 Entonces, la complejidad en el caso peor es O(n’). En general, el algoritmo de ordenacién 4quicksort tiene como complejidad media Ofnlog n) siendo posiblemente el algoritmo més ripi- do, La Tabla 8.1 muestra las complejidades de los algoritmos empleados en los métodos expli: ccados en el libro. Tabla 8.1. Comparacién complejidad métodos de ordenacién, Método Complejidad Burbuja ie Tnsereién ® Seleccién ne Monticulo nlog n Mergersort nog n Shell ne? Quicksort log n En conclusién, se suele recomendar que para listas pequefias, los métodos més eticientes son: insercisn y selecciGn, y para listas grandes: quicksort, Shell, mergesort (Apartado 7.5), ¥ ‘monticulo (se desarrolla en Capitulo 13; Colas de Prioridades y Monticulos). www.FreeLibros.me

También podría gustarte