[英]Complexity Analysis in Haskell
positions2 :: (Eq a) => a -> [a] -> [Int]
positions2 = f where
f aa aas = filter (g aa aas) [0 .. (length aas - 1)]
g aa aas it = aa == aas !! it
此代碼用於查找給定列表中給定元素的位置。
為了找出它的復雜性,我想過filter
采用的函數是g
和列表[0..length-1]
。
現在,我無法弄清楚positions2
復雜性是什么(n)
還是由於filter
功能會有任何循環。
請建議是否有任何其他方法來編寫更緊湊的代碼,以降低復雜性。
(!!)
具有O(n)復雜度。 由於嵌套(!!),您的天真實現是二次的
列表在這里是懶惰的,因此過濾器和列表生成器具有組合的O(n)復雜度加上每個元素的一些常量(具有惰性,列表的生成和過濾在一次傳遞中有效地發生)。
即生成和過濾的工作是在惰性列表上的(O(n + n * k)) ,而不是嚴格列表中的O(2 * n) ,其中k是生成單個cons單元的成本。
但是,無論如何,使用線性索引會使這個二次方成為正方形。 我注意到,在具有融合的嚴格向量上,由於常數j查找復雜度,或者使用日志結構化查找O(n + n * log n) ,這將是O(n + n * j) (線性) 。
線性復雜性
positions2 :: Eq a => a -> [a] -> [Int]
positions2 x = map fst . filter ((x==).snd) . zip [0..]
main = print $ positions2 3 [1,2,3,4,1,3,4]
(我建議你閱讀並理解究竟是如何工作的)
(你的代碼需要二次時間,因為(!!)
需要線性時間)
首先,幾乎沒有變化:
positions2 :: (Eq a) => a -> [a] -> [Int]
positions2 = f where
f aa aas = filter (g aa aas) [0 .. (length aas - 1)]
g aa aas it = aa == aas !! it
-- ==
positions2 aa aas = filter (g aa aas) [0 .. (length aas - 1)]
g aa aas it = aa == aas !! it
-- ==
positions2 aa aas = [it | it <- [0 .. (length aas - 1)], aa == (aas !! it)]
{-
positions2 aa aas = for each it in [0 .. (length aas - 1)]
if (aas !! it) == aa
emit it
-}
現在,你可以清楚地看到,對於給定it
的列表的遍歷aas
被重新反復 ,從一開始,由(!!)
這是一個經典的二次行為,當完全探索positions2 aa aas
的結果時,你執行1 + 2 + 3 + 4 + ... + n = O(n 2 )步驟(返回的列表遍歷到它最后)。
但是你探索逐步增加指標,所以你可以和沿列表中的前點開始(而不是從自身做起 ),並通過每次1個位置提前(而不是全it
指數,如(!!)
是這樣做):
positions2 aa aas = g 0 aas
where
g i (a:as) | a == aa = i : g (i+1) as
| otherwise = g (i+1) as
g _ [] = []
在這里你可以看到如何將索引的遞增1( i --> i+1
)與列表中的推進一起編織一個位置( (a:as) --> as
)。
再次使用列表推導,使用zip
實現編織(或實際上更像是拉鏈):
positions2 aa aas = [it | (a,it) <- zip aas [0 .. (length aas - 1)]
, a == aa]
{-
= for each a in aas while incrementing it by 1 from 0 to (length aas - 1),
if a == aa
emit it
-}
現在,這顯然是一種O(n)解決方案。 而且因為Haskell的列表是懶惰的,並且當最短列表結束時zip
停止,
positions2 aa aas = [it | (a,it) <- zip aas [0 ..], a == aa]
(這相當於此答案中的代碼)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.