La descarga está en progreso. Por favor, espere

La descarga está en progreso. Por favor, espere

Algoritmos de ordenamiento

Presentaciones similares


Presentación del tema: "Algoritmos de ordenamiento"— Transcripción de la presentación:

1 Algoritmos de ordenamiento
Capítulo 9: Algoritmos de ordenamiento

2 En este capítulo se tratarán los siguientes temas:
9.1 Introducción 9.2 Bubble sort (ordenamiento por burbujeo) 9.3 Selection sort (ordenamiento por selección) 9.4 Insertion sort (ordenamiento por inserción) 9.5 Quicksort (ordenamiento rápido) 9.6 Heapsort (ordenamiento por montículos) 9.7 Shellsort (ordenamiento Shell) 9.8 Binsort (ordenamiento por cajas) 9.9 Radix sort (ordenamiento de raíz)

3 9.1 Introducción Explicaremos la lógica de los algoritmos de ordenamiento, analizaremos su complejidad algorítmica e incluso mediremos su rendimiento, pero en todos los casos la implementación quedará a cargo del lector y constituirá la guía de ejercicios del presente capítulo. Veremos que los algoritmos más simples, como el caso de bubble sort, tienen un orden de complejidad cuadrática O(n2) mientras que los más eficientes, y también más difíciles de implementar, tienen una complejidad cuasi lineal O(n log(n)). Para facilitar la comprensión de los algoritmos de ordenamiento, solo trabajaremos sobre arrays de valores enteros. El lector ya sabe que proveyendo una adecuada implementación de Comparator, se puede extender la funcionalidad de estos algoritmos a arrays de cualquier otro tipo de datos.

4 9.2 Bubble sort (ordenamiento por burbujeo)
El algoritmo de la burbuja consiste en recorrer el array comparando el valor del i-ésimo elemento con el valor del elemento i+1 y, si estos se encuentran desordenados, entonces hay que permutarlos. Dicho de otro modo, comenzamos comparando arr[0] con arr[1]. Si arr[0]>arr[1] entonces los permutamos. Luego comparamos arr[1] con arr[2] y, si corresponde, también los permutamos y así hasta llegar a comparar arr[len-2] con arr[len-1]. Luego de esta “pasada” o iteración a través de los elementos del array, el elemento de mayor valor o el más “pesado” quedará ubicado en la última posición. El peor escenario con el que nos podemos encontrar es el de tener que ordenar un array completamente desordenado, es decir, en orden inverso. Por ejemplo, si el array fuese: arr = {4, 3, 2, 1, 0}, entonces:

5 9.2 Bubble sort (ordenamiento por burbujeo)
Arr 4 3 2 1 Primera iteración Segunda iteración arr Tercera iteración Cuarta iteración Quinta iteración Como vemos, en cada iteración llevamos hacia abajo al elemento más pesado y solo podemos considerar que el array quedó ordenado luego de realizar una iteración en la que no haya sido necesario hacer ninguna permutación. Así, para ordenar el array arr de longitud 5 necesitamos realizar 5 iteraciones, en cada una de las cuales haremos 4 comparaciones. Si bien el algoritmo de ordenamiento por burbujeo es muy fácil de implementar, su complejidad cuadrática limita seriamente su capacidad para ordenar arrays de gran tamaño. Para probarlo empíricamente analizaremos su comportamiento al ordenar arrays de 10, 100, 1000, 10000, y elementos.

6 9.2 Bubble sort (ordenamiento por burbujeo)
Para generar arrays de semejantes tamaños desarrollaremos la clase utilitaria UArray con los siguientes métodos: generarArray – genera un array de cualquier longitud con valores aleatorios u ordenados de mayor a menor. estaOrdenado - retornará true o false según el array que reciba como parámetro esté o no ordenado ascendentemente. public class UArray { public static int[] generarArray(int n, boolean random) int arr[]=new int[n]; for( int i=0; i<n; i++ ) if( random ) arr[i]=(int)(Math.random()*n); } else arr[i]=n-i; return arr; public static boolean estaOrdenado(int arr[]) for( int i=0; i<arr.length-1; i++ ) if( arr[i+1]<arr[i] ) return false; return true; El método estaOrdenado será de mucha utilidad para poder verificar si las implementaciones de nuestros algoritmos funcionan correctamente con arrays de gran tamaño.

