Haskell deque的以下功能实现如何支持摊销固定时间内的提取?

Haskell deque的以下功能实现如何支持摊销固定时间内的提取?,haskell,data-structures,functional-programming,deque,amortized-analysis,Haskell,Data Structures,Functional Programming,Deque,Amortized Analysis,在模块中,使用以下数据结构 {- 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 o

在模块中,使用以下数据结构

{- 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
{-我们存储前列表、后列表和队列长度
只有snoc进入队列并且从不取消限制,我们知道当
队列长度加1是2的幂。请注意,我们依赖于
仅用于性能的长度字段。在不太可能发生溢出的情况下
性能将受到影响,但语义将保持正确。-}
数据SnocBuilder a=SnocBuilder{-#UNPACK}!字[a][a]
{-智能构造函数,当lp为1减去
2.不旋转非常小的建筑工人,因为这样做不值得
麻烦。lp<255测试首先进行,因为2次幂测试给出了可怕的结果
非常小的n的分支预测(1和2之间有5次幂
16) .首先进行预测良好的lp<255测试可避免在
2次幂检验,直到2次幂变得足够罕见,无法预测
嗯
{-#内联某人{-}
sb::Word->[a]->[a]->Snobuilder a
sb lp f r
|lp<255 | |(lp.&(lp+1))/=0=SnocBuilder lp f r
|否则=SnocBuilder lp(f++反向r)[]
--空建筑商
emptySB::Snobuilder a
emptySB=SnocBuilder 0[]
--将元素添加到队列的末尾。
snocSB::SnocBuilder a->a->SnocBuilder a
snocSB(SnocBuilder lp f r)x=sb(lp+1)f(x:r)
--将生成器转换为列表
toListSB::Snobuilder a->[a]
toListSB(SnocBuilder f r)=f++反向r
片段上面提到的注释如下所示:

队列保证(摊销)O(1)
snoc
和O(1)
uncon
, 这意味着我们可以把
toListSB
看作是一个O(1)到a的转换 类似列表的结构一个常数因子比正常列表慢——我们付费 当我们使用列表时,O(n)的成本是递增的


我不明白为什么
toListSB
在O(1)摊销中工作。右列表的长度不是在连续的2次幂之间越来越大吗?

如果列表的当前长度为
N=2^M
,则有M次加倍操作。第一次加倍需要1个时间单位,第二次加倍需要0 2个时间单位,第三次加倍需要-4个时间单位,依此类推。但众所周知(几何级数和公式)是


因此,每一次操作的摊销成本是
O(N)/N=O(1)

如果它越来越大,这意味着逆转(每次都要付出代价)变得越来越少了。@Bergi但是,每次我想
toListSB
是的,
toListSB
不是
O(1)
,我都必须逆转正确的列表,它是
O(n)
。但是调用
snocSB
n次仅仅是
O(n)
而不是
O(n²)
@Bergi,但是注释指出,我们可以将toListSB看作是O(1)转换。他们错了吗?啊,我没注意。是的,在哈斯凯尔,一切都是
O(0)
直到被消费:-)转换和消费列表需要
O(n)
一起。这似乎是对
snocSB
的分析,我在寻找
toListSB
toListSB
的时间复杂性肯定是O(n)。您引用的评论解释说,n
snocSB
操作之后的
toListSB
作为一个整体需要O(n)个工作。奇怪的是,评论中提到了一个无约束的操作,但是没有。
1 + 2 + 4 + 8 + ...+2^M = 2^(M+1) - 1 = O(N)