Es mucho más claro de leer el código en Haskell que en Java. Esto lo veremos también con los siguientes algoritmos.
Los Arrays otorgan muchísima ventaja a los algoritmos que acceden mucho a posiciones concretas.
Las listas en Haskell son mas eficientes que en Java.
En general comparar los 2 lenguajes como si fuera una competición no tiene mucho sentido, cada cual es mejor en lo suyo, como veremos en la segunda parte.
13 Bubble Sort: Conclusiones
FIN DE LA PRIMERA PARTE 14
Tiene complejidad O(n2). Funciona de la siguiente manera: Al principio se tiene un elemento, que se considera un conjunto ordenado. Después , al haber k elementos ordenados de menor a mayor se coge el elemento k+1 y se va comparando con los elementos ya ordenados deteniéndose hasta que se encuentra un elemento mayor. Ahí se inserta el elemento que hemos sacado desplazando el resto a la derecha.
15 InsertionSort
int firstOutOfOrder, location, temp; for(firstOutOfOrder = 1; firstOutOfOrder < list.length; firstOutOfOrder++) { //Starts at second term, goes until the end of the array. if(list[firstOutOfOrder] < list[firstOutOfOrder – 1]) { //If the two are out of order, we move the element to its rightful place. temp = list[firstOutOfOrder]; location = firstOutOfOrder;
do { //Keep moving down the array until we find exactly where it's supposed to go. list[location] = list[location-1]; location–; } while (location > 0 && list[location-1] > temp);
list[location] = temp; } } 16 Insertion sort: implementación en Java
insertion_sort :: (a -> a -> Bool) -> [a] -> [a] insertion_sort pred [] = [] insertion_sort pred (x:xs) = insert pred x (insertion_sort pred xs)
insert :: (a -> a -> Bool) -> a -> [a] -> [a]
insert pred x [] = [x]
insert pred x (y:ys) | pred x y = (x:y:ys) | otherwise = y:(insert pred x ys)
insertSort x= insertion_sort (< =) x 17 Insertion Sort: Implementación en Haskell
10000 números Haskell (interpretado): 15,28 s Haskell (compilado): 0,8248 s Java (Linked Lists): 316,6 ms Java (Arrays): 5,3 ms 18 Insertion sort: Resultados
Es de complejidad O(n2). Funciona de la siguiente manera: Se busca el mínimo entre la posición i y el resto de la lista. Se intercambia el mínimo con la posición i. 19 Selection sort
for (int i = 0; i < n – 1; i++) { //Buscamos el mínimo //supondremos que es el primero int posMin = i; //nos movemos por el resto for (int j = i+1; j < n; j++) { //si este es menor aun if (A[j] < A[posMin]) { //tomamos nota de su posición posMin = j; } } //intercambiar la posición i y el //mínimo encontrado int iaux = A[i]; A[i] = A[posMin]; A[posMin] = iaux; } 20 Selection sort:implementación en Java
min1:: (a->a->Bool)->[a]->a min1 (< =) [] = undefined min1 (< =) [x] = x min1 (< =) (x:xs) | x < = (min1 (< =) xs) = x | otherwise = min1 (< =) xs
delete:: (Eq a) => a->[a]->[a] delete a [] = [] delete a (x:xs) | a==x = xs | otherwise = x:(delete a xs)
ssort:: (Eq a) => (a->a->Bool)->[a]->[a] ssort (< =) [] = [] ssort (< =) xs = [x] ++ ssort (< =) (delete x xs) where x = min1 (< =) xs
ssortlc:: (Eq a) => (a->a->Bool)->[a]->[a] ssortlc (< =) [] = [] ssortlc (< =) xs = (x:(ssortlc (< =) (delete x xs))) where x = min1 (< =) xs selecsortlc (x)= ssortlc (< =) x 21 Selection sort: Implementación en Haskell
Se basa en la técnica del divide y vencerás. Funciona de la siguiente manera: Elegimos un elemento de la lista a ordenar. Lo llamaremos pivote. Mover los elementos a cada lado del pivote, de tal forma que los menores de éste queden a la izquierda y los mayores a la derecha. Ahora el pivote queda en la posición que le corresponde. Dividimos en 2 sublistas: los menores que el pivote y los mayores que el pivote.
22 Quicksort
Aplicamos Quicksort de nuevo a las 2 sublistas. Este proceso se repite hasta que la subdivisión quede en un solo elemento. Al final, por el propio proceso recursivo tendremos el algoritmo ordenado.
23 Quicksort
Visto el funcionamiento general, podemos intentar hacer un análisis de complejidad del algoritmo. En el mejor caso el pivote queda en el centro de la lista, dividiendo en 2 sublistas exactamente iguales. Su complejidad es O(nlogn). En el peor caso el pivote se queda en un extremo de la lista, lo que aumenta su complejidad a O(n2). Como caso intermedio tenemos O(nlogn) 24 Quicksort
Primero presentaremos el procedimiento ordenador principal: static void Quicksort(int arr[], int p, int r){ if(p < r) { int q = Particion(arr, p, r); Quicksort(arr, p, q – 1); Quicksort(arr, q + 1, r); } } 25 Quicksort: implementación en Java
Veamos el procedimiento auxiliar Particion: private static int Particion(int[] arr, int p, int r) { int x = arr[r]; int i = p – 1, t; for(int j = p; j < r; j++) { if(arr[j] < = x) { i++; t = arr[i]; arr[i] = arr[j]; arr[j] = t; } } t = arr[i + 1]; arr[i + 1] = arr[r]; arr[r] = t; return i + 1; } 26 Quicksort: implementación en Java
qsort [] = [] qsort (x:xs) = qsort [y | y < – xs, y < x] ++ [x] ++ qsort [y | y < – xs, y >= x] 27 Quicksort: implementación en Haskell
200000 números. Haskell (interpretado): 20,2293 s Haskell (compilado): 0,84284 s Java (arrays): 24,6 ms Java (linked lists):520,9 ms 28 Quicksort: resultados
Al igual que el anterior se basa en la técnica de divide y vencerás. Tiene una complejidad de O(nlogn). Funciona de la siguiente manera: Si la lista es de 0 o 1 elemento está ordenada. Dividir la lista desordenada en 2 mitades aproximadamente del mismo tamaño. Ordenar recursivamiente las 2 sublistas aplicando mergesort. Mezclar las 2 sublistas en una lista ordenada.
29 Mergesort
Se basa en 2 fundamentos: Una lista pequeña se ordena más rápidamente que una grande. Es más fácil unir 2 listas ordenadas que 2 listas desordenadas. Por todo ello hemos dicho que es un algoritmo de orden O(n*logn).
30 Mergesort
public static int[] mergeSortImpl(int[] array) { // Caso base. Un arreglo de cero o un elemento ya esta ordenado, // asi que lo regresamos. if (array.length < = 1) { return array; } int puntoMedio = array.length / 2; // Creamos subarreglo izquierdo int[] izquierdo = new int[puntoMedio]; System.arraycopy(array, 0, izquierdo, 0, puntoMedio); // Creamos el subarreglo derecho int[] derecho = new int[array.length – puntoMedio]; for (int i = 0; i < array.length – puntoMedio; i++) { derecho[i] = array[puntoMedio + i]; } 31 Mergesort: Implementación en Java
// Ordenamos las dos mitades recursivamente int[] izquierdoOrdenado = mergeSortImpl(izquierdo); int[] derechoOrdenado = mergeSortImpl(derecho); //Mezclamos la solucion— // El indice i es para recorrer el subarreglo izquierdo int i = 0; // El indice j es para recorrer el subarreglo derecho int j = 0; // En 'resultado' guardamos el resultado de la mezcla de los dos // subarreglos int[] resultado = new int[izquierdoOrdenado.length + derechoOrdenado.length]; /** * Terminamos de mezclar cuando i + j ya recorrieron todos los elementos * de los dos subarreglos */ while (i + j < izquierdoOrdenado.length + derechoOrdenado.length) { 32 Mergesort: implementación en Java
if (i == izquierdoOrdenado.length) { resultado[i + j] = derechoOrdenado[j]; j++; continue; } int elementoIzquierdo = izquierdoOrdenado[i]; int elementoDerecho = derechoOrdenado[j]; 33 Mergesort: implementación en Java
if (elementoIzquierdo < = elementoDerecho) { resultado[i + j] = elementoIzquierdo; i++; } else { resultado[i + j] = elementoDerecho; j++; } } return resultado; } 34 Mergesort: implementación en Java
mergeSort [] = [] mergeSort [x] = [x] mergeSort xs = let (as,bs) = splitNow xs in merge (mergeSort as) (mergeSort bs)
merge [] ys = ys merge xs [] = xs merge xs@(x:xs') ys@(y:ys') | x < = y = x : merge xs' ys | otherwise = y : merge xs ys' 35 Mergesort: Implementación en Haskell
500000 números. Haskell (interpretado): 10,468 s Haskell (compilado): 1,202 s Java (linked lists): "java.lang.OutOfMemoryError" tras ocupar 2.151.848KB de RAM La memoria se desborda por las llamadas recursivas. 36 Mergesort: resultados
Se ordenan los elementos usando un árbol binario de búsqueda. Se basa en introducir los elementos poco a poco en el árbol, quedando cada uno de estos elementos ordenados. Después se sacan los elementos en inorden. Así queda ordenada. Su complejidad es de O(n2). 37 Tree Sort
data Tree a = Leaf | Node (Tree a) a (Tree a)
insert :: Ord a => a -> Tree a -> Tree a insert x Leaf = Node Leaf x Leaf insert x (Node t y t') | x < = y = Node (insert x t) y t' insert x (Node t y t') | x > y = Node t y (insert x t')
flatten :: Tree a -> [a]flatten Leaf = [] flatten (Node t x t') = flatten t ++ [x] ++ flatten t' 38 Tree Sort (Haskell)
treesort :: Ord a => [a] -> [a] treesort = flatten . foldr insert Leaf
El tiempo que tarda en ordenar 200000 números es de 2,25s. 39 Tree Sort (Haskell)
Quicksort no es más que una versión optimizada del Tree Sort. En vez de ir insertando secuencialmente elementos en un árbol, Quicksort "organiza" este árbol a través de sus llamadas recursivas. Sin embargo, el número de comparaciones que se realizan en ambos casos es la misma. Por ello, su complejidad temporal es la misma O(n*logn). Su complejidad espacial es lo que diferencia a ambos algoritmos ya que uno tiene que ir almacenando un árbol, mientras que el otro no es necesario por la propia estructura de las llamadas recursivas. 40 De Tree Sort a Quicksort
Hemos investigado que Haskell posee una librería que implementa los arrays, sería bastante interesante realizar otra comparativa usando arrays, si bien es cierto que queda fuera de nuestro objeto de estudio. Hay otros algoritmos que si bien no son iguales de eficaces que estos también sería interesante desarrollar su ineficiencia para ver como los lenguajes palian estas: stupid sort, bogosort. Sort de haskell es mucho más eficiente que el resto de algoritmos ordenando 200000 elementos en 0,32 s de forma interpretada. 41 Posibles ampliaciones
"Razonando con Haskell"-Blas C. Ruíz, José Gallardo, Pablo Guerrero es.wikipedia.org (para buscar la definición de algoritmos de ordenación) http://en.wikipedia.org/wiki/Tree_sort (para el cambio de Treesort a Quicksort) , última revisión el 21 de Febrero de 2012 rosettacode.org codecodex.com en.literateprograms.org API de Haskell 2011 http://www.haskell.org/hoogle/ Otras webs de consulta de algoritmos de ordenación implementados en Java 42 Bibliografía:
Página anterior | Volver al principio del trabajo | Página siguiente |