Массивы в 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 и посмотрите, как вам это понравится.