PHP
的 SPL
庫內置了 SplPriorityQueue
優先級隊列,是以Heap
堆特性實現的,默認爲MaxHeap
模式,即priority
越大越優先出隊,同時可以通過重寫compare
方法來使用MinHeap
(優先級越低越優先出隊,場景貌似很少吧)。
SplPriorityQueue
堆特性
這裏需要注意並理解:SplPriorityQueue
是以堆
數據結構來實現的,當我們出隊時會拿出堆頂
的元素,此時堆
的特性被破壞,堆
會進行相應的調整至穩定態
(MaxHeap
or MinHeap
),即會將最後
一個元素替換到堆頂
,然後進行穩定態
驗證,不符合堆特性則繼續調整,或者我們就得到了一個穩定態
的堆
,所以當優先級相同,出隊順序並不會按照入隊順序。
源碼示例:
<?php
$splPriorityQueue = new \SplPriorityQueue();
// 設定返回數據的meta信息
// \SplPriorityQueue::EXTR_DATA 默認 只返回數
// \SplPriorityQueue::EXTR_PRIORITY 只返回優先級
// \SplPriorityQueue::EXTR_BOTH 返回數據和優先級
// $splPriorityQueue->setExtractFlags(\SplPriorityQueue::EXTR_DATA);
$splPriorityQueue->insert("task1", 1);
$splPriorityQueue->insert("task2", 1);
$splPriorityQueue->insert("task3", 1);
$splPriorityQueue->insert("task4", 1);
$splPriorityQueue->insert("task5", 1);
echo $splPriorityQueue->extract() . PHP_EOL;
echo $splPriorityQueue->extract() . PHP_EOL;
echo $splPriorityQueue->extract() . PHP_EOL;
echo $splPriorityQueue->extract() . PHP_EOL;
echo $splPriorityQueue->extract() . PHP_EOL;
//執行結果
task1
task5
task4
task3
task2
可以看到,雖然 5 個任務的優先級相同,但隊列並沒有按照入隊順序
返回數據,因爲堆
的特性使然:
1、入隊 task1, task2, task3, task4, task5,因爲優先級相同,所以堆一直處於穩定態。
2、出隊,得 task1,堆先將結構調整爲 task5, task2, task3, task4,已然達到了穩定態。
3、出隊,得 task5,堆先將結構調整爲 task4, task2, task3,已然達到了穩定態。
4、出隊,得 task4,堆先將結構調整爲 task3, task2,已然達到了穩定態。
5、出隊,得 task3,堆先將結構調整爲 task2,已然達到了穩定態。
4、出隊,得 task2。
Iterator, Countable
SplPriorityQueue
實現了 Iterator, Countable
接口,所以我們可以foreach/count
函數操作它,或者使用rewind,valid,current,next/count
方法。
注意,因爲是堆
實現,所以rewind
方法是一個no-op
沒有什作用的操作,因爲頭指針
始終指向堆頂
,即current
始終等於top
,不像List
只是遊走指針,出隊是會刪除堆元素的,extract
= current + next
(current出隊,從堆中刪除)。
<?php
$splPriorityQueue = new \SplPriorityQueue();
$splPriorityQueue->insert("task1", 1);
$splPriorityQueue->insert("task2", 2);
$splPriorityQueue->insert("task3", 1);
$splPriorityQueue->insert("task4", 4);
$splPriorityQueue->insert("task5", 5);
echo "Countable: " . count($splPriorityQueue) . PHP_EOL;
// 迭代的話會刪除隊列元素 current 指針始終指向 top 所以 rewind 沒什麼意義
for ($splPriorityQueue->rewind(); $splPriorityQueue->valid();$splPriorityQueue->next()) {
var_dump($splPriorityQueue->current());
var_dump($splPriorityQueue->count());
$splPriorityQueue->rewind();
}
var_dump("is empty:" . $splPriorityQueue->isEmpty());
Extract出隊
extract 出隊更爲友好,即始終返回優先級最高的元素,優先級相投時會以堆調整的特性返回數據。
<?php
$splPriorityQueue = new \SplPriorityQueue();
// data priority
$splPriorityQueue->insert("task1", 1);
$splPriorityQueue->insert("task2", 2);
$splPriorityQueue->insert("task3", 1);
$splPriorityQueue->insert("task4", 4);
$splPriorityQueue->insert("task5", 5);
echo "Countable: " . count($splPriorityQueue) . PHP_EOL;
while (! $splPriorityQueue->isEmpty()) {
var_dump($splPriorityQueue->extract());
echo $splPriorityQueue->count() . PHP_EOL;
}
自定義優先級處理方式
重寫compare
方法定義自己的優先級處理機制。
<?php
class CustomedSplPriorityQueue extends SplPriorityQueue
{
public function compare($priority1, $priority2): int
{
// return $priority1 - $priority2;//高優先級優先
return $priority2 - $priority1;//低優先級優先
}
}
$splPriorityQueue = new \CustomedSplPriorityQueue();
$splPriorityQueue->setExtractFlags(SplPriorityQueue::EXTR_BOTH);
$splPriorityQueue->insert("task1", 1);
$splPriorityQueue->insert("task2", 2);
$splPriorityQueue->insert("task3", 1);
$splPriorityQueue->insert("task4", 4);
$splPriorityQueue->insert("task5", 5);
echo "Countable: " . count($splPriorityQueue) . PHP_EOL;
while (!$splPriorityQueue->isEmpty()) {
var_dump($splPriorityQueue->extract());
}