在上一次的文章中, 我們通過陣列實現了棧, 在講解陣列的過程中, 也提到了一點, 就是棧是線性結構, 相比陣列, 佇列對應的操作是陣列的子集。在今天的文章裡,棧和佇列也是屬於線性結構。
那什麼是佇列呢? 我們一起來看下定義:
一、概念
佇列,又稱為佇列(queue),電腦科學中的一種抽象資料型別,是先進先出(FIFO, First-In-First-Out)的線性表。在具體應用中通常用連結串列或者陣列來實現。佇列只允許在後端(稱為rear)進行插入操作,在前端(稱為front)進行刪除操作。
維基百科
通過定義我們可以知道,先進先出(FIFO)的線性資料結構,通常可以通過陣列或者連結串列進行實現。比如我們在進行買票的時候,優先排隊的人,自然得到有限處理,在我們現實生活中還有很多,如乘公交,結賬,檢票等等。
(圖片來之網路 -- 如有侵權,請聯絡刪除)
大概講了一些佇列的概念和一些現實生活中的一些案例, 我們來看一下佇列在計算機系統中”她”是什麼樣子的吧
(佇列示意圖)
二、程式碼實現
前面說過,實現的方式可以通過連結串列和陣列實現,這次的程式碼我們只是通過陣列進行實現,如果有興趣的同學,可以自行通過連結串列進行一次實現
ArrayStructure.php
<?php
class ArrayStructure
{
// 陣列實際元素
private $size = 0;
// 陣列的容量大小
private $capacity = 0;
// 用於存放資料
private $data = [];
/**
* ArrayStruct constructor.
*
* @param int $capacity 陣列容量大小
*/
public function __construct($capacity = 10)
{
$this->capacity = $capacity;
}
/**
* Notes: 獲取陣列實際元素個數
* Author: PhpStorm
* Date: 2021/1/8 0008
* Time: 15:12
*
* @return int
*/
public function getSize(): int
{
return $this->size;
}
/**
* Notes: 擴容
* User: think abel
* Date: 2021/1/10 0010
* Time: 17:08
*
* @param $factor
*/
protected function resize($factor)
{
$this->capacity = $factor * $this->capacity;
}
/**
* Notes: 獲取陣列的容量
* Author: PhpStorm
* Date: 2021/1/8 0008
* Time: 15:14
*
* @return int
*/
public function getCapacity(): int
{
return $this->capacity;
}
/**
* Notes: 陣列是否為空
* Author: PhpStorm
* Date: 2021/1/8 0008
* Time: 15:16
*
* @return bool
*/
public function isEmpty(): bool
{
return $this->size === 0;
}
/**
* Notes: 往陣列指定位置插入資料
* Author: PhpStorm
* Date: 2021/1/8 0008
* Time: 15:27
*
* @param int $index 需要插入的下標/索引
* @param int $ele 需要插入的元素
*/
public function add(int $index, $ele): void
{
// 如果當前的實際大小 等於 當前容量, 則不可以進行插入
try {
if ($this->size === $this->capacity) {
throw new Exception("新增失敗, Array 已經到達最大容量");
}
if ($index < 0 || $index > $this->size) {
throw new Exception("新增失敗, index require >= 0 and <= " . $this->size);
}
/**
* 從最後一個元素開始進行遍歷, 直到下標 為 當前輸入的 下標, 終止
* 每次將當前遍歷的數值 往後 移動一位
*/
for ($i = $this->size - 1; $i >= $index; $i --) {
$this->data[$i + 1] = $this->data[$i];
}
// 目前當前下標還是存在之前的元素, 因為當前元素已經移動到後面一位, 所以直接覆蓋就好
$this->data[$index] = $ele;
// 維護當前實際大小
$this->size ++;
}
catch (Exception $e) {
echo $e->getMessage();
}
}
/**
* Notes: 向所有元素後新增一個元素
* Author: PhpStorm
* Date: 2021/1/8 0008
* Time: 15:19
*/
public function addLast($element): void
{
$this->add($this->size, $element);
}
/**
* Notes: 向第一位新增一個元素
* Author: PhpStorm
* Date: 2021/1/8 0008
* Time: 15:19
*/
public function addFirst(int $element): void
{
$this->add(0, $element);
}
/**
* Notes: 陣列轉字串
* Author: PhpStorm
* Date: 2021/1/8 0008
* Time: 15:50
*
* @return string
*/
public function toString(): string
{
$str = 'Array: size = ' . $this->size . ',' . 'capacity = ' . $this->capacity . PHP_EOL;
$str .= '[';
for ($i = 0; $i < $this->size; $i ++) {
$str .= $this->data[$i];
if ($i != $this->size - 1) {
$str .= ',';
}
}
$str .= ']';
return $str . PHP_EOL;
}
/**
* Notes: 獲取指定下標位置的元素
* Author: PhpStorm
* Date: 2021/1/8 0008
* Time: 16:13
*
* @param int $index
*
* @return int
*/
public function get($index)
{
try {
if ($index < 0 || $index >= $this->size) {
throw new Exception("獲取失敗, index require >= 0 and < " . $this->size);
}
return $this->data[$index];
}
catch (Exception $e) {
return $e->getMessage();
}
}
/**
* Notes: 獲取最後一個
* User: think abel
* Date: 2021/1/10 0010
* Time: 15:48
*
* @return int
*/
public function getLast()
{
return $this->get($this->size - 1);
}
/**
* Notes: 獲取第一個
* User: think abel
* Date: 2021/1/10 0010
* Time: 15:48
*
* @return int
*/
public function getFirst()
{
return $this->get(0);
}
/**
* Notes: 修改指定下標位置的元素為 ele
* User: think abel
* Date: 2021/1/9 0009
* Time: 12:04
*
* @param int $index
* @param int $ele
*
* @return string
*/
public function set(int $index, int $ele)
{
try {
if ($index < 0 || $index >= $this->size) {
throw new Exception("獲取失敗, index require >= 0 and < " . $this->size);
}
$this->data[$index] = $ele;
}
catch (Exception $e) {
return $e->getMessage();
}
}
/**
* Notes: 刪除指定位置上的元素
* Author: PhpStorm
* Date: 2021/1/8 0008
* Time: 16:19
*
* @param int $index
*
* @return int|string
*/
public function remove(int $index): int
{
try {
if ($index < 0 || $index >= $this->size) {
throw new Exception("移除失敗, index require >= 0 and < " . $this->size);
}
$return = $this->data[$index];
for ($i = $index + 1; $i < $this->size; $i ++) {
$this->data[$i - 1] = $this->data[$i];
}
$this->size --;
return $return;
}
catch (Exception $e) {
return $e->getMessage();
}
}
/**
* Notes: 刪除第一個
* Author: PhpStorm
* Date: 2021/1/8 0008
* Time: 16:39
*
* @return int
*/
public function removeFirst(): int
{
try {
return $this->remove(0);
}
catch (Exception $e) {
return $e->getMessage();
}
}
/**
* Notes: 刪除最後一個
* Author: PhpStorm
* Date: 2021/1/8 0008
* Time: 16:39
*
* @return int
*/
public function removeLast()
{
try {
return $this->remove($this->size - 1);
}
catch (Exception $e) {
return $e->getMessage();
}
}
/**
* Notes: 如果有元素, 就刪除
* Author: PhpStorm
* Date: 2021/1/8 0008
* Time: 16:44
*
* @param int $ele
*
* @return bool
*/
public function removeElement(int $ele): bool
{
$index = $this->find($ele);
if ($index != - 1) {
$this->remove($index);
}
}
/**
* Notes: 是否包含元素
* Author: PhpStorm
* Date: 2021/1/8 0008
* Time: 16:22
*
* @param int $ele
*
* @return bool
*/
public function contains(int $ele): bool
{
for ($i = 0; $i < $this->size; $i ++) {
if ($this->data[$i] == $ele) {
return true;
}
}
return false;
}
/**
* Notes: 獲取當前元素的索引
* Author: PhpStorm
* Date: 2021/1/8 0008
* Time: 16:22
*
* @param int $ele
*
* @return int
*/
public function find(int $ele): int
{
for ($i = 0; $i < $this->size; $i ++) {
if ($this->data[$i] == $ele) {
return $i;
}
}
return - 1;
}
}
Queue.php
<?php
/**
* Created by : PhpStorm
* User: think abel
* Date: 2021/1/10 0010
* Time: 16:36
*/
interface Queue
{
public function enqueue($element);
public function dequeue();
public function getFront();
public function getSize();
public function isEmpty();
}
ArrayQueue.php
<?php
/**
* Created by : PhpStorm
* User: think abel
* Date: 2021/1/10 0010
* Time: 16:38
*/
include "Queue.php";
include "ArrayStructure.php";
class ArrayQueue implements Queue
{
private $array;
public function __construct(int $capacity)
{
$this->array = new ArrayStructure($capacity);
}
/**
* Notes: 入隊
* User: think abel
* Date: 2021/1/10 0010
* Time: 16:42
*
* @param $element
*/
public function enqueue($element)
{
$this->array->addLast($element);
}
/**
* Notes: 出隊
* User: think abel
* Date: 2021/1/10 0010
* Time: 16:42
*
* @return int
*/
public function dequeue()
{
return $this->array->removeFirst();
}
/**
* Notes: 獲取隊首
* User: think abel
* Date: 2021/1/10 0010
* Time: 16:42
*
* @return int
*/
public function getFront()
{
return $this->array->getFirst();
}
/**
* Notes: 獲取實際元素個數
* User: think abel
* Date: 2021/1/10 0010
* Time: 16:42
*
* @return int
*/
public function getSize()
{
return $this->array->getSize();
}
/**
* Notes:獲取佇列的容量
* User: think abel
* Date: 2021/1/10 0010
* Time: 16:42
*
* @return int
*/
public function getCapacity()
{
return $this->array->getCapacity();
}
/**
* Notes: 是否為空
* User: think abel
* Date: 2021/1/10 0010
* Time: 16:43
*
* @return bool
*/
public function isEmpty()
{
return $this->array->isEmpty();
}
public function toString()
{
$str = 'Queue: front [';
for ($i = 0; $i < $this->array->getSize(); $i ++) {
$str .= $this->array->get($i);
if ($i != $this->array->getSize() - 1) {
$str .= ", ";
}
}
$str .= "] tail";
return $str;
}
}
index.php
<?php
include('ArrayQueue.php');
$arrayStack = new ArrayQueue(10);
for ($i = 0; $i < 10; $i ++) {
$arrayStack->enqueue($i);
echo PHP_EOL;
print_r($arrayStack->toString());
}
echo PHP_EOL;
$arrayStack->dequeue();
echo $arrayStack->toString();
echo PHP_EOL;
$front = $arrayStack->getFront();
echo $front;
(佇列出入隊示意圖)
三、簡單複雜度分析****
O: 描述是演算法的執行時間 和 輸入資料之間的關係 — 程式執行時間 和 數資料 成線性關係 O(n)
ArrayQueue
enqueue(e) O(1) // 如果是觸發了擴容, 通過均攤複雜度來分析的話,依然是O(1)
dequeue() O(n) // 為什麼是O(n), 因為出隊一個, 其他的元素要向前移動一位
getFront() O(1)
getSize() O(1)
isEmpty(1) O(1)
四、思考
通過上面的簡單時間複雜度分析, 陣列佇列存在一個問題,相信大家也能發現。出隊複雜度是O(n)。
僅僅一個出隊, 複雜度確實 O(n)的, 就不能像入隊一樣為O(1)嗎?我們帶著這個問題, 思考一下. 能不能讓他不移動, 在每次出隊的時候, 我們維護一下 front 的位置不就變成 O(1) 了嗎。說做就做,先看下流程示意圖
(佇列為空時)
佇列為空時, 隊首 和 隊尾 是在同一個位置, 這也就說明, 如果** front == rear 的時候, 佇列為空**。
(a1 出隊)
佇列中 a1 出隊, 我們進行維護 Front 進行 +1 操作, 這時的 Front 到了a2, 一次類推。
(a13 入隊)
佇列中a13入隊的時候, 我們進行維護Rear, 進行移動, Rear的計算公式是 (隊尾的下標 + 1) % 佇列的長度。這個時候,比如在進來一個元素,佇列長度為13, 目前隊尾的下標為1, 隊首的為2, 我們通過 計算公式 (1 + 1) % 13 = 2。這個時候是不是 隊尾和隊首相等了, 我們前面講到, 隊首 == 隊尾 的時候,代表佇列為空。但是目前是滿佇列了,所以這個時候就要做下處理,如果 (隊尾的下標 + 1) % 佇列的長度 == 隊首,說明滿佇列。** 所以也就是我們 需要有意的浪費一個空間, 也就是說,我們在申請建立佇列的時候,就需要在使用者傳遞的 容量上 進行 + 1.**
這時我們在分析下複雜度
LoopQueue
enqueue(e) O(1) // 如果是觸發了擴容, 通過均攤複雜度來分析的話,依然是O(1)
dequeue() O(1) // 如果是觸發了擴容, 通過均攤複雜度來分析的話,依然是O(1)
getFront() O(1)
getSize() O(1)
isEmpty(1) O(1)
程式碼我就不貼了, 如果有需要的話, 可以訪問一下資料倉儲
gitee.com/thinkAbel/data-structure
排版如果有問題的話, 大家可以關注一下公眾號. 我們一起學習成長.
本作品採用《CC 協議》,轉載必須註明作者和本文連結