什麼是佇列
佇列是另外一種遵循先進先出原則的線性資料結構。佇列有兩端可供操作,一端出隊,一端入隊。這個特點和棧不同,棧只有一端可以用來操作。入隊總是在後端,出隊在前端。
常見操作
- enqueue -> 入隊
- dequeue -> 出隊
- peek -> 返回佇列前端元素
- isEmpty -> 是否為空
PHP實現
首先我們定義一個QueueInterface。
interface QueueInterface
{
public function enqueue(string $item);
public function dequeue();
public function isEmpty();
public function peek();
}
複製程式碼
來看基於陣列的佇列實現
class ArrQueue implements QueueInterface
{
private $queue;
private $limit;
public function __construct(int $limit = 0)
{
$this->limit = $limit;
$this->queue = [];
}
public function isEmpty()
{
return empty($this->queue);
}
public function dequeue()
{
if ($this->isEmpty()) {
throw new \UnderflowException('queue is empty');
} else {
array_shift($this->queue);
}
}
public function enqueue(string $item)
{
if (count($this->queue) >= $this->limit) {
throw new \OverflowException('queue is full');
} else {
array_unshift($this->queue, $item);
}
}
public function peek()
{
return current($this->queue);
}
}
複製程式碼
得益於PHP強大的array結構,我們輕而易舉的寫出來了佇列的基本操作方法。果然世界上最好的語言名不虛傳。
可能機智的同學已經猜到了,我之前已經定義了一個佇列介面,那佇列的實現肯定不止只有上面一種哈。來看基於連結串列的實現。
class LinkedListQueue implements QueueInterface
{
private $limit;
private $queue;
public function __construct(int $limit = 0)
{
$this->limit = $limit;
$this->queue = new LinkedList();
}
public function isEmpty()
{
return $this->queue->getSize() == 0;
}
public function peek()
{
return $this->queue->getNthNode(0)->data;
}
public function enqueue(string $item)
{
if ($this->queue->getSize() < $this->limit) {
$this->queue->insert($item);
} else {
throw new \OverflowException('queue is full');
}
}
public function dequeue()
{
if ($this->isEmpty()) {
throw new \UnderflowException('queue is empty');
} else {
$lastItem = $this->peek();
$this->queue->deleteFirst();
return $lastItem;
}
}
}
複製程式碼
裡面涉及到了之前的連結串列實現,不瞭解細節的同學可以去這裡看看。
Spl中的佇列
強大的PHP已經內建了佇列實現,可以使用的方法和上面我們自己實現的類似。
class SqlQueue
{
private $splQueue;
public function __construct()
{
$this->splQueue = new \SplQueue();
}
public function enqueue(string $data = null)
{
$this->splQueue->enqueue($data);
}
public function dequeue()
{
return $this->splQueue->dequeue();
}
}
複製程式碼
優先佇列
優先佇列是一種特殊的佇列,入隊或者出隊的順序都是基於每個節點的權重。
順序序列
優先佇列可以分為有序優先佇列和無序優先佇列。有序優先佇列又有兩種情況,倒序或者順序。在順序序列中,我們可以迅速的找出最大或者最小優先順序別的節點,複雜度是O(1)。但是插入的話會花費掉更多的時間,因為我們要檢查每一個節點的優先順序別然後插入到合適的位置。
無序序列
在無序序列中,在插入新節點的時候我們不需要根據他們的優先順序確定位置。入隊的時候像普通佇列一樣,插入到佇列的末尾。但是當我們想移除優先順序最高的元素的時候,我們要掃描每一個節點來確定移除優先順序最高的那一個。接下來我們還是基於連結串列實現一個順序的優先佇列。
class LinkedListPriorityQueue
{
private $limit;
private $queue;
public function __construct(int $limit = 0)
{
$this->limit = $limit;
$this->queue = new LinkedList();
}
public function enqueue(string $data = null, int $priority)
{
if ($this->queue->getSize() > $this->limit) {
throw new \OverflowException('Queue is full');
} else {
$this->queue->insert($data, $priority);
}
}
public function dequeue(): string
{
if ($this->isEmpty()) {
throw new \UnderflowException('Queue is empty');
} else {
$item = $this->peek();
$this->queue->deleteFirst();
return $item->data;
}
}
public function peek()
{
return $this->queue->getNthNode(0);
}
public function isEmpty()
{
return $this->queue->getSize() === 0;
}
}
複製程式碼
環形佇列
為充分利用向量空間,克服"假溢位"現象的方法是:將向量空間想象為一個首尾相接的圓環,並稱這種向量為迴圈向量。儲存在其中的佇列稱為迴圈佇列。環形佇列也是一種陣列,只是它在邏輯上把陣列的頭和尾相連,形成迴圈佇列,當陣列尾滿的時候,要判斷陣列頭是否為空,不為空繼續存放資料。
class CircularQueue implements QueueInterface
{
private $queue;
private $limit;
private $front = 0;
private $rear = 0;
public function __construct(int $limit = 0)
{
$this->limit = $limit;
$this->queue = [];
}
public function isEmpty()
{
return $this->front === $this->rear;
}
public function isFull()
{
$diff = $this->rear - $this->front;
if ($diff == -1 || $diff == ($this->limit - 1)) {
return true;
}
return false;
}
public function peek()
{
return $this->queue[$this->front];
}
public function dequeue()
{
if ($this->isEmpty()) {
throw new \UnderflowException('Queue is empty');
}
$item = $this->queue[$this->front];
$this->queue[$this->front] = null;
$this->front = ($this->front + 1) % $this->limit;
return $item;
}
public function enqueue(string $item)
{
if ($this->isFull()) {
throw new \OverflowException('Queue is full');
}
$this->queue[$this->rear] = $item;
$this->rear = ($this->rear + 1) % $this->limit;
}
}
複製程式碼
雙端佇列
截止目前我們所實現的佇列都是在隊尾(rear)入隊,隊首(front) 出隊的結構。在特殊的情況下,我們希望不論是隊首還是隊尾都可以入隊或者出隊,這種結構就叫做雙端佇列。基於我們之前實現的連結串列結構,我們可以輕而易舉的實現這樣的結構。
class LinkedListDeQueue
{
private $limit = 0;
private $queue;
public function __construct(int $limit = 0)
{
$this->limit = $limit;
$this->queue = new \DataStructure\LinkedList\LinkedList();
}
public function dequeueFromFront(): string
{
if ($this->isEmpty()) {
throw new \UnderflowException('Queue is empty');
}
$item = $this->queue->getNthNode(0);
$this->queue->deleteFirst();
return $item->data;
}
public function dequeueFromBack(): string
{
if ($this->isEmpty()) {
throw new \UnderflowException('Queue is empty');
}
$item = $this->queue->getNthNode($this->queue->getSize() - 1);
$this->queue->deleteLast();
return $item->data;
}
public function isFull()
{
return $this->queue->getSize() >= $this->limit;
}
public function enqueueAtBack(string $data = null)
{
if ($this->isFull()) {
throw new \OverflowException('queue is full');
}
$this->queue->insert($data);
}
public function enqueueAtFront(string $data = null)
{
if ($this->isFull()) {
throw new \OverflowException('queue is full');
}
$this->queue->insertAtFirst($data);
}
public function isEmpty()
{
return $this->queue->getSize() === 0;
}
public function peekFront()
{
return $this->queue->getNthNode(0)->data;
}
public function peekBack()
{
return $this->queue->getNthNode($this->queue->getSize() - 1)->data;
}
}
複製程式碼
裡面涉及到了之前的連結串列實現,不瞭解細節的同學可以去這裡看看。
總結
棧和佇列是我們最常用的資料結構,我們會在其他的複雜資料結構中看到這兩種抽象資料型別的應用。在下一章,我們繼續學習PHP資料結構之遞迴,這是一種將複雜問題簡單化的常用思路。
專題系列
PHP基礎資料結構專題系列目錄地址:github.com/... 主要使用PHP語法總結基礎的資料結構和演算法。還有我們日常PHP開發中容易忽略的基礎知識和現代PHP開發中關於規範、部署、優化的一些實戰性建議,同時還有對Javascript語言特點的深入研究。