【发布时间】:2010-11-30 17:48:04
【问题描述】:
我知道可以在 O(log n) 中实现减少键功能,但我不知道如何?
【问题讨论】:
我知道可以在 O(log n) 中实现减少键功能,但我不知道如何?
【问题讨论】:
要有效地实现“减少键”,您需要访问“减少此元素并与子元素交换此元素,直到堆条件恢复”的功能。在heapq.py 中,这称为_siftdown(类似地_siftup 用于递增)。所以好消息是这些函数就在那里......坏消息是它们的名称以下划线开头,表明它们被认为是“内部实现细节”,不应由应用程序代码直接访问(下一个版本的标准库可能会使用此类“内部”来改变和破坏代码)。
由您决定是否要忽略警告前导-_,使用 O(N) heapify 而不是 O(log N) 筛选,或者重新实现 heapq 的部分或全部功能以使筛选原语“作为接口的公共部分公开”。由于 heapq 的数据结构已记录在案并且是公开的(只是一个列表),我认为最好的选择可能是部分重新实现——本质上,将筛选函数从 heapq.py 复制到您的应用程序代码中。
【讨论】:
[2, 3, 5],那么 2 将是父元素,3 和 5 将是它的两个子元素)
_siftup 和_siftdown)应该会触发地图的更新。
Decrease-key是很多算法(Dijkstra's Algorithm、A*、OPTICS)的必备操作,不知道为什么Python内置的优先级队列不支持。
很遗憾,我无法下载 math4tots 的软件包。
但是,我找到了 Daniel Stutzbach 的 this 实现。使用 Python 3.5 非常适合我。
hd = heapdict()
hd[obj1] = priority
hd[obj1] = lower_priority
# ...
obj = hd.pop()
【讨论】:
heapq documentation 有一个关于如何执行此操作的条目。
但是,我已经编写了一个 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),你会让你的数组保持堆不变。但是,如果您想将“洗衣服”的优先级更改为 1,如果没有对堆进行线性扫描,您将无法知道“洗衣服”在堆中的位置(因此不能在对数时间内执行 reduce_key) .注意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 标准库优先级队列也缺少此功能。标准的解决方法是推送一个新的键值对,并隐式或显式地将原始键值对标记为无效。见How to update elements within a heap? (priority queue)
【讨论】: