我正在使用SplHeap
来保存树的图形节点,该图形节点具有将从叶子遍历到根的有向边。为此,我预先计算节点的“扇入”并将它们放入堆中,这样我就可以随时检索具有最小扇入(0)的节点。
在访问节点之后,我将其后续节点的扇入减少1.显然,需要重新计算堆,因为后续节点现在位于错误的位置。我已经尝试了recoverFromCorruption()
,但它没有做任何事情并使堆保持错误的顺序(较大fanIn
的节点停留在较小的fanIn
前面。
作为一种解决方法,我现在在每次访问后创建一个新堆,每次都达到一个完整的O(N * log(N))排序。
然而,应该可以对更改的堆条目进行堆上操作,直到它在O(log(N))中的正确位置。
SplHeap
的API未提及上堆(或删除任意元素 - 然后可以重新添加)。我可以以某种方式从SplHeap
派生一个类来执行此操作,还是必须从头开始创建纯PHP堆?
编辑:代码示例:
class VoteGraph {
private $nodes = array();
private function calculateFanIn() { /* ... */ }
// ...
private function calculateWeights() {
$this->calculateFanIn();
$fnodes = new GraphNodeHeap(); // heap by fan-in ascending (leaves are first)
foreach($this->nodes as $n) {
// omitted: filter loops
$fnodes->insert($n);
}
// traversal from leaves to root
while($fnodes->valid()) {
$node = $fnodes->extract(); // fetch a leaf from the heap
$successor = $this->nodes[$node->successor];
// omitted: actual job of traversal
$successor->fanIn--; // will need to fix heap (sift up successor) because of this
//$fnodes->recoverFromCorruption(); // doesn't work for what I want
// workaround: rebuild $fnodes from scratch
$fixedHeap = new GraphNodeHeap();
foreach($fnodes as $e)
$fixedHeap->insert($e);
$fnodes = $fixedHeap;
}
}
}
class GraphNodeHeap extends SplHeap {
public function compare($a, $b) {
if($a->fanIn === $b->fanIn)
return 0;
else
return $a->fanIn < $b->fanIn ? 1 : -1;
}
}
完整代码也可用:https://github.com/md2k7/civicracy/blob/master/civi-php/protected/components/VoteGraph.php#L73
编辑2 :
$this->putNode(new GraphNode(4));
$this->putNode(new GraphNode(1, 2));
$this->putNode(new GraphNode(3, 2));
$this->putNode(new GraphNode(2, 4));
这意味着用户1 和用户3 投票用户2 ,用户2 投票赞成用户4 ,传递3票(2票+他/她自己)。这称为委托投票:我的算法传递“从底部”(叶子)的投票,我已经知道每个用户有多少重量(责任/表示/你喜欢它......)。
答案 0 :(得分:1)
更改值后,您不能只重新插入更新的节点吗?
$successor->fanIn--;
$fnodes->insert($updatedNode);
不插入力重新排序堆?这将是一个较低的顺序,然后创建一个新的。
答案 1 :(得分:1)
我最近解决了非常类似的问题,似乎SPL不支持更新。所以
我必须写自己的堆。
它不是非常有效,但它可以满足我的需求,而且比重复排序数组要快得多......虽然SPL堆仍然快得多......
这里是......
class heap
{
public $members=array();
// these two are just for statistics
private $swaps=0;
private $recurs=array('lups'=>0, 'ldowns'=>0);
public function insert($val){
if(is_array($val) && empty($this->members)){ // because heapify is (in theory) more efficient
foreach($val as $v){
$this->members[]=$v;
}
$this->heapify();
}else{
$emptyPosition=count($this->members); // count(members) gets index of first empty position, not last key
$this->members[]=$val; // puts $val in
$this->ladderup($emptyPosition);
}
}
public function heapify(){
/* in case all the heap is broken, we can always use this to repair it.
It should be more efficient to fill $members randomly and "repair" it with heapify after,
than inserting things one by one*/
$start=max(0, floor( (count($this->members)-1)/2)); // find last parent
for($i=$start;$i>=0;$i--){
$this->ladderdown($i);
}
}
private function ladderdown($index){
// recursively sifts down $index
$this->recurs['ldowns']++;
/*
indexes of children
they are stored at parent_position*2 and parent_position*2+1
but becouse php uses null-based array indexing, we have to modify it a little
*/
$iA=$index*2+1;
$iB=$index*2+2;
if($iA<count($this->members)){ // check if children exist
if($iB<count($this->members)){
if($this->compare($iA, $iB)>=0) $bigger=$iA; // if both exist, compare them, cause we want to swap with the bigger one ; I'm using ">=" here, that means if they're equal, left child is used
else $bigger=$iB;
}else{
$bigger=$iA; // if only one children exists, use it
}
if($this->compare($bigger, $index)>0){ // not using ">=" here, there's no reason to swap them if they're same
$this->swap($bigger, $index);
$this->ladderdown($bigger); // continue with $bigger because that's the position, where the bigger member was before we swap()ped it
}
}
}
private function ladderup($index){
// sift-up,
$this->recurs['lups']++;
$parent=max(0, floor( ($index-1)/2)); // find parent index; this way it actualy swaps one too many times: at the end of sift-up-ing swaps the root with itself
if($this->compare($index, $parent)>0){
$this->swap($index, $parent);
$this->ladderup($parent);
}
}
public function root(){
if(count($this->members)){
return $this->members[0];
}
return false;
}
public function extract(){
// removes and returns root member
if(!count($this->members)) return false;
$this->swap(0,count($this->members)-1); // swaps root with last member
$result=array_pop($this->members); // removes last member (now root)
$this->ladderdown(0); // root is on wrong position, sifts it down
return $result;
}
public function update($index, $value){
if($index<count($this->members)){
$this->members[$index]=$value;
$this->ladderup($index);
$this->ladderdown($index);
}
}
public function delete($index){
// removes index from heap the same way as root is extracted
$this->swap(count($this->members)-1, $index); // swaps index with last one
array_pop($this->members);
$this->ladderup($index);
$this->ladderdown($index);
}
private function swap($iA, $iB){
// swaps two members
$this->swaps++;
$swap=$this->members[$iA];
$this->members[$iA]=$this->members[$iB];
$this->members[$iB]=$swap;
}
private function compare($iA, $iB){
$result=$this->members[$iA] - $this->members[$iB];
return $result;
}
public function stats($text=""){
// prints and resets statistics
echo "STATS: $text... Sift-ups: ".$this->recurs['lups']." Sift-downs: ".$this->recurs['ldowns']." Swaps: ".$this->swaps." <br>";
$this->recurs=array('lups'=>0, 'ldowns'=>0);
$this->swaps=0;
}
}
//here's how to use it...
$h=new heap;
for($i=0; $i<10000; $i++){
$h->insert(rand(1,1000));
}
$h->stats("after inserting one-by-one");
while($biggest=$h->extract()); // note that $h->extract might return FALSE, but might return zero as well, if there was zero in the heap
$h->stats("after extracting all roots (like in heapsort)");
echo "Now, heap is empty. Let's try whole array at once <br>";
for($i=0; $i<10000; $i++){
$a[]=rand(1,1000);
}
$h->insert($a); // inserting whole array here, so heap will use more efficient heapify()
$h->stats("after heapify");
echo "let's update two indexes<br>";
$h->update(1234,44444);// sure on top
$h->stats("after update");
$h->update(8888,40000);// second place
$h->stats("after update");
echo "extract biggest three indexes<br>";
echo $h->extract()." - this should be 44444<br>";
echo $h->extract()." - this should be 40000<br>";
echo $h->extract()." - this should be biggest number given by rand(1,1000)<br>";
$h->stats("after three extracts");
while($h->extract());
$h->stats("after extracting the rest");
结果是:
STATS:在逐个插入之后... Sift-ups:22651 Sift-downs:0 Swaps:12651
年份:提取所有根(如在heapsort中)... Sift-ups: 0 Sift-downs:116737掉期:116737
现在,堆是空的。让我们一次尝试整个数组
STATS:堆积后...筛选:0筛选:12396交换:7396
让我们更新两个索引
STATS:更新后...筛选-ups:11 Sift-downs:1掉期:10年STATS:更新后... Sift-ups:13 Sift-downs:1掉期:12
提取最大三个指数
44444 - 这应该是44444
40000 - 这应该是40000
1000 - 这应该是rand给出的最大数字(1,1000)
STATS:经过三次提取...... Sift-ups:0筛选:42次掉期:42支持统计数据:提取其余数据后...筛选:0分解:116652掉期:116652
你必须稍微修改它,但无论如何,希望它有所帮助..