在haskell快速排序整数

时间:2012-04-01 13:49:55

标签: sorting haskell int

haskell库中是否有任何函数在O(n)时间内对整数进行排序? [By,O(n)我的意思是比比较排序更快,特定于整数]

基本上我发现下面的代码花了很多时间进行排序(与没有排序的列表相加):

import System.Random
import Control.DeepSeq
import Data.List (sort)

genlist gen = id $!! sort $!! take (2^22) ((randoms gen)::[Int])

main = do
    gen <- newStdGen
    putStrLn $ show $ sum $ genlist gen

总结一个列表并不需要deepseq,但我正在尝试的是,但上面的代码足以满足我所寻求的指针。

时间:6秒(不排序);约35秒(有分类)

内存:大约80 MB(没有排序);大约310 MB(有排序)

注1:对于我来说,内存对于我来说是一个比时间更大的问题,因为手头的任务我出现了内存错误(内存使用量变为3GB!运行时间为30分钟后)

我假设更快的算法也会提供下注内存打印,因此寻找O(n)时间。

注2:我正在寻找Int64的快速算法,不过其他特定类型的快速算法也会有所帮助。


使用的解决方案:带有未装箱的向量的IntroSort足以完成我的任务:

import qualified Data.Vector.Unboxed as V
import qualified Data.Vector.Algorithms.Intro as I

sort :: [Int] -> [Int]
sort = V.toList . V.modify I.sort . V.fromList

3 个答案:

答案 0 :(得分:9)

我会考虑使用向量而不是列表,因为列表每个元素有很多开销,而未装箱的向量基本上只是一个连续的字节块。 vector-algorithms包中包含可用于此的各种排序算法,包括radix sort,我希望在您的情况下可以做得很好。

这是一个简单的例子,但如果您计划对其进行进一步处理,最好将结果保持为矢量形式。

import qualified Data.Vector.Unboxed as V
import qualified Data.Vector.Algorithms.Radix as R

sort :: [Int] -> [Int]
sort = V.toList . V.modify R.sort . V.fromList

另外,我怀疑你的例子的大部分运行时间来自随机数生成器,因为标准的运行时间并不完全以其性能而闻名。你应该确保你只对排序部分进行计时,如果你的程序中需要大量的随机数,那么Hackage上有更快的生成器。

答案 1 :(得分:4)

使用数组对数字进行排序的想法是减少内存使用量的正确方法。

但是,使用列表的最大值和最小值作为边界可能会导致超出内存使用量,甚至在maximum xs - minimum xs > (maxBound :: Int)时导致运行时失败。

所以我建议将列表内容写入一个未装箱的可变数组,在那里进行排序(例如使用quicksort),然后再从中构建一个列表。

import System.Random
import Control.DeepSeq
import Data.Array.Base (unsafeRead, unsafeWrite)
import Data.Array.ST
import Control.Monad.ST

myqsort :: STUArray s Int Int -> Int -> Int -> ST s ()
myqsort a lo hi
   | lo < hi   = do
       let lscan p h i
               | i < h = do
                   v <- unsafeRead a i
                   if p < v then return i else lscan p h (i+1)
               | otherwise = return i
           rscan p l i
               | l < i = do
                   v <- unsafeRead a i
                   if v < p then return i else rscan p l (i-1)
               | otherwise = return i
           swap i j = do
               v <- unsafeRead a i
               unsafeRead a j >>= unsafeWrite a i
               unsafeWrite a j v
           sloop p l h
               | l < h = do
                   l1 <- lscan p h l
                   h1 <- rscan p l1 h
                   if (l1 < h1) then (swap l1 h1 >> sloop p l1 h1) else return l1
               | otherwise = return l
       piv <- unsafeRead a hi
       i <- sloop piv lo hi
       swap i hi
       myqsort a lo (i-1)
       myqsort a (i+1) hi
   | otherwise = return ()


genlist gen = runST $ do
    arr <- newListArray (0,2^22-1) $ take (2^22) (randoms gen)
    myqsort arr 0 (2^22-1)
    let collect acc 0 = do
            v <- unsafeRead arr 0
            return (v:acc)
        collect acc i = do
            v <- unsafeRead arr i
            collect (v:acc) (i-1)
    collect [] (2^22-1)

main = do
    gen <- newStdGen
    putStrLn $ show $ sum $ genlist gen

速度相当快,占用内存较少。它仍然为列表使用了大量内存,2 22 Int采用32MB存储原始(64位Int s),列表开销为iirc five每个元素的单词,总计约200MB,但不到原始单词的一半。

答案 2 :(得分:2)

这是摘自理查德·伯德的书“功能算法设计珍珠”(虽然我不得不稍微编辑一下,因为书中的代码并没有完全按照书面编写)。

import Data.Array(Array,accumArray,assocs)  

sort :: [Int] -> [Int]
sort xs = concat [replicate k x | (x,k) <- assocs count]
        where count :: Array Int Int 
              count = accumArray (+) 0 range (zip xs (repeat 1))
              range = (0, maximum xs)

它的工作原理是创建一个由整数索引的数组,其中的值是列表中每个整数出现的次数。然后它创建一个索引列表,根据计数重复它们在原始列表中出现的次数相同。

您应该注意它与列表中的最大值是线性的,而不是列表的长度,因此像[ 2^x | x <- [0..n] ]这样的列表不会线性排序。