我正在使用优先级队列作为具有一个额外要求的调度程序。我需要能够取消预定的项目。这相当于从优先级队列的中间删除一个项目。
我无法使用std::priority_queue
访问除top之外的任何元素都受到保护。
我正在尝试使用algorithm
的堆函数。但我仍然错过了我需要的那件作品。当我从堆中间删除一个元素时,我希望它能够有效地重建它自己。 C ++提供了这些堆函数:
std::make_heap
O(3n) std::push_heap
O(lg(n)) std::pop_heap
O(2 lg(n)) 我想要一个像std::repair_heap
这样的新函数,其中包含一个大的O< 3N 。我会为它提供已取消项目所在的洞位置,并且可以正确调整堆。
不提供std::repair_heap
功能似乎是一个巨大的疏忽。我错过了一些明显的东西吗?
是否有库提供符合stl标准的std::repair_heap
?
是否有更好的数据结构来建模调度程序?
注意:
我出于几个原因没有使用std::map
。
答案 0 :(得分:6)
我猜你知道要删除的堆容器(索引n)中的哪个元素。
v[n] = BIG;
,值BIG
确实比堆中的任何其他值大。std::push_heap( v.begin(), v.begin()+n+1 );
std::pop_heap( v.begin(), v.end() );
v.pop_back();
操作是O(ln(n))
RE:要求提供证明
首先,限定符:
该方法假设std push_heap使用的算法。
具体来说,它假设std push_heap(v.begin(),v.begin()+ n + 1)
只会改变范围[0,n]
对于那些作为n的后代的元素,即以下集合中的索引:
A(n)={n,(n-1)/2,((n-1)/2-1)/2....0}.
以下是std push_heap的典型规范:
http://www.cplusplus.com/reference/algorithm/push_heap/ “给定堆范围[first,last-1],此函数通过将(last-1)中的值放入其中的相应位置,将被视为堆的范围扩展为[first,last]。”
它是否保证使用您在教科书中阅读的“常规堆算法”? 你告诉我。
无论如何,这里是你可以运行的代码,从经验上看它是有效的。 我正在使用VC 2005.
#include <algorithm>
#include <vector>
#include <iostream>
bool is_heap_valid(const std::vector<int> &vin)
{
std::vector<int> v = vin;
std::make_heap(v.begin(), v.end());
return std::equal(vin.begin(), vin.end(), v.begin());
}
int _tmain(int argc, _TCHAR* argv[])
{
srand(0);
std::vector<int> v;
for (int i=0; i<100; i++)
{
v.push_back( rand() % 0x7fff );
}
std::make_heap(v.begin(), v.end());
bool bfail = false;
while( v.size() >= 2)
{
int n = v.size()/2;
v[n] = 0x7fffffff;
std::push_heap(v.begin(), v.begin()+n+1);
std::pop_heap(v.begin(), v.end());
v.resize(v.size()-1);
if (!is_heap_valid(v))
{
std::cout << "heap is not valid" << std::endl;
bfail = true;
break;
}
}
if (!bfail)
std::cout << "success" << std::endl;
return 0;
}
但我有另一个问题,就是如何知道需要删除的索引“n”。在使用std push_heap和std pop_heap时,我无法看到如何跟踪它(知道堆中的位置)。我认为你需要编写自己的堆代码,并在每次在堆中移动对象时将堆中的索引写入对象。叹息。
答案 1 :(得分:4)
不幸的是,标准缺少这个(相当重要的)功能。使用g ++,你可以使用非标准函数std::__adjust_heap
来实现这一点,但是没有简单易用的方法 - __adjust_heap
在不同版本的g ++中略有不同,所以你可以'甚至可以在g ++版本上轻松实现。
答案 2 :(得分:3)
您的repair_heap()
如何运作?这是我的猜测:
如果你的堆由一些迭代器范围定义,比如说(heapBegin,heapEnd)。要删除的元素是堆的某个子树的根,它由某个子范围(subHeapBegin,subHeapEnd)定义。使用std::pop_heap(subHeapBegin, subHeapEnd)
,然后使用subHeapEnd != heapEnd
,交换*(subHeapEnd-1)
和*(heapEnd-1)
处的值,这些值应将已删除的项目放在堆容器的末尾。现在你必须在你的子空间中在*(subHeapEnd-1)处向上渗透元素。如果我没有错过任何可能的东西,那么剩下的就是将末端元素从堆容器中切掉。
在尝试正确编码之前遇到麻烦(我已经跳过了计算subHeapBegin和subHeapEnd之类的一些细节),我会运行一些测试来确定make_heap()
是否真的让你失望。 Big-O很有用,但它与实际执行时间不同。
答案 3 :(得分:0)
在我看来,从堆中间删除可能意味着必须重建整个堆:没有repair_heap的原因是因为它必须像{{1}那样做(大哦)工作}。
你是否可以做一些事情,比如把make_heap
放在堆中,然后让项目无效而不是删除它们?然后,当他们最终到达顶部时,忽略该项目并继续前进。
答案 4 :(得分:0)
这是我用来从堆中删除项目的一些delphi代码。我不知道你说的C ++,也没有修复功能,但是嘿..
首先是流行音乐,这样你就可以了解它是如何工作的:
function THeap.Pop: HeapItem;
begin
if fNextIndex > 1 then begin
Dec(fNextIndex);
Result:= fBuckets[1]; //no zero element
fBuckets[1] := fBuckets[fNextIndex];
fBuckets[fNextIndex] := nil;
FixHeapDown; //this has a param defaulting to
end
else
Result:= nil;
end;
现在要对比一下,删除:
procedure THeap.Delete(Item: HeapItem);
var
i:integer;
begin
for i:=1 to pred(fNextIndex) do
if Item=fBuckets[i] then begin
dec(fNextIndex);
fBuckets[i] := fBuckets[fNextIndex];
fBuckets[fNextIndex] := nil;
FixHeapDown(i);
break;
end;
end;
它当然是一个不禁的想法 做我们在这里做的事情,但嘿,费用 有时会改变,工作也会被取消。
享受。 我希望这会有所帮助。
答案 5 :(得分:0)
您可以尝试将“ std :: multiset”实现为堆结构并支持“ std :: erase”操作,因此可以“ std :: find”该元素然后将其擦除。