【问题标题】:Array Based Deques: Why is AddFront/RemoveFront O(1)?基于数组的双端队列:为什么 AddFront/RemoveFront O(1)?
【发布时间】:2015-11-22 01:35:12
【问题描述】:

对于基于数组的双端队列,为什么要从前面摊销 O(1) 添加和删除?对我来说,它总是 O(n) 是有意义的,因为任一操作都意味着数组的当前值需要“移动”到 0 索引的右侧以添加并移到左侧删除...

我的教授说你不需要换班,只需更新 hi 和 lo。我不完全确定他的意思。

这本身不是一个 C++ 问题,但我试图用这种语言来理解它。

【问题讨论】:

  • 我假设它只是将指向“前”和“后”的指针移动到数组中的不同位置。这改变了队列的逻辑结构,而不必真正改变队列中的所有内容。

标签: c++ queue dynamic-arrays deque


【解决方案1】:

你有 2 个指针(hi 和 lo)指向双端队列的每一端 :

"aaaaa" <- hi
"bbbbb"
"ccccc"
"ddddd"<- lo

然后,当您从一端或另一端弹出时,您只需返回指针指向的内容并将指针更改为指向下一个/上一个:

 //"aaaa" is still in the array but not considered part of the deque
 "bbbbb" <- hi
 "ccccc"
 "ddddd" <- lo

非常简单的例子:

class Deque {
int deque[10];
int hi, lo;   // Initialize in constructor to -1 to flag deque is empty

public:
void push_front(int x) {
    // First time, add to middle of array
    if (-1 == lo) {
        hi = lo = 5; // Since array size is 10;
        deque[5] = x;
    }
    // Shift required?
    else if (lo == 0) {
        // Check if deque is full
        if (9 == hi) {
             // return error or get more space
        }
        else {
             // Shift by one
             // More optimally you would shift enough to move data to center of array
             for (int i = hi; i >= lo; --i) {
                 deque[i + 1] = deque[i];
             }
             // Update hi
             hi += 1;
             // Store in lo - lo remains at 0
             deque[0] = x;
         }
     }
     // No shift required
     else {
         deque[--lo] = x;
     }
 }
 // TODO: push_back, pop_front, pop_back

【讨论】:

  • 该死的,我应该意识到它使用指针。好的,现在说得通了。但是如果您添加到前面,您是否还需要移动/移动项目?
  • 是的,添加时会发生移位,但前提是两端的空间不足。
  • 假设您创建了这个双端队列:{3, 5, 10, 12},然后做了五个 addfronts。 (不要担心大小/空间)。不是每个 addfront 都意味着您必须转换,并且在每个 addfront 之后 lo 都会引用新值吗?
  • 视情况而定。例如:您的数组足够大,可以容纳 100 个数字,示例中的 3 位于 array[50],12 位于 array[53]。那么你的 5 个 addfronts 不需要任何移动。
  • @ohbrobig 分配新内存块时,会将旧元素复制到块的中间而不是开头。然后你可以在不移动的情况下添加到前面或后面,直到再次碰到边界。
【解决方案2】:

我不了解双端队列的确切 C++ 实现,但这是我最好的尝试来合理化您的教授关于时间复杂度的说法:

cppreferencecplusplus 看来,双端队列是非连续存储的。

Here 是另一个 StackOverflow 问题,它提供了有用的图像和一些丰富的信息。

请注意start 是如何简单地指向前面的,但如何也可以有unused 部分的单独块。因此,如果我们可以简单地将旧的正面标记为“未使用”,那么在弹出正面时可能不需要移动所有内容,背面也类似。

编辑:我的猜测是,因为在开头和结尾都有 unusued 部分,所以添加到前面或后面相对容易,直到正如约翰在他自己的回答中所说,你用完了空间/'达到边界'。

【讨论】:

  • std::deque 必须是非连续的,因为它对迭代器失效等有限制。在任一端插入分期 O(1) 并不是绝对必要的。
  • 我试图研究你所说的“迭代器失效”是什么意思,因为我不熟悉这个术语。根据我试图研究的内容,我认为这些“约束”是由于双端队列的特性(只能弹出/推动前/后)。真的吗?最重要的是,我不明白为什么“限制迭代器失效”需要“非连续”存储。你能帮忙解释一下这个要求吗?
  • 我错了,允许迭代器在插入时失效,但引用不是。这意味着每个元素都需要保留在其原始内存位置,它不能随着deque 的大小增加而移动——这就是为什么它必须是非连续的。与 vector 相比,只要容器改变大小,引用就会失效。规则见stackoverflow.com/questions/6438086/iterator-invalidation-rules
  • 感谢您提供额外的链接。我将继续研究您提供的要点[:
猜你喜欢
  • 2020-08-10
  • 1970-01-01
  • 2014-10-14
  • 2016-10-31
  • 2020-04-19
  • 2020-06-18
  • 1970-01-01
  • 2020-10-15
  • 2012-11-05
相关资源
最近更新 更多