【问题标题】:How to remove an arbitrary element from a standard heap in c++?如何从 C++ 中的标准堆中删除任意元素?
【发布时间】:2012-09-16 06:20:11
【问题描述】:

我有一些代码可以不断地从堆中提取最大值对象并对其进行处理。但是,在处理最大值期间,堆中的其他对象受到影响,可能需要被删除。大致:

vector<HeapEntry*> myHeap = vector<HeapEntry*>();
fillHeap(myHeap, someData);
make_heap(myHeap.begin(), myHeap.end());
while (!myHeap.empty())
{
    HeapEntry* hp = myHeap.front();
    HeapEntry* neighbor = hp->getNeighbor();
    if (someCondition)
    {
        remove(myHeap, neighbor);
    }
    //more processing of hp
}

以及删除功能:

void remove(vector<HeapEntry*> myHeap, HeapEntry* toRemove)
{
    for (it = myHeap.begin(); it != myHeap.end(); it++)
    {
        if (*it == hp)
        {
            myHeap.erase(it);
            break;
        }
    }
    make_heap(myHeap.begin(), myHeap.end());
}

这会运行并给出正确的输出。但它很慢:处理一个 40kb 的文件需要 2 分钟(堆的大小与文件的大小成线性关系)。无论如何,它需要更高效。

remove 函数最终会被调用大约 n 次,其中 n 是堆的大小。因此,进行线性搜索会使整个算法变为 O(n^2)。我认为这就是问题所在,我相信这可以在 O(n*log(n)) 中运行。

我的目标是在 O(log(n)) 时间内完成删除功能。比如:

  • 直接进入目标元素
  • 用最后一个元素切换
  • pop_heap(myHeap.begin(), myHeap.end()); myHeap.pop_back();
  • make_heap(myHeap.begin(), myHeap.end());

我不太确定如何实现这一点(我对 stl 堆几乎不熟悉)。 有谁知道如何在不进行线性搜索的情况下做到这一点?

【问题讨论】:

  • 典范!先做对,再做快。
  • HeapEntry 对象的顺序重要吗?
  • @Lou 在维护堆属性的意义上,顺序很重要。

标签: c++ heap


【解决方案1】:

简单的方法是不删除您认为要删除的元素。相反,您将维护一个优先级队列来确定下一个最大元素和已删除元素的std::set&lt;HeapEntry*&gt;。获取最大元素时,您检查它是否在已删除元素的集合中,然后将其从堆中删除,然后尝试下一个元素。根据可能被移除的元素的数量,您可能还希望在从堆中移除元素时将其从移除的元素集中移除。

您只需将它们添加到已删除元素的集合中,而不是从堆中删除元素。这样,堆元素仍然保持对数,并且您可以对一组元素进行多达 O(n log n) 的操作。

另一种选择是使用基于节点的优先级队列来有效地找到节点在堆中的位置。例如,Boost 提供了一个斐波那契堆作为 Boost Graph Library 的一部分。您可以在那里跟踪元素的位置。但是,由于在重新排列元素时会产生开销,基于节点的堆在实际问题规模上的执行速度往往较慢。

【讨论】:

  • 我喜欢第一种方法,目前正在尝试实现类似的方法。我在 HeapEntry 类中使​​用了一个简单的布尔标志,并将其设置为 false 而不是删除它。当我提取最大值时,我会检查这个标志,如果它是假的则跳过处理。不幸的是,它给了我运行时错误(无效堆),所以我也需要快速解决这个问题。
  • 确保在将元素放入队列后不要更改确定元素优先级的属性!需要告知堆有关元素优先级的更改。可以实现支持相应选项的堆,但它再次需要能够跟踪位置。
  • 对,那肯定会搞砸,但我使用了另一个名为 valid 的字段,仅用于此目的。
【解决方案2】:

感谢您的所有回复。我决定采用一种实际上在 HeapEntries 不再有效时删除它们的方法。实际上,我尝试向 HeapEntry 添加一个有效标志,我认为如果不是因为我已经修复了一些其他错误,这会起作用。无论如何,这就是我最终解决它的方法。

重申一下,我需要能够从堆中删除一个元素,只要一个指向该元素的指针。问题在于,指针没有告诉我关于堆中元素的 position 的任何信息。因此,我决定存储位置,每当元素移动时保持更新,并编写一个函数从给定位置的堆中删除。简单来说,堆存储为数组,元素的位置定义了父子关系。元素的父元素应位于位置 floor((myPos - 1) / 2),其子元素应位于位置 2*myPos+1 和 2*myPos+2。我意识到我可以编写一个 remove(position) 函数,并且在交换元素以维护堆属性的同时,也可以交换它们存储的位置。这是结果的链接,它使执行速度提高了 5 或 10 倍:

https://github.com/yankrasny/CC-Heap-with-random-delete

【讨论】:

  • 随机删除的代码只会渗透下来。但是,不能保证它就足够了。考虑将数据划分为 3 个集合的情况。 Root、High、Low,其中 High 中的每个元素都大于 Low 中的每个元素。考虑来自 High 的元素形成根的左子树,Low 形成根的右子树并且 N(High) > N(low) 的情况,使得最后一个元素在左子树中.在这种情况下,当您从右子树中删除一个元素时,最后一个元素来自 High(左子树)并且需要向上渗透。
【解决方案3】:

stl 的理念是先反思你的算法,然后选择你的数据结构。你却反其道而行之。

如果您打算以“随机”顺序从数据结构中删除元素,则最好使用priority_queue 甚至是链接的list。 (不过要小心:从某些 stl 容器中删除后,迭代器可能会失效)。

【讨论】:

  • 从这个意义上说,priority_queue 会有什么不同?它不只是对相同的 make_heap()、push_heap 等函数的包装吗?另外,我很想找到一个数据结构来满足我的需要,即能够提取最大值,以及删除给定指向随机条目的指针。
【解决方案4】:

我已经晚了将近 7 年,但希望它能对其他人有所帮助。上面已经讨论了一些不错的选择,我想再添加一个。

如果你使用平衡的 BST(即set&lt;HeapEntry*&gt;),你可以在 O(log n) 中找到最大值并删除一个元素。这将使你的整个算法 O(n log n)。

注意 1:如果您有重复,请改用 multiset 并使用 &lt;ms&gt;.erase(&lt;ms&gt;.find(&lt;obj&gt;)) 删除以仅删除一次出现的 &lt;obj&gt;。 &lt;ms&gt;.erase(&lt;obj&gt;) 删除所有出现的&lt;obj&gt;。

注意 2:如果一个元素被删除,所有迭代器、指针和对其他元素的引用仍然有效 (source),因此可以使 find max 成为 O(1)。如果需要,您将迭代器缓存到最大元素并在每次插入或删除时更新它。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-10-17
    • 2018-08-13
    • 1970-01-01
    • 2012-04-27
    • 1970-01-01
    • 1970-01-01
    • 2012-08-27
    相关资源
    最近更新 更多