【问题标题】:ArrayDeque operationsArrayDeque 操作
【发布时间】:2021-02-24 18:06:01
【问题描述】:

我有一些空闲时间并试图了解 ArrayDeque 在内部是如何工作的。我在这里阅读了几篇文章和问题/答案,我想我已经很接近了。我使用调试来遵循工作流程,有些事情让我很困扰。 我创建了一个空的双端队列,它产生了一个包含 16 个为空元素的数组。 当我使用 addFirst 时,它在数组中的第 16 位添加了一个元素,并在第 0 位的开头添加了 addLast。我错过了什么,请您分享一些知识或指出我正确的方向,这样我就可以完全理解窗帘后面发生的事情。 提前致谢!

【问题讨论】:

    标签: java data-structures deque circular-buffer arraydeque


    【解决方案1】:

    基于数组的双端队列通常使用称为循环缓冲区的数据结构来实现。这个想法是我们维护一个元素数组,但假设数组的末端粘在一起形成一个环。

    从您的调试来看,ArrayDeque 内部似乎维护了一个包含 16 个元素的数组,我们可以这样查看:

    +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
    |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
    +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
    

    我们维护两个不同的指针,一个头指针和一个尾指针,跟踪双端队列第一个元素的位置和最后一个元素的位置双端队列的元素。最初,它们将指向数组的开头:

     head
      |
      v
    +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
    |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
    +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
      ^
      |
     tail
    

    每当我们执行addFirst 时,我们都会将头指针备份一步,然后将元素写入我们找到的位置。由于我们假设数组的两端是连在一起的,所以这里倒退一步会将头指针移动到最后一个位置:

                                                                 head
                                                                  |
                                                                  v
    +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
    |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   | X |
    +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
      ^
      |
     tail
    

    要做一个addLast,我们写到尾部位置,然后向前推进:

                                                                 head
                                                                  |
                                                                  v
    +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
    | X |   |   |   |   |   |   |   |   |   |   |   |   |   |   | X |
    +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
          ^
          |
         tail
    

    如果我们再做两个addFirsts 会是什么样子:

                                                         head
                                                          |
                                                          v
    +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
    | X |   |   |   |   |   |   |   |   |   |   |   |   | X | X | X |
    +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
          ^
          |
         tail
    

    如果我们再做两个addLasts,这就是它的样子:

                                                         head
                                                          |
                                                          v
    +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
    | X | X | X |   |   |   |   |   |   |   |   |   |   | X | X | X |
    +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
                  ^
                  |
                 tail
    

    我们从头指针开始读取双端队列的元素,然后继续向前直到到达尾指针。所以在这种情况下,我们从head指向的槽开始读取,而不是数组中的第一个位置。

    最终,两个指针会在中间相遇。发生这种情况时,我们会创建一个比原始数组大(通常大 150%)的全新数组,然后将元素复制到新数组中以释放一些空间。

    【讨论】:

    • 您是如何在 2 分钟内输入答案的?没有 5 分钟。
    • 这非常有帮助,我非常感谢你。这有点令人困惑,因为看起来这两个操作都以与它们的名称相反的方式工作,因为 addFirst 在头部添加,这恰好在数组的末尾(索引方式),而 addLast 在尾部添加恰好在数组的开头。再次感谢您的完整回答!
    • 我已经编辑了我的答案以澄清一个重要细节 - 从数组中读取时,我们不像往常那样从位置 0 开始,而是从头指针的位置开始。这样,即使我们在数组末尾进行备份和写入,我们也假设数组从头部位置开始。
    • 是的,我想通了。再次非常感谢!
    猜你喜欢
    • 2013-07-14
    • 2017-10-13
    • 2013-05-04
    • 2012-05-17
    • 1970-01-01
    • 2023-03-26
    • 2012-12-05
    • 2014-06-03
    • 2015-04-03
    相关资源
    最近更新 更多