我有一些代码可以从堆中不断提取最大值对象并对其进行处理。但是,在处理max期间,堆中的其他对象会受到影响,并且可能需要删除。大致是:
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());
}
运行并提供正确的输出。但它完全缓慢:2分钟处理40kb文件(堆的大小与文件大小呈线性关系)。无论如何,它需要更高效。
remove函数最终被调用大约n次,其中n是堆的大小。因此,进行线性搜索会使整个算法成为O(n ^ 2)。我认为这是问题,我相信这可以在O(n * log(n))中运行。
我的目标是在O(log(n))时间内执行remove函数。类似的东西:
我不太确定如何实现它(我对stl堆几乎不熟悉)。 有没有人知道如何在不进行线性搜索的情况下做到这一点?
答案 0 :(得分:5)
简单的方法是不来删除您认为要删除的元素。相反,您将维护一个优先级队列来确定下一个最大元素和已移除元素的std::set<HeapEntry*>
。获取max元素时,检查它是否在删除元素集中,然后将其从堆中删除,尝试下一个元素。根据可能删除的元素的数量,您可能还希望在从堆中删除元素时从删除的元素集中删除该元素。
您只需将它们添加到已删除元素集中,而不是从堆中删除元素。这样堆元素仍然保持对数,你可以对元素集进行最多O(n log n)次操作。
另一种选择是使用基于节点的优先级队列来有效地找到堆中节点的位置。例如,Boost提供Fibonacci堆作为Boost Graph Library的一部分。您可以在那里跟踪元素的位置。但是,基于节点的堆在实际问题大小上往往会因重新排列元素时的开销而执行速度较慢。
答案 1 :(得分:1)
stl的理念是首先反思你的算法,然后选择你的数据结构。你反过来也是这样做的。
如果您打算以“随机”顺序从数据结构中删除元素,那么使用priority_queue
甚至链接的list
可能会更好。 (但要小心:从某些stl容器中删除后,迭代器可能会失效。)
答案 2 :(得分:1)
感谢您的所有回复。我决定采用一种方法,实际上当它们不再有效时删除HeapEntries。实际上我尝试向HeapEntry添加一个有效的标志,我认为如果不是因为我已经修复过的其他一些错误就行了。无论如何,这是我最终解决它的方式。
重申一点,我需要能够只给出一个指向该元素的指针从堆中删除一个元素。问题是,指针没有告诉我任何关于堆中元素的位置的信息。因此,我决定存储位置,在元素移动时保持更新,并编写一个函数以从给定位置的堆中删除。简单地说,堆存储为数组,元素的位置定义父/子关系。元素的父元素应位于位置((myPos - 1)/ 2),其子元素应位于2 * myPos + 1和2 * myPos + 2的位置。我意识到我可以编写一个remove(position)函数,并且在交换元素以维护堆属性时,也可以交换它们存储的位置。这是结果的链接,加速执行5或10倍:
答案 3 :(得分:0)
我已经快7年了,但希望对您有所帮助。上面已经讨论了一些不错的选择,我只想添加另一个。
如果使用平衡的BST(即set<HeapEntry*>
),则可以找到最大值并删除O(log n)中的元素。这将使您的整个算法为O(n log n)。
注1:如果有重复项,请改用multiset
,然后用<ms>.erase(<ms>.find(<obj>))
删除,只删除一次<obj>
。 <ms>.erase(<obj>)
删除所有出现的<obj>
。
注2:可以使用以下操作将find max设为O(1):如果删除了一个元素,则所有迭代器,指针和对其他元素的引用均保持有效。 (source)