前面的兩篇文章分別介紹了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版