如何在 Python 的 heapq 中实现减少键功能?

发布于 2024-08-05 11:17:44 字数 42 浏览 13 评论 0原文

我知道可以在 O(log n) 中实现减少键功能,但我不知道如何实现?

I know it is possible to realize decrease-key functionality in O(log n) but I don't know how?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

孤凫 2024-08-12 11:17:44

为了有效地实现“减少键”,您需要访问“递减此元素并与子元素交换此元素,直到堆条件恢复”的功能。在 heapq.py 中,称为 _siftdown (以及类似的 _siftup 用于 INcrementing)。因此,好消息是这些函数就在那里……坏消息是它们的名称以下划线开头,表明它们被视为“内部实现细节”,不应该由应用程序代码直接访问(下一个版本的标准库可能会改变周围的情况并使用此类“内部结构”破坏代码)。

由您决定是否要忽略前​​导-_ 警告,使用 O(N) heapify 而不是 O(log N) 筛选,或者重新实现一些或heapq 的所有功能都使筛选原语“作为接口的公共部分公开”。由于 heapq 的数据结构已记录并公开(只是一个列表),我认为最好的选择可能是部分重新实现 - 本质上将筛选函数从 heapq.py 复制到您的应用程序代码中。

To implement "decrease-key" effectively, you'd need to access the functionality "decrement this element AND swap this element with a child until heap condition is restore". In heapq.py, that's called _siftdown (and similarly _siftup for INcrementing). So the good news is that the functions are there... the bad news is that their names start with an underscore, indicating they're considered "internal implementation details" and should not be accessed directly by application code (the next release of the standard library might change things around and break code using such "internals").

It's up to you to decide whether you want to ignore the warning leading-_, use O(N) heapify instead of O(log N) sifting, or reimplement some or all of heapq's functionality to make the sifting primitives "exposed as public parts of the interface". Since heapq's data structure is documented and public (just a list), I think the best choice is probably a partial-reimplementation -- copy the sifting functions from heapq.py into your application code, essentially.

云醉月微眠 2024-08-12 11:17:44

Decrease-key是很多算法(Dijkstra算法、A*、OPTICS)的必备操作,我想知道为什么Python内置的优先级队列不支持它。

不幸的是,我无法下载 math4tots 的软件包。

但是,我能够找到 Daniel Stutzbach 的实现。 Python 3.5 非常适合我。

hd = heapdict()
hd[obj1] = priority
hd[obj1] = lower_priority
# ...
obj = hd.pop()

Decrease-key is a must-have operation for many algorithms (Dijkstra's Algorithm, A*, OPTICS), i wonder why Python's built-in priority queue does not support it.

Unfortunately, i wasn't able to download math4tots's package.

But, i was able to find this implementation by Daniel Stutzbach. Worked perfectly for me with Python 3.5.

hd = heapdict()
hd[obj1] = priority
hd[obj1] = lower_priority
# ...
obj = hd.pop()
忆悲凉 2024-08-12 11:17:44

heapq 文档有一个关于具体如何实现的条目来做到这一点。

但是,我编写了一个 heap 包来完成此操作(它是 heapq 的包装器)。因此,如果您有 pipeasy_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. 

It is 不过,它是相当新的,因此可能充满错误。

The heapq documentation has an entry on exactly how to do this.

However, I have written a heap package that does exactly this (it is a wrapper around heapq). So if you have pip or easy_install you could do something like

pip install heap

Then in your code write

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. 

It is pretty new though, so might be full of bugs.

豆芽 2024-08-12 11:17:44

想象一下,您正在使用堆作为优先级队列,其中有一堆由字符串表示的任务,每个任务都有一个键。具体而言,请查看:task_list = [[7,"洗衣服"], [3,"洁净室"], [6,"打电话给父母"]],其中 中的每个任务task_list 是一个带有优先级和描述的列表。如果运行 heapq.heapify(task_list),您的数组将保持堆不变量。但是,如果您想将“do洗衣”的优先级更改为1,则在不线性扫描堆的情况下,您无法知道“do洗衣”在堆中的位置(因此无法在对数时间内执行decrease_key) 。注意 decrease_key(heap, i, new_key) 要求您知道堆中要更改的值的索引。

即使您维护对每个子列表的引用并实际更改键,您仍然无法在日志时间内完成此操作。由于列表只是对一堆可变对象的引用,因此您可以尝试执行以下操作:记住任务的原始顺序:(在本例中,“洗衣服”是原始 task_list):

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

Imagine you are using a heap as a priority queue, where you have a bunch of tasks represented by strings and each task has a key. For concreteness, look at: task_list = [[7,"do laundry"], [3, "clean room"], [6, "call parents"]] where each task in task_list is a list with a priority and description. If you run heapq.heapify(task_list), you get your array to maintain the heap invariant. However, if you want to change the priority of "do laundry" to 1, you have no way of knowing where "do laundry" is in the heap without a linear scan through the heap (hence can't do decrease_key in logarithmic time). Note decrease_key(heap, i, new_key) requires you to know the index of the value to change in the heap.

Even if you maintain a reference to each sublist and actually change the key, you still can't do it in log time. Since a list is just a reference to a bunch of mutable objects, you could try doing something like remember the original order of the task: (in this case that "do laundry" was the 0th task in your original task_list):

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

However, you now need to call heapq._siftdown(task_list_heap, 1) to maintain the heap invariant in log time (heapq.heapify is linear time), but unfortunately we don't know the index of "do laundry" in task_list_heap (the heap_index in this case is 1).

So we need to implement our heap keeps track of the heap_index of each object; e.g., have an list (for the heap) and a dict mapping each object to its index in the heap/list (that gets updated as the heap positions get swapped adding a constant factor to each swap). You could read through heapq.py and implement yourself as the procedure is straightforward; however, others have implement this sort of HeapDict already.

烟酉 2024-08-12 11:17:44

可能没有必要使用 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

It might be unnecessary to have the decrease_key function (albeit it's nice to have it).

You can just push your (priority, item) into the priority queue anyway, and use a set to check whether you have seen it. For example:

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!")

The output is:

item3 1
item1 2
item2 2
item2 is already handled with a higher priority!
item4 4
趁微风不噪 2024-08-12 11:17:44

C++ 和 Java 标准库优先级队列也缺少此功能。标准解决方法是推送新的键值对,并隐式或显式地将原始键值对标记为无效。

请参阅如何更新其中的元素一堆? (优先级队列)为什么Dijkstra算法使用decrease-key? (结论是,没有减少键不会在理论上和实践中显着影响运行时;特别是参见论文 https://www3.cs.stonybrook.edu/~rezaul/papers/TR-07-54.pdf)

This functionality is also missing from C++ and Java standard library priority queues. The standard workaround is to push a new key-value pair and either implicitly or explicitly mark the original key-value pair as invalid.

See How to update elements within a heap? (priority queue) and Why does Dijkstra's algorithm use decrease-key? (the conclusion there is that not having decrease-key won't significantly impact runtime in theory and in practice; in particular see the paper https://www3.cs.stonybrook.edu/~rezaul/papers/TR-07-54.pdf)

芸娘子的小脾气 2024-08-12 11:17:44

使用具有唯一键的最小堆的优先级队列简单实现。如果优先级队列中已存在键,则此实现会在 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())

Priority Queue Simple Implementation with Min heap with unique keys. This implementation updates the priority of the key during push() operation if key already exists in priority queue.

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

Example Usage:

pq = HeapPQ()

pq.push(5, "A")
pq.push(3, "B")
pq.push(1, "A")

while not pq.empty():
    print(pq.pop())
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文