我知道可以用 O(log n) 的时间复杂度实现 decrease-key 功能,但是我不知道怎么做?
_siftdown
(增量类似于_siftup
)。因此好消息是这些函数都在那里......坏消息是它们的名称以下划线开头,表示它们被认为是“内部实现细节”,不应该被应用程序代码直接访问(标准库的下一个版本可能会改变事情并打破使用这样的“内部实现”的代码)。_
,使用O(N) heapify
而不是O(log N)筛选,或重新实现一些或所有heapq的功能,使筛选原语“公开为接口的一部分”。由于heapq的数据结构已记录并公开(只是一个列表),我认为最好的选择可能是部分重新实现 - 将sifting功能从heapq.py复制到应用程序代码中。[2, 3, 5]
,那么2
将是父元素,3
和5
将是其两个子元素) - tscizzle_siftup
和_siftdown
)应触发地图的更新。 - Michael EkokaDecrease-key是许多算法(Dijkstra算法、A*、OPTICS)必备的操作,我想知道为什么Python内置的优先级队列不支持该操作。
不幸的是,我无法下载math4tots包。
但是,我找到了这个由Daniel Stutzbach实现的库。在Python 3.5中与我完美地配合使用。
hd = heapdict()
hd[obj1] = priority
hd[obj1] = lower_priority
# ...
obj = hd.pop()
heapq文档详细介绍了如何实现此操作。
不过,我已经编写了一个heap
包,它完全可以实现这一点(它是heapq
的包装器)。因此,如果您有pip
或easy_install
,可以执行类似以下命令:
pip install heap
from heap.heap import heap
h = heap()
h['hello'] = 4 # Insert item with priority 4.
h['hello'] = 2 # Update priority/decrease-key has same syntax as insert.
它相当新,可能会有很多漏洞。
task_list = [[7,"do laundry"], [3, "clean room"], [6, "call parents"]]
,其中task_list
中的每个任务都是一个具有优先级和描述的列表。如果运行heapq.heapify(task_list)
,则可以使数组维护堆不变量。然而,如果您想将“do laundry”的优先级更改为1,则无法在不通过堆进行线性扫描的情况下知道“do laundry”在堆中的位置(因此无法在对数时间内进行降低关键字操作)。请注意,decrease_key(heap, i, new_key)
要求您知道要在堆中更改值的索引。task_list
中的第0个任务):task_list = [[7, "do laundry"], [3, "clean room"], [6, "call parents"]]
task_list_heap = task_list[:] # make a non-deep copy
heapq.heapify(task_list_heap)
# at this point:
# task_list = [[7, 'do laundry'], [3, 'clean room'], [6, 'call parents']]
# task_list_heap = [3, 'clean room'], [7, 'do laundry'], [6, 'call parents']]
# Change key of first item of task_list (which was "do laundry") from 7 to 1.
task_list[0][0] = 1
# Now:
# task_list = [[1, 'do laundry'], [3, 'clean room'], [6, 'call parents']]
# task_list_heap = [3, 'clean room'], [1, 'do laundry'], [6, 'call parents']]
# task_list_heap violates heap invariant at the moment
heapq._siftdown(task_list_heap, 1)
来以对数时间维护堆不变性(heapq.heapify
是线性时间),但不幸的是我们不知道 "洗衣服" 在 task_list_heap
中的索引(在这种情况下,heap_index
是 1)。heap_index
;例如,有一个 list
(用于堆)和一个 dict
,将每个对象映射到其在堆/列表中的索引(随着堆位置的交换而更新,为每个交换添加一个常数因子)。您可以阅读 heapq.py 并自己实现,因为该过程很简单;但是,其他人已经实现了这种类型的 HeapDict。也许没有必要使用 decrease_key
函数(虽然有它非常好),
您可以将(priority,item)
直接推送到优先队列中,并使用set
来检查是否已经看到它。例如:
pq = [] # heapq is a min heap
seen = set()
heappush(pq, (2, "item1"))
heappush(pq, (3, "item2"))
heappush(pq, (1, "item3"))
heappush(pq, (4, "item4"))
heappush(pq, (2, "item2"))
while pq:
p, item = heappop(pq)
if item not in seen:
seen.add(item)
print(item, p)
else:
print(item, "is already handled with a higher priority!")
输出结果为:
item3 1
item1 2
item2 2
item2 is already handled with a higher priority!
item4 4
在C++和Java标准库的优先队列中也缺少这种功能。标准解决方法是推送一个新的键值对,并将原始键值对隐式或显式地标记为无效。
请参见如何更新堆内元素?(优先队列)和Dijkstra算法为什么要使用减少键操作?(结论是,理论上和实际上没有减少键操作不会对运行时间产生显著影响;特别是请参见论文https://www3.cs.stonybrook.edu/~rezaul/papers/TR-07-54.pdf)
使用最小堆实现的优先队列,具有唯一键。如果键已经存在于优先队列中,则此实现在push()
操作期间更新键的优先级。
import heapq
class HeapPQ:
"""
Only hashable key type is supported
"""
def __init__(self):
self.pq = []
self.pq_set = set()
def push(self, priority, key):
if key not in self.pq_set:
heapq.heappush(self.pq, (priority, key))
self.pq_set.add(key)
else:
index = list(map(lambda x:x[1], self.pq)).index(key)
self.pq[index] = (priority, key)
heapq.heapify(self.pq)
def pop(self):
priority, key = heapq.heappop(self.pq)
self.pq_set.remove(key)
return priority, key
def empty(self) -> bool:
return len(self.pq) == 0
使用示例:
pq = HeapPQ()
pq.push(5, "A")
pq.push(3, "B")
pq.push(1, "A")
while not pq.empty():
print(pq.pop())