【问题标题】:O(1) indexable deque of integers in PythonO(1) Python 中整数的可索引双端队列
【发布时间】:2012-05-04 17:05:35
【问题描述】:

我有哪些选择?我需要调用很多appends(到右端)和poplefts(从左端,自然),还要从存储的中间读取,这自然会稳步增长的算法。我想在O(1) 中进行所有这些操作。

我可以在 C 语言中很容易地在循环寻址数组(单词是什么?)上实现它,它会在它满时自动增长;但是 Python 呢?指向其他语言的指针也很受欢迎(我意识到“集合”标签更面向 Java 等,并且希望进行比较,但作为次要目标)。

我来自 Lisp 背景,惊讶地发现在 Python 中从列表中删除 head 元素是 O(n) 操作。 deque 可能是一个答案,除非文档说访问是在中间的O(n)。还有什么预建的吗?

【问题讨论】:

标签: python collections


【解决方案1】:

您可以通过使用两个 python 列表来获得一个摊销的 O(1) 数据结构,一个包含 deque 的左半部分,另一个包含右半部分。前半部分是反向存储的,因此双端队列的左端位于列表的后面。像这样的:

class mydeque(object):

  def __init__(self):
    self.left = []
    self.right = []

  def pushleft(self, v):
    self.left.append(v)

  def pushright(self, v):
    self.right.append(v)

  def popleft(self):
    if not self.left:
      self.__fill_left()
    return self.left.pop()

  def popright(self):
    if not self.right:
      self.__fill_right()
    return self.right.pop()

  def __len__(self):
    return len(self.left) + len(self.right)

  def __getitem__(self, i):
    if i >= len(self.left):
      return self.right[i-len(self.left)]
    else:
      return self.left[-(i+1)]

  def __fill_right(self):
    x = len(self.left)//2
    self.right.extend(self.left[0:x])
    self.right.reverse()
    del self.left[0:x]

  def __fill_left(self):
    x = len(self.right)//2
    self.left.extend(self.right[0:x])
    self.left.reverse()
    del self.right[0:x]

我不能 100% 确定这段代码和 python 列表的摊销性能之间的交互是否实际上导致每个操作的 O(1),但我的直觉是这样说的。

【讨论】:

  • 非常感谢,会考虑的。 del self.right[0:x] 会花费 O(n) 时间吗?还有extend
  • 实际上,在这个算法中我从不调用poprightpushleft,所以只要左边用尽了,我就会把反转的右边换成左边。这太棒了!非常感谢!
  • 是的,但想法是 fill_rightfill_left 仅在每个 O(n) 操作中调用,因此每个操作需要分摊的 O(1) 时间。我很确定你的要求只能在分期 O(1) 时间内实现,除非大小是有界的(你在 Q 中提到的 C 实现的调整大小也会使其成为 O(n) 最坏的情况)。
  • 是的,我明白这一点,偶尔 O(n) 是好的(但比 O(n) 更糟糕,这就是我的意思)。很抱歉不清楚。但是由于我从不打电话给poprightpushleft,所以唯一会感到筋疲力尽的是left,我将使用相反的right!我将直接在代码中互换使用这两个列表。非常感谢!
  • 代码q = mydeque(); q.pushleft(1); print(q.popright()) 崩溃,因为__fill_left 函数仅将1 // 2 == 0 元素从“左”列表传输到“右”列表。
【解决方案2】:

访问 lisp 列表的中间部分也是 O(n)。

Python lists 是数组列表,这就是为什么弹出头部很昂贵(弹出尾部是恒定时间)。

您正在寻找的是一个在头部具有(摊销)恒定时间删除的数组;这基本上意味着您将不得不在 list 之上构建一个使用延迟删除的数据结构,并且能够在队列为空时回收延迟删除的插槽。

或者,使用哈希表和几个整数来跟踪当前连续的键范围。

【讨论】:

  • 我可以在 O(n) 时间内从列表的头部删除一个连续的范围吗?怎么样?
  • @WillNess 然而……python 程序员仍然使用哈希表。如果您尝试在“H”下查找哈希表,那么我建议您按照教程(或仔细阅读文档)来了解 python 提供的内容。
  • @WillNess 如果您不知道如何删除列表的一部分,那么我肯定建议您遵循 python 教程。
  • @WillNess,我会给你一个提示:字典。还有 Marcin,你可以提供更多帮助。
  • @WillNess 您没有费心去学习使用基本类型的语法,甚至没有了解存在哪些内置数据类型。您在此答案上提出的两个问题的答案是使用标准文档或介绍性材料实际学习 python。
【解决方案3】:

Python's Queue Module 可能会对您有所帮助,尽管我不确定访问权限是否为 O(1)

【讨论】:

  • 队列不支持访问队列中间。
猜你喜欢
  • 2011-01-18
  • 2020-06-18
  • 2016-10-31
  • 1970-01-01
  • 2020-11-08
  • 1970-01-01
  • 2020-08-10
  • 1970-01-01
相关资源
最近更新 更多