heapq python - 如何修改堆排序的值

时间:2018-02-07 22:14:10

标签: python sorting heap

我将名为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就像我现在所做的那样不会改变影响堆排序的值,我直接尝试修改{ {1}}(以上UNVISITED[i][0]在创建堆时作为第二个参数的第二部分传递。)

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

a.f

我真的需要修改对象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 的值f,这必须在每次需要时影响堆的排序,而不能通过{{1}执行此操作(显然)。有没有办法做到这一点或任何解决方法?

编辑(已完成的工作)

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

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

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

2 个答案:

答案 0 :(得分:3)

无效并重新插入

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

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

Reheapify

调整一个或多个值后,运行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堆更新。它可能比重新加热技术使用更少的比较,但这并不完全令人满意,因为它使用非公共函数。

诉诸

最后,您可以使用数据:

>>> data.sort()

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

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

答案 1 :(得分:1)

即使这个代码运行,我相信它不会做你想要的。 具体来说,如果你做了

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

然后可以执行

tup[0] = 3

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

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

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

然后,不要将元组放入heapq,而是直接将节点对象放入。

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