【问题标题】:heapq python - how to modify values for which heap is sortedheapq python - 如何修改堆排序的值
【发布时间】:2018-07-18 08:30:29
【问题描述】:

我将一个名为 UNVISITED 的空列表转换为一个堆,这样:

UNVISITED = []
heapq.heappush(UNVISITED, (a.f, a))

我推送的对象a,是从一个类中实例化的,有以下字段:

class UNVISITEDNode():
    def __init__(self, value1, value2 , value3, value4, value5):
            self.q = value1
            self.h = value2
            self.g = value3
            self.f = value4
            self.p = value5

在我的算法中,我会在需要时不断修改堆中已经存在的对象中的任何valueX,例如:

for i in range(len(UNVISITED)):
        UNVISITED[i][1].q = newvalue1
        UNVISITED[i][1].h = newvalue2
        UNVISITED[i][1].g = newvalue3
        UNVISITED[i][1].f = newvalue4
        UNVISITED[i][1].p = newvalue5

因为(或者我认为,如果我错了请纠正我)像我现在所做的那样修改值f不会改变影响堆排序的值,我直接尝试修改UNVISITED[i][0] (即上面的a.f 在创建堆时作为第二个参数的第二部分传递)。

[THE PROBLEM] -> 然后我被告知这个值不允许修改:

UNVISITED[i][0] = newvalue4

*Traceback (most recent call last):
  File "/home/daniel/pycharm-2017.3.3/helpers/pydev/
_pydevd_bundle/pydevd_exec.py", line 3, in Exec
        exec exp in global_vars, local_vars
      File "<input>", line 1, in <module>
    TypeError: 'tuple' object does not support item assignment

我确实需要修改对象a的值f,每次需要都会影响堆的排序,你不能通过UNVISITED[i][1].f = newvalue4来做到这一点(显然)强>。有没有办法做到这一点或任何解决方法?

编辑(已解决)

最终,我将一个简单的手动堆定义为 heap = []heap.append() 的对象。 您可以使用heap.pop() 弹出堆中的第一个元素,并使用heap.sort(key=lambda x: x.f, reverse=True) 根据属性值对其进行排序。 像这样,您将更接近heapq 的行为,并且您可以修改堆中的元素,该堆为其排序。 重要的是要说这比使用heapq 慢得多。

尽管如此,我将@Raymong Hettinger 的答案标记为好的答案,因为其他可能的变通方法的详细信息。

此外,@Davis Yoshida 提出了一个有效的观点,即定义的 可能不是存储数据的最佳方式。

【问题讨论】:

  • 代码中的其他地方可能存在错误,您无意中将元组分配给UNVISITED[i][0] 而不仅仅是UNVISITED[i]
  • @chepner 我是故意这样做的,因此根据UNVISITED[i][0] 中的值对堆进行排序。你可以在这篇文章的第一行看到这一点。如果您还想弹出其他内容,Afaik 是一种定义它的方法。

标签: python sorting heap


【解决方案1】:

无效并重新插入

通常的解决方案是将对象标记为无效并重新插入一个新值。弹出值时,忽略无效条目。

只要不存在大量无效条目,此技术就非常有效。失效步骤在constant time 中运行,随后的弹出在logarithmic time 中运行。

重新堆放

调整一个或多个值后,运行heapify()函数恢复堆不变量。

这使用了一个保证在linear time中运行的公共函数。

直接堆调整

另一种方法是在堆列表中定位对象,使用list.index()。更改值后,根据值是增加还是减少,运行内部 _siftup()_siftdown() 函数。

增加大小写:

>>> from heapq import _siftup, _siftdown, heapify, heappop

>>> data = [10, 5, 18, 2, 37, 3, 8, 7, 19, 1]
>>> heapify(data)
>>> old, new = 8, 22              # increase the 8 to 22
>>> i = data.index(old)
>>> data[i] = new
>>> _siftup(data, i)
>>> [heappop(data) for i in range(len(data))]
[1, 2, 3, 5, 7, 10, 18, 19, 22, 37]

减小大小写:

>>> data = [10, 5, 18, 2, 37, 3, 8, 7, 19, 1]
>>> heapify(data)
>>> old, new = 8, 4              # decrease the 8 to 4
>>> i = data.index(old)
>>> data[i] = new
>>> _siftdown(data, 0, i)
>>> [heappop(data) for i in range(len(data))]
[1, 2, 3, 4, 5, 7, 10, 18, 19, 37]

此技术使用线性时间列表索引和logarithmic time 堆更新。它使用的比较可能比 reheapifying 技术少,但这并不完全令人满意,因为它使用了非公共函数。

度假

最后,您可以使用数据:

>>> data.sort()

这种技术可能比 reheapifying 或直接堆调整进行更多的比较。它起作用的原因是“如果数据被排序,那么它已经是一个堆”。

最坏情况下运行时间可以是O(n log n);然而,排序 实现应用了Timsort 算法,该算法对于部分排序的输入非常有效。

【讨论】:

  • 有没有办法在 heapq 架构中执行此操作,以便 pop 自动在堆中弹出以下值?还是我必须想办法在外部标记这个并解释弹出无效?
  • @thepirate16 问题是堆并不是真正的架构。相反,只是数据的半有序排列。因此,您留下了需要修改排序或使数据无效的解决方案。
  • 已向here 提出了有关此答案的问题,我的answer 可能会提供一些关于_siftup.sort()heapify() 在实践中的比较的额外信息。根据我的观察,heapify 总是更快,即使是在纠正单个不变违规时也是如此。
【解决方案2】:

即使此代码运行,我相信它也不会达到您的预期。 具体来说,如果你这样做了

tup = (a.f, a) # a.f = 7

然后可以执行

tup[0] = 3

您会将tup 设置为(3, a),但a.f 仍为7。

您可以做的一件事是通过向您的 UNVISITEDNode 类添加 __lt__(小于)方法来允许直接比较,如下所示:

class UNVISITEDNode:
   ...
   def __lt__(self, other):
       return self.f < other.f

那么,不用把元组放到heapq里,直接把节点对象放进去就行了。

但是,如果您修改不在堆根的对象,则不再保证堆是有效的,因此您需要重新heapfiy UNVISITED

【讨论】:

  • 这很有趣。尽管如此,我必须直接丢弃一个堆。
  • 你有一个 for 循环在整个堆上运行,修改每个值,所以无论你如何重新堆,你都将使用 O(n log n) 操作。您不妨运行整个 for 循环并重新堆放。
  • 另外一点是,如果您打算遍历所有数据,最好将其存储为堆。
猜你喜欢
  • 2016-11-15
  • 2019-08-06
  • 2016-10-12
  • 2018-06-30
  • 2011-04-26
  • 2011-05-23
  • 2012-12-08
  • 1970-01-01
  • 2012-09-22
相关资源
最近更新 更多