【问题标题】:How do I move the last item in a list to the front in O(1) time?如何在 O(1) 时间内将列表中的最后一项移动到前面?
【发布时间】:2017-09-12 19:46:29
【问题描述】:

我的代码如下所示,其中heaplist

 heap[0] = heap.pop()

问题是弹出唯一的项目时它不起作用。根据 python wiki,弹出最后一个元素是 O(1)。 (我也想去掉最后一个元素)

到目前为止,我想出了什么:

last_element = heap.pop()  # I don't want to catch potential IndexError here
try:
    heap[0] = last_element
except IndexError:
    heap.append(last_element)

更长的时间

或者这个

if len(heap) != 1:
    heap[0] = heap.pop()

应该更慢。

有没有更蟒蛇的方式来做到这一点?在内部,python 甚至可能不会调整数组的大小。

更新: 我需要 O(1) 访问给定索引处的元素(因此它不能是链表)。

【问题讨论】:

  • heap[0] = heap.pop(-1) 是否也适用于更大的列表?例如,如果heap[1,2,3],那么您希望它变成[3,1,2],对吗?但是heap[0] = heap.pop(-1) 导致它变成[3,2],.. 或者这就是你想要的?请澄清。
  • 单个if 声明不是您应该担心的成本。
  • Python 文档说list.pop(),没有参数,删除并返回列表中的最后一项。
  • 您确定了解您的要求吗? heap[0] = heap.pop(-1) 听起来像是在筛选之前立即出现在堆弹出例程中的那种东西,但是如果你从堆中弹出唯一的元素,你不应该在全部;您应该只删除并返回单个元素。
  • @user2357112 是的,它在筛选之前。你刚刚把我从另一个调试中救了出来。所以我可能会使用 try except 的东西,在这种情况下不要冒泡。

标签: python python-3.x heap


【解决方案1】:

听起来你正在实现一个堆,而这一行是堆弹出操作实现的一部分。在上下文中,它看起来像

def heappop_wrong(heap):
    popped_value = heap[0]
    heap[0] = heap.pop()  # <-- here
    sift_down(heap, 0)
    return popped_value

在这种情况下,如果列表只有一个元素,则根本不应该将最后一个元素移到前面。您应该只删除并返回唯一的元素:

def heappop(heap):
    if len(heap) == 1:
        return heap.pop()
    popped_value = heap[0]
    heap[0] = heap.pop()
    sift_down(heap, 0)
    return popped_value

至于if的成本,这个成本是微不足道的,根本不是你应该担心的。


为了将列表的最后一个元素移动到前面而不替换旧的前面元素,这在 O(1) 时间内是不可能的。你必须使用不同的数据结构,比如collections.deque,或者实现你自己的可调整大小的ring buffer。不过,这与堆用例无关。

为了将列表的最后一个元素移到前面,如果有多个元素则踩在旧的第一个元素上,如果只有一个元素则保持列表不变,if 检查可能是最清晰的,但同样, “如果只有一个元素,则保持列表不变”行为实际上对于实现堆弹出没有用。

【讨论】:

  • 是的,如果列表为空,则筛选没有意义。此外,如果列表为空,我的筛选实现会给我另一个 IndexError
  • 如果@ShadowRanger 真的是O(1),那么heap[0] = heap.pop() 无论如何都是一个解决方案。
【解决方案2】:

一种有效的方法(没有例外,只要list 不为空)是分配给list 的一个切片:

heap[:1] = (heap.pop(),)

弹出最后一个元素并创建一个长度为 1 的tuple,然后将其分配给heap,这样如果它存在则替换第一个元素,或者如果它为空则成为list 的内容(由于pop)。它应该保留O(n),因为它总是用单个元素替换单个元素;较高的指数保持不变。

【讨论】:

  • 要清楚,这有点过于神奇了。对于真正的代码,我可能会使用user235112's answer 之类的东西。好吧,在 real 代码中,我会使用 the heapq module;如果您还没有意识到这一点,我建议您看一下。编写自己的堆作为学习练习很好,但不要为生产代码重新发明轮子。
  • 实际上我使用了heapq 模块,但我发现缺少decrease 功能令人失望。我不喜欢文档中的解决方法,因为我下周要参加编程考试,所以我写了一个。它要短得多(对于我需要的东西),它是一个对象而不是函数。
【解决方案3】:

collections.deque 支持在 O(1) 中从前后删除和附加。

https://wiki.python.org/moin/TimeComplexity

【讨论】:

  • 我使用列表作为二进制堆优先级队列,所以我需要直接访问索引。
  • 你应该可以做到。只是那个是O(n)。你不能拥有一切。
  • 来自文档:索引访问在两端是 O(1),但在中间减慢到 O(n)。对于快速随机访问,请改用列表。
【解决方案4】:

您可以在尝试弹出之前检查列表是否为空。

if len(a) > 0:
  a.insert(0,a.pop())

默认情况下,pop 返回列表中的最后一个元素,因此您不必显式传递 -1。由于如果列表为空会引发错误,我们可以在尝试弹出之前检查其长度。

但是对列表的插入操作是 O(n)。对于 O(1),您将需要使用出队。

from collections import deque
a = deque()
a.append(10)
a.append(12)
if len(a) > 0:
  a.appendleft(a.pop())
print a

【讨论】:

  • 你的意思是if len(a) &gt; 1: 吗?这将处理零长度和一长度列表。
  • 是的,做 >1 是一个轻微的优化。
  • 这个不去掉前导元素,运行在O(n)时间,所以不符合要求。
  • @ShadowRanger 我更新了答案以说明它是 O(n) 并给出了双端队列的例子
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-07-18
  • 1970-01-01
  • 2022-11-27
  • 1970-01-01
相关资源
最近更新 更多