Эффективные кучи в чисто функциональных языках
в качестве упражнения в Haskell я пытаюсь реализовать heapsort. Куча обычно реализуется как массив на императивных языках, но это было бы крайне неэффективно в чисто функциональных языках. Поэтому я посмотрел на бинарные кучи, но все, что я нашел до сих пор, описывает их с императивной точки зрения, и представленные алгоритмы трудно перевести в функциональную настройку. Как эффективно реализовать кучу на чисто функциональном языке, таком как Хаскелл?
Edit: под эффективным я имею в виду, что он все еще должен быть в O(N*log n), но он не должен бить программу C. Кроме того, я хотел бы использовать чисто функциональное программирование. Какой еще смысл делать это в Хаскелле?
9 ответов
существует ряд реализаций кучи Haskell в приложении к Okasaki Чисто Функциональные Структуры Данных. (Исходный код можно загрузить по ссылке. Саму книгу стоит прочитать.) Ни один из них не является бинарными кучами, как таковыми, но "левак" кучи очень похож. Он имеет операции вставки, удаления и слияния O(log n). Существуют также более сложные структуры данных, такие как косое кучи, бином кучи и splay кучи, которые имеют более высокую производительность.
Джон Фэрбэрн опубликовал функциональный heapsort в список рассылки Haskell Cafe еще в 1997 году:
http://www.mail-archive.com/haskell@haskell.org/msg01788.html
я воспроизвожу его ниже, переформатированный в соответствии с этим пространством. Я также немного упростил код merge_heap.
Я удивлен, что treefold не находится в стандартной прелюдии, так как это так полезно. В переводе с версию я писал в вдумайтесь в октябре 1992 -- Джон Fairbairn
module Treefold where
-- treefold (*) z [a,b,c,d,e,f] = (((a*b)*(c*d))*(e*f))
treefold f zero [] = zero
treefold f zero [x] = x
treefold f zero (a:b:l) = treefold f zero (f a b : pairfold l)
where
pairfold (x:y:rest) = f x y : pairfold rest
pairfold l = l -- here l will have fewer than 2 elements
module Heapsort where
import Treefold
data Heap a = Nil | Node a [Heap a]
heapify x = Node x []
heapsort :: Ord a => [a] -> [a]
heapsort = flatten_heap . merge_heaps . map heapify
where
merge_heaps :: Ord a => [Heap a] -> Heap a
merge_heaps = treefold merge_heap Nil
flatten_heap Nil = []
flatten_heap (Node x heaps) = x:flatten_heap (merge_heaps heaps)
merge_heap heap Nil = heap
merge_heap node_a@(Node a heaps_a) node_b@(Node b heaps_b)
| a < b = Node a (node_b: heaps_a)
| otherwise = Node b (node_a: heaps_b)
вы также можете использовать ST
монада, которая позволяет писать императивный код, но безопасно предоставлять чисто функциональный интерфейс.
в качестве упражнения в Haskell я реализовал императивный heapsort с ST монадой.
{-# LANGUAGE ScopedTypeVariables #-}
import Control.Monad (forM, forM_)
import Control.Monad.ST (ST, runST)
import Data.Array.MArray (newListArray, readArray, writeArray)
import Data.Array.ST (STArray)
import Data.STRef (newSTRef, readSTRef, writeSTRef)
heapSort :: forall a. Ord a => [a] -> [a]
heapSort list = runST $ do
let n = length list
heap <- newListArray (1, n) list :: ST s (STArray s Int a)
heapSizeRef <- newSTRef n
let
heapifyDown pos = do
val <- readArray heap pos
heapSize <- readSTRef heapSizeRef
let children = filter (<= heapSize) [pos*2, pos*2+1]
childrenVals <- forM children $ \i -> do
childVal <- readArray heap i
return (childVal, i)
let (minChildVal, minChildIdx) = minimum childrenVals
if null children || val < minChildVal
then return ()
else do
writeArray heap pos minChildVal
writeArray heap minChildIdx val
heapifyDown minChildIdx
lastParent = n `div` 2
forM_ [lastParent,lastParent-1..1] heapifyDown
forM [n,n-1..1] $ \i -> do
top <- readArray heap 1
val <- readArray heap i
writeArray heap 1 val
writeSTRef heapSizeRef (i-1)
heapifyDown 1
return top
кстати, я оспариваю, что если это не чисто функционально, то нет смысла делать это в Haskell. Я думаю, что моя реализация игрушек намного лучше, чем то, что можно было бы достичь в C++ с шаблонами, передавая материал внутренним функциям.
и вот куча Фибоначчи в Haskell:
https://github.com/liuxinyu95/AlgoXY/blob/algoxy/datastruct/heap/other-heaps/src/FibonacciHeap.hs
вот pdf-файл для некоторых других куч k-ary, основанных на работе Окасаки.
Как и в эффективных алгоритмах Quicksort, написанных в Haskell, вам нужно использовать монады (трансформаторы состояния), чтобы делать вещи на месте.
Я попытался перенести стандартную двоичную кучу в функциональные настройки. Есть статья с описанной идеей:функциональный подход к стандартным двоичным кучам. Все исходные коды в статье находятся в Scala. Но он может быть легко перенесен на любой другой функциональный язык.
массивы в Haskell не так уж неэффективны, как вы могли бы подумать, но типичной практикой в Haskell, вероятно, было бы реализовать это с использованием обычных типов данных, например:
data Heap a = Empty | Heap a (Heap a) (Heap a)
fromList :: Ord a => [a] -> Heap a
toSortedList :: Ord a => Heap a -> [a]
heapSort = toSortedList . fromList
если бы я решал эту проблему, я мог бы начать с заполнения элементов списка в массив, облегчая их индексирование для создания кучи.
import Data.Array
fromList xs = heapify 0 where
size = length xs
elems = listArray (0, size - 1) xs :: Array Int a
heapify n = ...
Если вы используете двоичную максимальную кучу, вы можете отслеживать размер кучи при удалении элементов, чтобы вы может найти нижний правый элемент в O (log N) времени. Вы также можете взглянуть на другие типы куч, которые обычно не реализуются с помощью массивов, таких как биномиальные кучи и кучи Фибоначчи.
последнее замечание о производительности массива: в Haskell есть компромисс между использованием статических массивов и использованием изменяемых массивов. В статических массивах при изменении элементов необходимо создавать новые копии массивов. С изменяемыми массивами, сборщик мусора имеет трудное время держать различные поколения объектов разделены. Попробуйте реализовать heapsort с помощью STArray и посмотреть, как вам это нравится.
вот страница, содержащая ML-версию HeapSort. Это довольно подробно и должно обеспечить хорошую отправную точку.
http://flint.cs.yale.edu/cs428/coq/doc/Reference-Manual021.html