从经典trick开始,使用lazy list将单次操作的最坏复杂度降为 O(1)。
路径
- 经典方法 paired list:单次操作的均摊复杂度 $O(1)$,但最坏复杂度为 $O(n)$
- 使用lazy list替换list:单次操作的最坏复杂度为 $O(\log{n})$
- 使用incrementally pre-evaluating lazy list:单次操作的最坏复杂度为 $O(1)$
- 从queue拓展到deque
实现
Paired List
标准的paired list的实现方式是使用两个list(或者说stack)$\langle L,R\rangle$ 分别存储当前队列 $q$ 的左右子区间,其中 $L$ 是倒序的(栈顶为q.front
),$R$ 是正序的(栈顶为q.back
)
这个trick很经典,直接给出操作函数:
$$
\begin{aligned}
f(x)&=1 \
&=2 \
&=3 \
\end{aligned}
$$
具体解释为:
由于每个元素至多在 $L,R$ 插入/弹出各一次,因此单次操作的均摊时间复杂度为 $O(1)$。
但是这个做法有一个问题:当执行 $\text{remove}$ 且 $L$ 为空的情况时,单次操作需要 $O(|R|)$ 的时间(即翻转列表/栈中元素素全部弹出),这在部分应用场景下不可接受。
代码实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
| -- 使用元组来表示成对的列表
type PairedList a = ([a], [a])
-- 队列类型
newtype Queue a = Queue (PairedList a) deriving (Show)
-- 创建一个空队列
emptyQueue :: Queue a
emptyQueue = Queue ([], [])
-- 将元素添加到队列末尾
push :: a -> Queue a -> Queue a
push x (Queue (front, rear)) = Queue (front, x:rear)
front :: Queue a -> Maybe (a, Queue a)
front (Queue ([], [])) = Nothing
front (Queue (ll, rr)) =
case ll of
[] -> front (Queue (reverse rr, []))
xs -> Just (head xs, Queue (xs, rr))
-- 从队列前部取出元素,并返回新的队列
pop :: Queue a -> Maybe (Queue a)
pop (Queue ([], [])) = Nothing
pop (Queue (front, rear)) =
case front of
[] -> pop (Queue (reverse rear, []))
(x:xs) -> Just (Queue (xs, rear))
-- 检查队列是否为空
empty :: Queue a -> Bool
empty (Queue ([], [])) = True
empty _ = False
size :: Queue a -> Int
size (Queue (x, y)) = length x + length y
|
Paired Lazy List(Stream)
Lazy List的优势在于操作可以延迟,直到对应元素访问时才执行,且中间结果是记忆化的。
对于Lazy List:
$$
\begin{align*}
X\texttt{++} Y&=Y&&{|X|=0}\
&=\text{hd}\ X:(\text{tl}\ X\texttt{++}Y)&&{|X|>0}\
\text{rev}\ X &= \text{rev}’(X, []) \
&\phantom{\text{rev}\ X} \text{where } \text{rev}’(X, A) = A && {|X| = 0} \
&\phantom{\text{rev}\ X\text{where } \text{rev}’(X, A)} = \text{rev}’(\text{tl}\ X, \text{hd}\ X:A) && {|X| > 0} \
\text{take}(n, X) &= [] && {n = 0} \
&= \text{hd}\ X:\text{take}(n-1,\text{tl}\ X) && {n > 0} \
\text{drop}(n, X) &= X && {n = 0} \
&= \text{hd}\ X:\text{drop}(n-1,\text{tl}\ X) && {n > 0}
\end{align*}
$$
其中,$\texttt{++}$ 和 $\text{take}$ 是增量的(incremental),即其中部分操作可以延迟到需要取出对应位置的元素时再执行;而 $\text{rev}$ 和 $\text{drop}$ 不是增量的,需要一次性将所有操作全部执行完成。
为了避免执行 $\text{rev}\ R$ 操作时的复杂度过高,我们需要提前执行该操作(而不是等到 $L$ 为空时再执行,此时 $|R|$ 太大了)。
周期性对 $\langle L,R\rangle$ 执行 $\langle{L\texttt{++}\text{rev}\ R, []}\rangle$,将这一操作称为 $\text{rot}$。
定义 $\text{rot}(L,R,[])=L\texttt{++}\text{rev}\ R$,用 $A$ 存储当前已经翻转的列表元素,则:
$$
\begin{align*}
\text{rot}(L,R,A)&=\text{hd}\ R:A && {|L|=0}\
&=\text{hd}\ L:\text{rot}(\text{hd}\ L,\text{tl}\ R,\text{hd}\ R:A)&& {|L|>0}\
\end{align*}
$$
由于 $\texttt{++}$ 是增量的,因此只要选择一个合适的周期,即可降低每次执行 $\text{rot}$ 操作时的复杂度。显然,当 $|R|=|L|+1$ 时最优。
如此,执行执行 $\text{remove}$ 时的最坏时间复杂度为 $O(\log{n})$(因为每次。
总结一下,操作函数为:
$$
\begin{alignat*}{3}
&[]_q &&= \langle [],[]\rangle&\
&\langle L,R\rangle_q&&=|L|+|R|&\
&\text{insert}(e,\langle L,R\rangle)&&=\text{makeq}\langle L, e:R\rangle\
&\text{remove}\langle L,R\rangle&&=\langle\text{hd}\ L,\text{makeq}(\text{tl}\ L, R)\rangle\
& \text{makeq}\langle L,R\rangle&&=\langle L,R\rangle&&&{|R|\leq |L|}\
& &&=\langle\text{rot}(L,R,[]), []\rangle&&&{|R|=|L|+1}\
&\text{rot}(L,R,A)&&=\text{hd}\ R:A &&& {|L|=0}\
& &&=\text{hd}\ L:\text{rot}(\text{hd}\ L,\text{tl}\ R,\text{hd}\ R:A)&&& {|L|>0}\
\end{alignat*}
$$
代码实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
| data MyList a = MyList Int [a] deriving (Show)
-- 创建一个空列表
emptyList :: MyList a
emptyList = MyList 0 []
-- 添加元素到列表
append :: a -> MyList a -> MyList a
append x (MyList len xs) = MyList (len + 1) (x : xs)
-- 获取列表的长度
length' :: MyList a -> Int
length' (MyList len _) = len
-- 拼接两个 MyList
concatenate :: MyList a -> MyList a -> MyList a
concatenate (MyList len1 xs1) (MyList len2 xs2) =
MyList (len1 + len2) (xs1 ++ xs2)
-- 获取前 n 个元素
take' :: Int -> MyList a -> MyList a
take' n (MyList len xs) = MyList (min n len) (take n xs)
rev :: MyList a -> MyList a
rev x = rev' x emptyList
where
rev' (MyList _ []) acc = acc
rev' (MyList len (x:xs)) acc = rev' (MyList (len - 1) xs) (append x acc)
type PairedLazyList a = (MyList a, MyList a)
newtype Queue a = Queue (PairedLazyList a) deriving (Show)
emptyQueue :: Queue a
emptyQueue = Queue (emptyList, emptyList)
push :: a -> Queue a -> Queue a
push x (Queue (front, tail)) = makeq (Queue (front, append x tail))
front :: Queue a -> Maybe(a, Queue a)
front (Queue (MyList 0 _, _)) = Nothing
front (Queue (MyList len (x:xs), rear)) = Just (x, makeq (Queue (MyList len (x:xs), rear)))
pop :: Queue a -> Maybe(Queue a)
pop (Queue (MyList 0 _, _)) = Nothing
pop (Queue (MyList len (x:xs), rear)) = Just (makeq (Queue (MyList (len - 1) xs, rear)))
makeq :: Queue a -> Queue a
makeq (Queue (MyList lf xf, MyList lr xr))
| lf >= lr = Queue (MyList lf xf, MyList lr xr)
| otherwise = Queue (rot(MyList lf xf) (MyList lr xr) emptyList, emptyList)
rot :: MyList a -> MyList a -> MyList a -> MyList a
rot (MyList 0 _) r a = concatenate r a
rot (MyList ll (l:xl)) (MyList lr (r:xr)) a = append l (rot (MyList (ll-1) xl) (MyList (lr-1) xr) (append r a))
empty :: Queue a -> Bool
empty (Queue (MyList 0 _, _)) = True
empty _ = False
size :: Queue a -> Int
size (Queue (MyList ll _, MyList lr _)) = ll + lr
|
With pre-evaluation
为了保证单次操作的最坏复杂度为 $O(1)$,我们需要在 $O(1)$ 的时间内得到当前查询的队尾元素。
因此我们需要提前预处理出可能的队尾元素,在需要时直接取出。
现在使用 $\langle L,R,\hat{L}\rangle$ 来表示 $q$,其中 $L,R$ 的含义与此前相同,而 $\hat{L}$ 存储的是 $L$ 的部分队尾元素,标志着 $L$ 的已评估部分和未评估部分之间的边界。
当 $\hat{L}=[]$ 时,整个列表的元素都已计算,而每当进行 $\text{insert}$ 和 $\text{remove}$ 操作(未触发 $\text{rot}$ )时, 都会导致 $\hat{L}$ 移动一格,需要对下一个元素进行评估。而每次指定 $\text{rot}$ 操作后,将 $\hat{L}$ 置为 $L$。
我们需要保证每次执行 $\text{rot}$ 时,$\hat{L}=[]$。因此让 $|\hat{L}|=|L|-|R|$,当执行 $\text{rot}$ 时, $|L|=|R|$,此时正好 $|\hat{L}|=0$。
此时的操作函数为:
$$
\begin{alignat*}{3}
&[]_q &&= \langle [],[],[]\rangle&\
&\langle L,R,\hat{L}\rangle_q&&=|L|+|R|&\
&\text{insert}(e,\langle L,R,\hat{L}\rangle)&&=\text{makeq}\langle L, e:R,\hat{L}\rangle\
&\text{remove}\langle L,R,\hat{L}\rangle&&=\langle\text{hd}\ L,\text{makeq}(\text{tl}\ L, R,\hat{L})\rangle\
& \text{makeq}\langle L,R\rangle&&=\langle L,R,\text{tl}\ \hat{L}\rangle&&&{|\hat{L}|> 0}\
& &&=\text{let}\ L’=\text{rot}(L,R,[])\ \text{in}\ \langle L’,[],L’\rangle&&&{|\hat{L}|=0}\
&\text{rot}(L,R,A)&&=\text{hd}\ R:A &&& {|L|=0}\
& &&=\text{hd}\ L:\text{rot}(\text{hd}\ L,\text{tl}\ R,\text{hd}\ R:A)&&& {|L|>0}\
\end{alignat*}
$$
代码
需要特别注明的是,如果不需要查询队列的大小,则可以使用内置的List而非自定义MyList(因为不需要维护 $L,R,\hat{L}$ 的长度,只需要判断 $\hat{L}$ 是否为空)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
| import System.Exit (exitSuccess)
data MyList a = MyList Int [a] deriving (Show)
-- 创建一个空列表
emptyList :: MyList a
emptyList = MyList 0 []
-- 添加元素到列表
append :: a -> MyList a -> MyList a
append x (MyList len xs) = MyList (len + 1) (x : xs)
-- 获取列表的长度
length' :: MyList a -> Int
length' (MyList len _) = len
-- 拼接两个 MyList
concatenate :: MyList a -> MyList a -> MyList a
concatenate (MyList len1 xs1) (MyList len2 xs2) =
MyList (len1 + len2) (xs1 ++ xs2)
-- 获取前 n 个元素
take' :: Int -> MyList a -> MyList a
take' n (MyList len xs) = MyList (min n len) (take n xs)
rev :: MyList a -> MyList a
rev x = rev' x emptyList
where
rev' (MyList _ []) acc = acc
rev' (MyList len (x:xs)) acc = rev' (MyList (len - 1) xs) (append x acc)
type TripleLazyList a = (MyList a, MyList a, MyList a)
newtype Queue a = Queue (TripleLazyList a) deriving (Show)
emptyQueue :: Queue a
emptyQueue = Queue (emptyList, emptyList, emptyList)
push :: a -> Queue a -> Queue a
push x (Queue (front, tail, pp)) = makeq (Queue (front, append x tail, pp))
front :: Queue a -> Maybe(a, Queue a)
front (Queue (MyList 0 _, _, _)) = Nothing
front (Queue (MyList len xs, rear, pp)) = Just (head xs, Queue (MyList len xs, rear, pp))
pop :: Queue a -> Maybe(Queue a)
pop (Queue (MyList 0 _, _, _)) = Nothing
pop (Queue (MyList len (x:xs), rear, pp)) = Just (makeq (Queue (MyList (len - 1) xs, rear, pp)))
makeq :: Queue a -> Queue a
makeq (Queue (front, rear, MyList 0 _)) = Queue (newl, emptyList, newl) where newl = rot front rear emptyList
makeq (Queue (MyList lf front, rear, MyList lp (p:pp))) = Queue (MyList lf front, rear, MyList (lp - 1) pp)
rot :: MyList a -> MyList a -> MyList a -> MyList a
rot (MyList 0 _) r a = concatenate r a
rot (MyList ll (l:xl)) (MyList lr (r:xr)) a = append l (rot (MyList (ll-1) xl) (MyList (lr-1) xr) (append r a))
empty :: Queue a -> Bool
empty (Queue (MyList 0 _, _, _)) = True
empty _ = False
size :: Queue a -> Int
size (Queue (MyList ll _, MyList lr _, _)) = ll + lr
|
备注
三份代码的正确性通过 洛谷 B3616 【模板】队列 验证,没想到洛谷如今已经支持了Haskell语言的提交评测。
根据题目要求,代码中的交互部分如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
| import System.Exit (exitSuccess)
foldM' :: (Monad m) => (a -> b -> m a) -> a -> [b] -> m a
foldM' _ z [] = return z
foldM' f z (x:xs) = do
z' <- f z x
z' `seq` foldM' f z' xs
opt :: Queue Int -> IO(Queue Int)
opt q = do
input <- getLine
let integers = map read (words input) :: [Int]
case head integers of
1 -> return $ push (integers !! 1) q
2 -> case pop q of
Nothing -> do
putStrLn "ERR_CANNOT_POP"
return q
Just q2 -> return q2
3 -> case front q of
Just (x, q2) -> do
print x
return q2
Nothing -> do
putStrLn "ERR_CANNOT_QUERY"
return q
4 -> do
print (size q)
return q
main :: IO ()
main = do
n <- readLn :: IO Int
let initialQueue = (emptyQueue :: Queue Int)
finalQueue <- foldM' (\q _ -> opt q) initialQueue [1..n]
exitSuccess
|