9

を使用してSplHeap、葉から根にトラバースされる有向エッジを持つツリーのグラフノードを保持しています。このために、ノードの「ファンイン」を事前に計算してヒープに配置し、ファンイン(0)が最小のノードを常に取得できるようにします。

ノードにアクセスした後、後継のファンインを1つ減らします。その後、後継が間違った場所にあるため、明らかにヒープを再計算する必要があります。私は試しrecoverFromCorruption()ましたが、何もせず、ヒープを間違った順序に保ちます(大きいノードfanInは小さいノードの前に留まりますfanIn)。

回避策として、訪問するたびに新しいヒープを作成し、毎回完全なO(N * log(N))ソートになります。

ただし、O(log(N))の正しい位置になるまで、変更されたヒープエントリに対してヒープ操作を行うことは可能です。

のAPISplHeapは、アップヒープ(または任意の要素の削除-その後、再追加される可能性があります)については言及していません。これを行うために、どういうわけかクラスを派生させることができますか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票+自分自身)を渡すことを意味します。これは委任投票と呼ばれます。私のアルゴリズムは、各ユーザーの重み(責任/表現/好きなように...)がすでにわかっている「下から」(葉)に投票を渡します。

4

2 に答える 2

1

値を変更した後、更新されたノードを再挿入することはできませんか?

$successor->fanIn--;
$fnodes->insert($updatedNode);

ヒープの再ソートを強制的に挿入しませんか? それは、新しいものを作成するよりも低い順序になります。

于 2012-11-13T16:56:24.173 に答える
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");

結果は次のとおりです。

統計: 1 つずつ挿入した後... シフトアップ: 22651 シフトダウン: 0 スワップ: 12651 統計
: すべてのルートを抽出した後 (ヒープソートのように)... シフトアップ: 0 シフトダウン: 116737 スワップ: 116737
現在、ヒープは空です。一度に配列全体を試して
みましょ
う統計: 更新後... シフトアップ: 13 シフトダウン: 1 スワップ: 12最大の 3 つのインデックスを抽出44444 - これは 44444 である必要があります 40000 - これは 40000 である必要があります) STATS: 3 回の抽出後... シフトアップ: 0 シフトダウン: 42 スワップ: 42







STATS: 残りを抽出した後... シフトアップ: 0 シフトダウン: 116652 スワップ: 116652

少し変更する必要がありますが、とにかく、それが役立つことを願っています..

于 2012-11-18T22:18:26.783 に答える