Javascript資料結構與演算法--佇列(順序佇列、優先佇列、迴圈佇列)的實現與用法
前言
佇列和棧非常類似,前面已經講過了棧的實現與用法,現在我們來說說佇列。
佇列介紹
佇列遵循FIFO(First In First Out,先進先出)原則的一組有序的項。
佇列是一種特殊的線性表,特殊之處在於它只允許在表的前端(front)進行刪除操作,而在表的後端(rear)進行插入操作,和棧一樣,佇列是一種操作受限制的線性表。進行插入操作的端稱為隊尾,進行刪除操作的端稱為隊頭。
佇列有順序佇列,還有其他修改版本的佇列,比如:優先佇列、迴圈佇列。
順序佇列
順序佇列是佇列的順序儲存結構,它是運算受限制的順序表(線性表)。建立順序佇列結構必須為其靜態分配或動態申請一片連續的儲存空間,並設定兩個指標進行管理。一個是隊頭指標front,它指向隊頭元素;另一個是隊尾指標rear,它指向下一個入隊元素的儲存位置,如圖所示。
生活中,各種排隊現象。例如:排隊買票,新來的人,排在隊尾,相當於新增操作。隊頭的人先買票,買完票離開佇列,相當於刪除操作。而新增的操作卻只能在佇列的尾部進行,因此新來的人就只能排在佇列的最後。
建立佇列
佇列中的元素,可以使用連結串列儲存,也可以使用陣列儲存。我們使用陣列來儲存佇列中的元素。
/** * 佇列 * 我們使用陣列來儲存佇列中的元素 * *=====佇列的入隊、出隊示意圖======== * * 出隊 ----------------- 入隊 * <--- A1,A2,A3,...,An <--- * ----------------- * *================================ */ export default class Queue { constructor() { this.items = []; } /** * 向隊尾新增一個(或多個)新的元素 * @param {*} element 新元素 */ enqueue(element) { this.items.push(element) } /** * 移除佇列的第一(即排在佇列最前面的)項,並返回被移除的元素 */ dequeue() { // 根據佇列的先進先出原則,使用shift方法 // shift方法會從陣列中移除儲存在索引為0的元素 return this.items.shift() } /** * 返回佇列中的第一個元素--最先被新增,也將是最先被移除的元素。 * 佇列不做任何變動(不移除元素,只返回元素資訊) */ front() { return this.items[0] } /** * 清除佇列中的所有元素 */ clear() { this.items = [] } /** * 如果佇列中不包含任何元素,返回true,否則返回false */ isEmpty() { return this.items.length === 0 } /** * 返回佇列包含的元素個數,與陣列length屬性類似 */ size() { return this.items.length } /** * 佇列內容字串化 */ toString() { return this.items.toString() } }
順序佇列應用
印表機,有一個列印佇列,誰先進入佇列,誰就先列印。
/** * 列印佇列的內容 * @param {Array} queueArr 需要列印的陣列 */ function print(queueArr) { let queue = new Queue() let s = '' /** * 檢查引數型別是否為陣列 */ if (queueArr instanceof Array) { /** * 將需要列印內容新增到佇列 */ for (let i = 0; i < queueArr.length; i++) { queue.enqueue(queueArr[i]) } /** * 將隊頭的資料取出 */ while (!queue.isEmpty()) { s += queue.dequeue() + ', ' } s = s.substr(0, s.length - 2) } return s; }
優先佇列
佇列有順序佇列,還有其他修改版本的佇列,比如:迴圈佇列、優先佇列
優先佇列是順序佇列的修改版本,元素的新增和移除是基於優先順序的。一個現例項子是,在銀行排隊辦業務的順序。VIP客戶的優先順序要高於普通客戶的。另一個例子是醫院的急診科候診室。醫生會優先處理病情比較嚴重的患者。通常,護士會鑑別分類,根據患者病情的嚴重程度放號。
實現一個優先佇列,有兩種選項:
設定優先順序,然後在正確的位置新增元素。(優先新增,正常出隊)
用入列操作新增元素,然後按照優先順序移除它們。(正常新增,優先出隊)
優先佇列的實現
我們在這裡將會使用第一種方式,在正確的位置新增元素,因此可以對它們使用預設的出列操作。
queueElement.js 檔案
// queueElement.js
/**
* 優先佇列中的元素,包含元素和優先順序
*/
export default class QueueElement {
/**
*
* @param {*} element 佇列的元素
* @param {*} priority 優先順序
*/
constructor(element, priority) {
this.element = element
this.priority = priority
}
}
priorityQueue.js檔案
// priorityQueue.js
import QueueElement from "./queueElement";
/**
* 最小優先佇列
*/
export default class PriorityQueue {
constructor() {
this.items = []
}
/**
* 在正確的位置新增元素
* @param {*} element 要新增的元素
* @param {Int32Array} priority 優先順序
*/
enqueue(element, priority) {
let queueElement = new QueueElement(element, priority)
let added = false
for (let i = 0; i < this.items.length; i++) {
// 當找到一個比要新增的元素的優先順序更高的項時,將新元素插入到它之前。
if (queueElement.priority < this.items[i].priority) {
this.items.splice(i, 0, queueElement) // 插入新元素
added = true
break // 終止佇列迴圈
}
}
// 當需要新增的元素優先順序大於佇列中的任何一個元素的時候,把該元素新增到隊尾。
if (!added) {
this.items.push(queueElement)
}
}
/**
* 列印佇列中的元素(包含優先順序)
*/
print() {
for (let i = 0; i < this.items.length; i++) {
console.log(`${i + 1} - ${this.items[i].element} - ${this.items[i].priority}`)
}
}
/**
* 移除佇列的第一(即排在佇列最前面的)項,並返回被移除的元素
*/
dequeue() {
// 根據佇列的先進先出原則,使用shift方法
// shift方法會從陣列中移除儲存在索引為0的元素
return this.items.shift()
}
/**
* 返回佇列中的第一個元素--最先被新增,也將是最先被移除的元素。
* 佇列不做任何變動(不移除元素,只返回元素資訊)
*/
front() {
return this.items[0]
}
/**
* 清除佇列中的所有元素
*/
clear() {
this.items = []
}
/**
* 如果佇列中不包含任何元素,返回true,否則返回false
*/
isEmpty() {
return this.items.length === 0
}
/**
* 返回佇列包含的元素個數,與陣列length屬性類似
*/
size() {
return this.items.length
}
/**
* 佇列內容字串化
*/
toString() {
return this.items.toString()
}
}
優先佇列的應用
let priorityQueue = new PriorityQueue()
priorityQueue.enqueue('wei', 2)
priorityQueue.enqueue('qin', 3)
priorityQueue.enqueue('world', 1)
priorityQueue.enqueue('china', 1)
priorityQueue.print()
輸出結果
1 - wei - 1
2 - world - 1
3 - china - 1
4 - qin - 2
迴圈佇列
迴圈佇列的一個例子就是擊鼓傳花的遊戲。在這個遊戲中,孩子們圍成一個圓圈,把花盡快的傳遞給旁邊的人。某一時刻傳花停止,這個時候花在誰手裡,誰就退出圓圈結束遊戲。重複這個過程,直到只剩一個孩子(勝者)。
另一個類似的案例是,約瑟夫環問題。
下面,我們以擊鼓傳花遊戲來分析。
import Queue from "./queue-array";
export default class CircleQueue {
constructor() {}
hotPotato(nameList, num) {
// 利用順序佇列建立的佇列,來完成迴圈佇列
let queue = new Queue()
for (let i = 0; i < nameList.length; i++) {
queue.enqueue(nameList[i])
}
let eliminated = '';
while (queue.size() > 1) {
// 此過程將佇列變成迴圈佇列
for (let i = 0; i < num; i++) {
queue.enqueue(queue.dequeue());
}
eliminated = queue.dequeue();
console.log(eliminated + '在擊鼓傳花遊戲中被淘汰')
}
// 返回最後勝利者
return queue.dequeue()
}
}
以上所有的佇列,都是在陣列結構的基礎上建立和應用的。
[完]