在Data.List模块中,使用以下数据结构
{- We store a front list, a rear list, and the length of the queue. Because we
only snoc onto the queue and never uncons, we know it's time to rotate when the
length of the queue plus 1 is a power of 2. Note that we rely on the value of
the length field only for performance. In the unlikely event of overflow, the
performance will suffer but the semantics will remain correct. -}
data SnocBuilder a = SnocBuilder {-# UNPACK #-} !Word [a] [a]
{- Smart constructor that rotates the builder when lp is one minus a power of
2. Does not rotate very small builders because doing so is not worth the
trouble. The lp < 255 test goes first because the power-of-2 test gives awful
branch prediction for very small n (there are 5 powers of 2 between 1 and
16). Putting the well-predicted lp < 255 test first avoids branching on the
power-of-2 test until powers of 2 have become sufficiently rare to be predicted
well. -}
{-# INLINE sb #-}
sb :: Word -> [a] -> [a] -> SnocBuilder a
sb lp f r
| lp < 255 || (lp .&. (lp + 1)) /= 0 = SnocBuilder lp f r
| otherwise = SnocBuilder lp (f ++ reverse r) []
-- The empty builder
emptySB :: SnocBuilder a
emptySB = SnocBuilder 0 [] []
-- Add an element to the end of a queue.
snocSB :: SnocBuilder a -> a -> SnocBuilder a
snocSB (SnocBuilder lp f r) x = sb (lp + 1) f (x:r)
-- Convert a builder to a list
toListSB :: SnocBuilder a -> [a]
toListSB (SnocBuilder _ f r) = f ++ reverse r
摘录上方的评论如下:
队列保证(摊销)O(1)
snoc
和O(1)uncons
, 这意味着我们可以将toListSB
视为O(1)转换为a 列表式结构比常规列表慢的常数因素 - 我们支付 当我们使用列表时,O(n)成本会逐渐增加。
我不明白为什么toListSB
在O(1)摊销中工作。在2的连续幂之间,右列表的长度不是越来越大吗?
答案 0 :(得分:1)
如果列表的当前长度为N=2^M
,则有M次加倍操作。第一次加倍需要1个时间单位,第二个加倍需要2个时间单位,第三个加倍需要1个时间单位,依此类推。但众所周知(几何级数和公式)
1 + 2 + 4 + 8 + ...+2^M = 2^(M+1) - 1 = O(N)
因此,每次操作的摊销成本为O(N)/N = O(1)