7 9.2.1 Bubble sort optimizado
Si aprovechamos el hecho de que luego de cada iteración el elemento más “pesado” se ubica en la última posición del array (es decir, en su posición definitiva), podemos mejorar el algoritmo evitando comparar, innecesariamente, aquellos elementos que ya quedaron ordenados. Es decir, en la primera iteración, comparamos todos los elementos del array hasta llegar a comparar arr[len-2] con arr[len-1]. En la segunda iteración, comparamos todos los elementos, pero solo hasta comparar arr[len-3] con arr[len-2] porque, como ya sabemos, en arr[len-1] se encuentra el elemento que, definitivamente, ocupará esa posición. El desarrollo de esta variante del algoritmo queda a cargo del lector pero a título informativo podemos comentar que una implementación ejecutada sobre la misma computadora mencionada más arriba arrojó los siguientes resultados al ordenar arrays de 10, 100, 1000, 10000, y elementos: 0 milisegundos (0 minutos, 0 segundos) true 94 milisegundos (0 minutos, 0 segundos) true 9140 milisegundos (0 minutos, 9 segundos) true milisegundos (15 minutos, 51 segundos) true El rendimiento mejoró un 300% respecto de la implementación anterior, incluso logró terminar de ordenar el array de 1 millón de elementos, aunque demoró algo más de 15 minutos.  Si bien la mejora es importante, el rendimiento del algoritmo sigue siendo inaceptable para ordenar arrays de gran tamaño.

8 9.3 Selection sort (ordenamiento por selección)
El algoritmo de ordenamiento por selección es simple y consiste en recorrer el array buscando el menor elemento para intercambiarlo con el primero. Luego recorrer el array, pero comenzando desde la segunda posición para buscar el menor elemento e intercambiarlo por el segundo y así sucesivamente. Es decir que si consideramos un array arr y un índice i=0, debemos buscar el menor elemento de arr entre las posiciones i y len-1 e intercambiarlo con arr[i]. Luego incrementamos i para descartar el primer elemento porque, obviamente, ya contiene su valor definitivo. En el siguiente gráfico, vemos cómo se ordena un array arr = {3, 4, 1, 0, 2} luego de len-1 iteraciones. El algoritmo de ordenamiento por selección es de orden cuadrático O(n2) y tiene un rendimiento similar al de bubble sort optimizado.

9 9.4 Insertion sort (ordenamiento por inserción)
Pensemos en un array inicialmente vacío. Luego, cualquier elemento que le agreguemos (llamémosle e1) ocupará la primera posición y el array estará ordenado. Si agregamos otro elemento (digamos e2), este deberá ubicarse antes o después de e1 según se verifique o no que e2<e1. El algoritmo de ordenamiento por inserción tiene una complejidad cuadrática O(n2) para el peor de los casos, pero su rendimiento puede mejorar si el array que queremos ordenar está parcialmente ordenado. 9.5 Quicksort (ordenamiento rápido) Este es un algoritmo relativamente simple y extremadamente eficiente cuya lógica es recursiva y, según su implementación, puede llegar a requerir el uso de arrays auxiliares.

10 9.5.1 Implementación utilizando arrays auxiliares
Llamemos arr al array que queremos ordenar. La idea es la siguiente: tomamos cualquier elemento de arr. A este elemento lo llamaremos pivote. Luego recorremos arr para generar dos arrays auxiliares: el primero tendrá aquellos elementos de arr que resulten ser menores que pivote. El segundo tendrá los elementos de arr que sean mayores que pivote. A estos arrays auxiliares los llamaremos, respectivamente, menores y mayores. Ahora repetimos el procedimiento, primero sobre menores y luego sobre mayores. Finalmente obtenemos el array ordenado uniendo menores + pivote + mayores. Por ejemplo: arr = {4, 7, 2, 5, 1, 9, 3, 8} Si consideramos pivote = 4 (es decir, el primer elemento de arr), entonces: 4 7 2 5 1 9 3 8

