PHP優先佇列

wangbjun發表於2018-11-28

PHP優先佇列

1.什麼是優先佇列?

佇列大家應該都很熟悉,專業的說佇列是一種特殊的線性表,簡單的說就是先進先出(FIFO),與佇列相反的還有一種資料結構叫作棧,先進後出(FILO),這裡的棧和記憶體裡面的棧沒啥關係,不要理解錯了!

佇列在開發的應用挺多的,最廣泛的就是訊息佇列,用來處理一些任務比如下單,搶購,需要按請求的時間排序,先來的先處理,關鍵是保持一種順序結構。實際開發中,我們一般很少自己去實現佇列,通常都是使用一些現成的服務,比如redis queue,rabbitmq。

優先佇列(Priprity Queue),顧名思義,就是帶有優先順序的佇列,也就是說不是按請求的順序排序,而且根據某一些規則屬性。舉個例子:有一些12306的刷票軟體,花錢買了加速包搶到票的機率更高。這裡所謂機率更高換個說法就是優先順序更高,如果只有10張票,肯定是先讓那些花了錢的先搶到票,沒花錢的話排後面。

2.為什麼需要優先佇列?

假設現在有10000個人搶票,其中有50個人交了數目不一的錢,當系統搶到一張票後需要按照這些使用者交錢的數目從大到小排序依次分配。如果讓你去實現上面所說的搶票優先順序,你會怎麼設計呢?

做法一:

如果這些使用者資訊是儲存到資料庫裡面,當每次搶到一張票的時候,使用sql語句排序取出符合條件的使用者裡面交錢最多的那位就行了。如果不是儲存到資料庫裡面的,可能就需要在記憶體裡面排序了,1萬個使用者資訊雖然不多,但是你每次都需要重新排序。

做法二:

使用redis sorted set 實現,Redis 有序集合和集合一樣也是string型別元素的集合,且不允許重複的成員。不同的是每個元素都會關聯一個double型別的分數,redis正是通過分數來為集合中的成員進行從小到大的排序,有序集合的成員是唯一的,但分數(score)卻可以重複。

sorted set 操作
ZADD:向 sorted set 中新增元素
ZCOUNT: sorted set 中 score 等於指定值的元素有多少個
ZSCORE:sorted set 中指定元素的 score 是多少
ZCARD: sorted set 中總共有多少個元素
ZREM:刪除 sorted set 中的指定元素
ZREVRANGE:按照從大到小的順序返回指定索引區間內的元素
ZRANGE: 按照從小到大的順序返回指定索引區間內的元素
複製程式碼

值得一說的是,這個並不是併發安全的,因為取優先順序最高的元素以及刪除這個元素是兩次操作,不是原子性的,不過可以使用lua指令碼解決這個問題。

做法三:

使用優先佇列,大部分程式語言的標準庫裡面都自帶優先佇列實現,並不需要自己去實現,不過像PHP這樣的Web程式每次請求結束後記憶體資料都會被銷燬,使用自己構建的優先佇列還不如第二種做法好使,或者實現一個常駐程式的服務供Web呼叫。

3.原理和使用

優先佇列是基於二叉堆的,構建一個優先佇列實際上就是在構建一個二叉堆,二叉堆是一種特殊的堆,二叉堆是完全二元樹(二叉樹)或者是近似完全二元樹(二叉樹)。

二叉堆有兩種:最大堆和最小堆。最大堆:父結點的鍵值總是大於或等於任何一個子節點的鍵值;最小堆:父結點的鍵值總是小於或等於任何一個子節點的鍵值。

二叉樹是每個結點最多有兩個子樹的樹結構。

樹是一種非線性的資料結構,是由n(n >=0)個結點組成的有限集合。

以上內容僅供參考,關於這些資料結構的實現和演算法細節這裡不說了,畢竟不簡單,感興趣的話可以詳細瞭解一下。

這些演算法雖然不簡單,但是畢竟我們都是站在巨人的肩膀上,下面看一下在PHP SPL裡面提供的優先佇列實現。PHP的標準庫裡面提供了常用的資料結構,比如連結串列,堆,棧,最大堆,最小堆,固定大小陣列,其中就有優先佇列,其類摘要如下:

SplPriorityQueue implements Iterator , Countable {
    /* 方法 */
    public __construct ( void )
    public int compare ( mixed $priority1 , mixed $priority2 )
    public int count ( void )
    public mixed current ( void )
    public mixed extract ( void )
    public int getExtractFlags ( void )
    public void insert ( mixed $value , mixed $priority )
    public bool isCorrupted ( void )
    public bool isEmpty ( void )
    public mixed key ( void )
    public void next ( void )
    public void recoverFromCorruption ( void )
    public void rewind ( void )
    public void setExtractFlags ( int $flags )
    public mixed top ( void )
    public bool valid ( void )
}
複製程式碼

其中常用的是compare,count,current,insert,next,rewind,valid等方法,用法也相對簡單,下面看一個完整的例子:

<?php
$queue = new SplPriorityQueue();

$queue->insert("A", 2);
$queue->insert("B", 17);
$queue->insert("C", 4);
$queue->insert("D", 10);
$queue->insert("E", 1);

//獲取優先順序最高的元素
echo $queue->top()."\n";

//按照優先順序從大到小遍歷所有元素
while ($queue->valid()) {
    echo $queue->current()."\n";
    $queue->next();
}
複製程式碼

預設情況下,這個是按照數值大小排序的,但是如果排序比較的屬性的並不是一個數值怎麼辦呢?比如說是物件,這時候可以採用下面的寫法,我們可以新建一個類繼承標準庫的類,然後根據自己的規則重寫compare的方法:

<?php
class MyQueue extends SplPriorityQueue
{
    public function compare($priority1, $priority2)
    {
        if ($priority1->age === $priority2->age) {
            return 0;
        }
        return $priority1->age < $priority2->age ? -1 : 1;
    }
}

class Person
{
    public $age;
    public function __construct($age)
    {
        $this->age = $age;
    }
}

$queue = new MyQueue();

$queue->insert("A", new Person(2));
$queue->insert("B", new Person(17));
$queue->insert("C", new Person(4));
$queue->insert("D", new Person(10));
$queue->insert("E", new Person(1));

//獲取優先順序最高的元素
echo $queue->top() . "\n";

//按照優先順序從大到小遍歷所有元素
while ($queue->valid()) {
    echo $queue->current() . "\n";
    $queue->next();
}
複製程式碼

大家看懂了嗎?如果錯誤歡迎指正!

相關文章