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
答案 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] ]
这样的列表不会线性排序。