11 9.5.2 Implementación sin arrays auxiliares
Otra implementación de quicksort puede ser la siguiente: luego de seleccionar el elemento pivote movemos todos los elementos menores a su izquierda y todos los elementos mayores a su derecha. Esto podemos lograrlo utilizando dos índices: i, inicialmente, apuntando al primer elemento del array y j apuntando al último.  La idea es recorrer el array desde la izquierda hasta encontrar el primer elemento mayor que pivote. Luego recorrer desde la derecha hasta encontrar el primer elemento menor que el pivote y permutarlos.  Por último, invocamos recursivamente dos veces al algoritmo, primero pasándole el subarray comprendido entre el inicio y la posición que ocupa el pivote (no inclusive) y luego pasándole el subarray formado por los elementos que se encuentran ubicados en posiciones posteriores a la del pivote. Por ejemplo, si pivote es 3, entonces: 5 9 4 3 8 1 2 6 Luego de este proceso todos los elementos menores que pivote quedarán ubicados a su izquierda mientras que todos los elementos mayores quedarán ubicados a su derecha. Notemos que pivote quedó ubicado en su lugar definitivo. El próximo paso será repetir el proceso sobre cada uno de estos subarrays.  La complejidad algorítmica de quicksort es, en el promedio de los casos: n log(n).

12 9.6 Heapsort (ordenamiento por montículos)
Heapsort es un algoritmo de ordenamiento no recursivo de orden O(n log(n)) que se basa en la propiedad de “montículo” (heap en inglés) de los árboles binarios semicompletos. 9.6.1 Árbol binario semicompleto Decimos que un árbol binario es semicompleto cuando todos sus niveles están completos a excepción del último cuyos nodos, de estar incompleto, deben ir apareciendo de izquierda a derecha. 6 4 8 15 5 10 7 3 2

13 9.6.2 Representar un árbol binario semicompleto en un array
Un árbol binario semicompleto puede ser representado linealmente si asignamos cada uno de sus valores, comenzando de izquierda a derecha y desde arriba hacia abajo, en posiciones consecutivas de un array. Es decir: en la posición 0 del array colocamos el valor de la raíz y en las posiciones 1 y 2 colocamos a su hijo izquierdo y a su hijo derecho. Diremos que la raíz es el nodo número 0 y sus hijos izquierdo y derecho son los nodos número 1 y 2 respectivamente. Esto nos permite calcular las posiciones que ocupan el hijo izquierdo y el hijo derecho de cualquier nodo i de la siguiente manera:  izquierdo(i) = 2*i+1 derecho(i) = 2*i+2  Así, en el array encontramos a la raíz del árbol (6) en la posición 0. Su hijo izquierdo (4) se encuentra en la posición 2*0+1=1 y su hijo derecho (8) en la posición 2*0+2=2. El hijo izquierdo de 4 lo encontramos en la posición 2*1+1=3 y el derecho en la posición 4. Todo esto siempre y cuando 2*i sea menor que n-1 siendo n la longitud del array. 6 4 8 15 5 10 7 3 2 1

14 9.6.3 Montículo (heap) Un montículo es un árbol binario semicompleto que tiene la característica de que el valor de cada nodo padre resulta ser mayor que el valor de cualquiera de sus hijos. En este caso diremos que se trata de un “montículo de máxima”. Análogamente, podemos hablar de un “montículo de mínima” cuando el valor de cada nodo padre es menor que los valores de sus hijos. En adelante, siempre hablaremos de montículos de máxima salvo que se especifique lo contrario. Por ejemplo, el siguiente árbol binario puede ser considerado montículo porque, además de ser semicompleto, cumple con que el valor de cada nodo padre es mayor al valor de cada uno de sus hijos. 15 10 8 6 5 4 7 3 2

