【问题标题】:Haskell: FIFO queue algorithm complexityHaskell:FIFO队列算法复杂度
【发布时间】:2012-07-14 15:50:40
【问题描述】:

这是我对 FIFO 队列的尝试:

type Queue a = [a] -> [a]

empty :: Queue a
empty = id

remove :: Int -> Queue a -> ([a], Queue a)
remove n queue = (take n (queue []), (\x -> drop n (queue x)));

add :: [a] -> Queue a -> Queue a
add elems queue = (\x -> queue (elems ++ x))

empty 创建一个空队列,remove 获取队列的第一个 n 元素并将队列的其余部分作为元组的第二个元素返回,add 将列表 elems 添加到队列。

这会在O(1) time 中添加/删除 1 个元素,在 O(n) time 中添加/删除 n 个元素吗?

【问题讨论】:

  • 也许是我,但我不明白你的代码是如何工作的。你能添加类型签名吗?队列的类型是什么?
  • 为什么? (++) 不是 O(1) 操作,它是 O(n),尽管有惰性。
  • @efie:我添加了类型签名。
  • 不幸的是,remove 可能需要 O(N) 时间。如果使用 N 个 add 操作创建队列,remove 将需要评估 N 个 ++ 操作以找到第一个元素。
  • remove 不仅在差异列表上无效,而且也不是特别符合 Haskell 习惯,而且通常比更快的替代方案更麻烦:在一个地方完全建立 FIFO在您的程序(“源”)中,只对它应用一次toList(即$ [])并将结果作为一个整体传递给“消费者”函数。由于懒惰,这不会导致您在开始从另一端开始阅读之前完成构建过程。读数将自动“等待”源产生每个元素。 FIFO 的理想行为,适用于许多问题!

标签: algorithm haskell


【解决方案1】:

您有效实施的内容相当于difference lists。 (参见:dlist。)

差异列表允许廉价的附加,但不幸的是,您的删除将需要线性时间。如果我们稍微重写您的代码,它会变得更加清晰:

type Queue a = [a] -> [a]

empty :: Queue a
empty = id

toList :: Queue a -> [a]
toList q = q []

fromList :: [a] -> Queue a
fromList = (++)

remove :: Int -> Queue a -> ([a], Queue a)
remove n q = (xs, fromList ys)
  where
    (xs, ys) = splitAt n (toList q)

add :: [a] -> Queue a -> Queue a
add xs q = (++ xs) . q

请注意,我在列表之间的转换比在您的代码中更明确。您清楚地看到删除代码的核心位于toListfromList 之间。

【讨论】:

  • 线性时间是什么意思?你的意思是时间与队列的长度成正比吗?因为使用您的代码,我可以在有限的时间内从无限长的队列中删除。
  • 这个add 函数不是将元素添加到队列的开头而不是结尾吗?
  • @is7s 啊,你是对的。 OP的代码也是如此,不是吗。我会修改代码。 :)
  • @Clinton 从差异列表到列表的时间与元素数量呈线性关系,不是吗?
  • @dblhelix:是的,不过我不确定这有什么关系。
【解决方案2】:

好吧,稍微回避您的问题,FIFO 队列的经典纯功能实现是一对列表,一个用于“前”,一个用于“后”。您通过将元素添加为后列表的头部来使元素入队,并通过获取前列表的头部来出列;如果前面的列表为空,则通过反转后面的列表并将其与空的前面的列表交换来“旋转”队列。在代码中:

import Control.Monad
import Data.List
import Data.Maybe

data FIFO a = FIFO [a] [a]
              deriving Show

empty :: FIFO a
empty = FIFO [] []

isEmpty :: FIFO a -> Bool
isEmpty (FIFO [] []) = True
isEmpty _ = False

enqueue :: a -> FIFO a -> FIFO a
enqueue x (FIFO front back) = FIFO front (x:back)

-- | Remove the head off the queue.  My type's different from yours
-- because I use Maybe to handle the case where somebody tries to
-- dequeue off an empty FIFO.
dequeue :: FIFO a -> Maybe (a, FIFO a)
dequeue queue = case queue of
                  FIFO [] [] -> Nothing
                  FIFO (x:f) b -> Just (x, FIFO f b)
                  otherwise -> dequeue (rotate queue)
    where rotate (FIFO [] back) = FIFO (reverse back) []


-- | Elements exit the queue in the order they appear in the list.
fromList :: [a] -> FIFO a
fromList xs = FIFO xs []

-- | Elements appear in the result list in the order they exit the queue.
toList :: FIFO a -> [a]
toList = unfoldr dequeue

这是经典的实现。现在你的操作可以这样写:

-- | Enqueue multiple elements.  Elements exit the queue in the order
-- they appear in xs.
add :: [a] -> FIFO a -> FIFO a
add xs q = foldl' (flip enqueue) q xs

要根据dequeue 编写remove,您需要处理来自dequeue(a, FIFO a) 结果中的所有这些中间FIFO。一种方法是使用State monad:

import Control.Monad.State

-- | Remove n elements from the queue.  My result type is different
-- from yours, again, because I handle the empty FIFO case.  If you
-- try to remove too many elements, you get a bunch of Nothings at
-- the end of your list.
remove :: Int -> FIFO a -> ([Maybe a], FIFO a)
remove n q = runState (removeM n) q

-- | State monad action to dequeue n elements from the state queue.
removeM :: Int -> State (FIFO a) [Maybe a]
removeM n = replicateM n dequeueM

-- | State monad action to dequeue an element from the state queue.
dequeueM :: State (FIFO a) (Maybe a)
dequeueM = do q <- get
              case dequeue q of
                Just (x, q') -> put q' >> return (Just x)
                Nothing -> return Nothing

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-04-22
    • 2013-11-01
    • 1970-01-01
    • 2021-12-26
    • 1970-01-01
    • 2018-03-13
    相关资源
    最近更新 更多