前面的兩篇文章分別介紹了List和Stack,下面讓我們一起來學習Queue
佇列的概況
佇列是一種列表,不同的是佇列只能在隊尾插入元素,在隊首刪除元素。佇列用於儲存按順序排列的資料,先進先出,這點和棧不一樣,在棧中,最後入棧的元素反而被優先處理。可以將佇列想象成在銀行前排隊的人群,排在最前面的人第一個辦理業務,新來的人只能在後面排隊,直到輪到他們為止。
佇列是一種先進先出(First-In-First-Out,FIFO)的資料結構。佇列被用在很多地方,比如提交作業系統執行的一系列程式、列印任務池等,一些模擬系統用佇列來模擬銀行或雜貨店裡排隊的顧客。
基礎佇列
佇列的兩種主要操作是:向佇列中插入新元素和刪除佇列中的元素。插入操作也叫做入隊,刪除操作也叫做出隊。入隊操作在隊尾插入新元素,出隊操作刪除隊頭的元素。下圖演示了這兩個操作。
佇列的另外一項重要操作是讀取隊頭的元素。這個操作叫做 peek()
。該操作返回隊頭元素,但不把它從佇列中刪除。除了讀取隊頭元素,我們還想知道佇列中儲存了多少元素,可以使用 length
屬性滿足該需求;要想清空佇列中的所有元素,可以使用 clear()
方法來實現。
使用陣列來實現佇列看起來順理成章。JavaScript 中的陣列具有其他程式語言中沒有的優點,陣列的 push()
方法可以在陣列末尾加入元素,shift()
方法則可刪除陣列的第一個元素。
構建Queue類
class Queue {
constructor() {
this.dataStore = [];
}
enqueue(element) {
this.dataStore.push(element);
}
dequeue() {
return this.dataStore.shift();
}
front() {
return this.dataStore[0];
}
back() {
return this.dataStore[this.dataStore.length - 1];
}
empty() {
return this.dataStore.length === 0;
}
toString() {
return this.dataStore.toString();
}
length() {
return this.dataStore.length;
}
}
優先佇列
在一般情況下,從佇列中刪除的元素,一定是率先入隊的元素。但是也有一些使用佇列的應用,在刪除元素時不必遵守先進先出的約定。這種應用,需要使用一個叫做優先佇列的資料結構來進行模擬。
從優先佇列中刪除元素時,需要考慮優先權的限制。比如醫院急診科的候診室,就是一個採取優先佇列的例子。當病人進入候診室時,分診護士會評估患者病情的嚴重程度,然後給一個優先順序程式碼。高優先順序的患者先於低優先順序的患者就醫,同樣優先順序的患者按照先來先服務的順序就醫。
先來定義儲存佇列元素的物件,然後再構建我們的優先佇列系統:
class Patient {
constructor(name, code) {
this.name = name;
this.code = code;
}
}
變數 code
是一個整數,表示患者的優先順序或病情嚴重程度(code越小代表病情越嚴重)。
下面需要重新定義Queue
類的dequeue
方法,使其刪除佇列中擁有最高優先順序的元素。新的 dequeue()
方法遍歷佇列的底層儲存陣列,從中找出優先碼最低的元素,然後使用陣列的 splice()
方法刪除優先順序最高的元素。新的dequeue()
方法定義如下所示:
dequeue() {
let priority = this.dataStore[0];
for (const i = 0, len = this.dataStore.length; i < len; i++){
if (this.dataStore[i].code < priority) {
priority = i;
}
}
return this.dataStore.splice(priority, 1);
}
最後,需要定義 toString()
方法來顯示 Patient
物件。
toString() {
let retStr = "";
for (var i = 0; i < this.dataStore.length; ++i) {
retStr += `${this.dataStore[i].name} code: ${this.dataStore[i].code}\n`;
}
return retStr;
}
測試優先佇列
雙端佇列
雙端佇列(deque,或稱double-ended queue)是一種允許我們同時從前端和後端新增和移除元素的特殊佇列。
在電腦科學中,雙端佇列的一個常見應用是儲存一系列的撤銷操作。每當使用者在軟體中進行了一個操作,該操作就會被存在雙端佇列中。當使用者點選撤銷按鈕時,該操作會從雙端佇列中彈出,表示它被從後端移除了一個了。在進行了一定數量的操作後,最先進行的操作會被從雙端佇列的前端移除。由於雙端佇列同時遵循了先入先出和後入先出的原則,可以說是它是把佇列和棧相結合的一種資料結構。
建立Deque類
class Deque {
constructor() {
this.dataStore = [];
}
addFront(element) {
if(this.empty()){
this.addBack(element)
}else{
this.dataStore.unshift(element);
}
}
addBack(element) {
this.dataStore.push(element);
}
removeFront() {
return this.dataStore.shift();
}
removeBack() {
return this.dataStore.pop();
}
front() {
return this.dataStore[0];
}
back() {
return this.dataStore[this.dataStore.length - 1];
}
empty() {
return this.dataStore.length === 0;
}
toString() {
return this.dataStore.toString();
}
length() {
return this.dataStore.length;
}
}
測試Deque類的程式碼
實際應用-->迴文字串的判斷
迴文是指這樣一種現象:一個單詞、短語或數字,從前往後寫和從後往前寫都是一樣的。 比如,單詞“dad”、“racecar”就是迴文;如果忽略空格和標點符號,下面這個句子也是回 文,“A man, a plan, a canal: Panama”;數字 1001 也是迴文。
在之前的文章中是使用棧(Stack)這一資料結構,其實迴文字串字號的判斷方法是使用雙端佇列(Deque)來實現。
function isPalindrome(word) {
if (typeof word !== "string") {
throw TypeError(`引數不是string型別`);
}
let tmp = new Deque();
for (let element of word) {
tmp.addBack(element);
}
while (tmp.length() > 1) {
if (tmp.removeFront() !== tmp.removeBack()) {
return false;
}
}
return true;
}
console.log(isPalindrome("racecar")) // true
console.log(isPalindrome("hello")) // false
參考資料
- 資料結構與演算法JavaScript描述
- 學習JavaScript資料結構與演算法 第3版