15 9.6.4 Transformar un árbol binario semicompleto en un montículo
Para convertir un árbol binario semicompleto en montículo, alcanza con realizar algunas permutaciones como veremos en las siguientes figuras. Notemos que el orden en que haremos las permutaciones es de abajo hacia arriba y de derecha a izquierda. 6 4 8 15 5 10 3 2 7 6 4 8 15 5 10 3 2 7 Luego de las permutaciones 10 por 8, 15 por 4 y 15 por 6 el árbol quedó convertido en montículo ya que cada nodo padre tiene un valor mayor que el de sus nodos hijos. 6 15 10 4 5 8 3 2 7 15 6 10 4 5 8 3 2 7

16 9.6.5 Transformar un árbol binario semicompleto en un montículo
Definiremos las operaciones (funciones o métodos) izq y der que retornan los valores de los hijos izquierdo y derecho respectivamente del nodo ubicado en la i-ésima posición del array. public static int izq(int i) { return 2*i+1; } public static int der(int i) return izq(i)+1; Encontrará los ejemplos sobre este tema en la página 599 del libro.

17 9.6.6 El algoritmo de ordenamiento por montículos
Dado que en un montículo el valor del nodo raíz siempre es el mayor entonces, al representarlo en un array este valor siempre se ubicará en la posición 0. Luego el algoritmo de ordenamiento resulta, relativamente, simple ya que consiste en los siguientes pasos:  1 - Convertir el array en montículo. Luego de esto arr[0] será el elemento de mayor valor del array.  2 – Permutar arr[0] por arr[n-1]. Como arr[0] es el elemento de mayor valor del array, al llevarlo a arr[n-1] lo estaremos ubicando en su posición definitiva.  3 – Reconstruir el montículo excluyendo al último elemento ya que éste se encuentra ordenado. Luego repetir el proceso hasta que todos los elementos del array queden ordenados.  Recordemos que n es la longitud del array.

18 9.7 Shellsort (ordenamiento Shell)
Este método de ordenamiento de complejidad cuadrática para el peor caso surge de una generalización del método de ordenamiento por inserción. El algoritmo consiste en dividir al array en varios subarrays más pequeños formados por aquellos elementos del array original que se encuentran separados entre sí por una determinada “distancia de paso”. Este algoritmo no requiere del uso de memoria adicional. Para su implementación podemos pensar en una función nextElement que reciba la distancia de paso y la posición actual y retorne la posición del siguiente elemento del subarray para esa distancia de paso.

19 9.8 Binsort (ordenamiento por cajas)
Este algoritmo consiste en distribuir los elementos del array que queremos ordenar en diferentes “cajas”. Cada caja clasifica los elementos según una determinada propiedad o condición que, obviamente, debe ser mutuamente excluyente para asegurar que cada elemento del array ingrese en una única caja. 9.9 Radix sort (ordenamiento de raíz) Radix sort es diferente a todos los otros algoritmos ya que su estrategia de ordenamiento es netamente computacional. El algoritmo permite ordenar un conjunto de valores numéricos en función del valor ASCII de cada uno de sus dígitos.

20 9.9.1 Ordenar cadenas de caracteres con radix sort
Radix sort permite ordenar cadenas de caracteres ya que cada carácter tiene asignado un valor numérico definido en la tabla ASCII. En este caso debemos completar las cadenas más cortas con espacios en blanco a la derecha para que todas tengan la misma longitud.  Si vamos a comparar las cadenas “Juan” con “Alberto” debemos tener en cuenta lo siguiente: Correcto Incorrecto [Juan] [Juan ] [ Juan] [Alberto]


Descargar ppt "Algoritmos de ordenamiento"

Presentaciones similares


Anuncios Google