佇列和棧非常類似, 棧的一端是封閉的, 類似一口深井, 遵循先進後出原則 FILO.
佇列則兩端是放開的, 抽象於現實世界的排隊現象, 遵循先進先出原則 FIFO.
佇列在尾部進行元素的新增, 稱為 "入隊", 然後從頭部移除元素, 成為 "出隊". 生活中我們去坐火車進站檢票, 去某個機關辦理業務, 去用印表機印一疊檔案等都是需要 "排隊", 先來的先處理, 後來的往後站, 不允許插隊哦.
建立佇列
最簡單方式可直接用 js 陣列實現, 隊尾用 arr.push(), arr.pop()
, 隊首用 arr.unshift(), arr.shift()
但從元素獲取層面希望能更高效, 還是覺得用 js物件
來實現更合適一些呢.
class Queue {
constructor() {
this.count = 0 // 控制佇列大小, 非長度
this.fontIndex = 0 // 隊首第一個元素索引
this.obj = {}
}
}
然後是要給佇列宣告一些常用方法
- enqueue () 向隊尾新增元素
- dequeue () 向隊首移除元素, 並返回
- peek () 返回隊首元素
- isEmpty () 檢查是否為空佇列, 返回 true 或者 false
- size () 返回佇列的元素個數, 即佇列長度, 類似陣列的 length
元素入隊
即元素必須要從隊尾進行新增, 因為用的底層是 JS物件, 即用 count
作為鍵, 新增後自增 1 即可.
class Queue {
constructor() {
this.count = 0
this.frontIndex = 0
this.obj = {}
}
// 元素入隊
enqueue(item) {
this.obj[this.count] = item
this.count += 1
}
}
佇列長度
即佇列中元素個數, 即用佇列的長度 減去 隊首元素的索引即可, 當這個值為0 , 則說明是空佇列了.
class Queue {
constructor() {
this.count = 0
this.frontIndex = 0
this.obj = {}
}
// 佇列長度
size() {
return this.count - this.frontIndex
}
// 佇列是否為空
isEmpty() {
this.size() == 0
}
}
還是來簡單模擬一下,直觀理解一波這個長度的計算.
假設左邊是隊尾, 右邊是隊首, 而且是有序的
先入隊a: { 0:a },
再入隊b: { 1:b, 0:a },
再入隊c: { 2:c, 1:b, 0:a }
此時的 this.count = 3;
隊首索引是: this.frontIndex = 0
根據先進先出, 對 a 進行出隊
此時到對首元素是0, 動態表示為 obj[this.frontIndex], 值是 a
然後進行刪除後, 此時的佇列是 { 2:c, 1:b }, 即當前隊首元素索引 從 0 變到 1
動態表示即 this.frontIndex + 1 表示此時隊首的元素
元素出隊
只要弄清楚瞭如何計算佇列長度, 非空下的 frontIndex
就是隊首長度, 刪掉它即可.
class Queue {
constructor() {
this.count = 0
this.frontIndex = 0
this.obj = {}
}
// 佇列是否為空
isEmpty() {
this.count - this.frontIndex == 0
}
// 元素出隊
dequeue() {
if (this.isEmpty()) return undefined
let frontItem = this.obj[this.frontIndex]
delete this.obj[this.frontIndex]
this.frontIndex += 1 // 更新隊首元素索引
return frontItem
}
}
清空佇列元素
粗暴方式是直接指向空, 或者一直進行 dequeue()
直到返回 undefined 為止.
// 清空佇列
clear() {
this.obj = {}
this.frontIndex = 0
this.count = 0
}
}
檢視隊首元素和全佇列
隊首就是索引為 this.frontIndex
的值, 檢視全部可以類似棧封裝一個 toString
方法
class Queue {
constructor() {
this.count = 0
this.frontIndex = 0
this.obj = {}
}
// 檢視隊首元素
peek() {
if (this.isEmpty()) return undefined
return this.obj[this.frontIndex]
}
// 檢視全佇列
toString() {
if (this.isEmpty()) return undefined
let objString = `${this.obj[this.frontIndex]}`
for (let i = this.frontIndex + 1; i < this.count; i++) {
objString = `${objString}, ${this.obj[i]}`
}
return objString
}
}
這樣以來,我們的佇列就基本建好了, 然後我們來整體測試一波
class Queue {
constructor() {
this.count = 0
this.frontIndex = 0
this.obj = {}
}
// 元素入隊
enqueue(item) {
this.obj[this.count] = item
this.count += 1
}
// 佇列長度
size() {
return this.count - this.frontIndex
}
// 佇列是否為空
isEmpty() {
this.count - this.frontIndex == 0
}
// 元素出隊
dequeue() {
if (this.isEmpty()) return undefined
let frontItem = this.obj[this.frontIndex]
delete this.obj[this.frontIndex]
this.frontIndex += 1 // 更新隊首元素索引
return frontItem
}
// 清空佇列
clear() {
this.obj = {}
this.frontIndex = 0
this.count = 0
}
// 檢視隊首元素
peek() {
if (this.isEmpty()) return undefined
return this.obj[this.frontIndex]
}
// 檢視全佇列
toString() {
if (this.isEmpty()) return undefined
let objString = `${this.obj[this.frontIndex]}`
for (let i = this.frontIndex + 1; i < this.count; i++) {
objString = `${objString}, ${this.obj[i]}`
}
return objString
}
}
// test
const queue = new Queue()
queue.enqueue('youge')
queue.enqueue('yaya')
queue.enqueue('jack')
// 檢驗入隊
console.log('此時的佇列是: ', queue.toString());
console.log('佇列長度是: ', queue.size());
console.log('隊首元素是: ', queue.peek());
// 檢驗出隊
console.log('出隊的元素是: ', queue.dequeue());
console.log('此時的佇列是: ', queue.toString());
console.log('佇列長度是: ', queue.size());
console.log('隊首元素是: ', queue.peek());
// 剩下元素出隊
console.log('出隊的元素是: ', queue.dequeue());
console.log('出隊的元素是: ', queue.dequeue());
// 空了
console.log('此時的佇列是: ', queue.toString());
console.log('佇列長度是: ', queue.size());
console.log('隊首元素是: ', queue.peek());
檢視一下結果:
PS F:\algorithms> node queue.js
此時的佇列是: youge, yaya, jack
佇列長度是: 3
隊首元素是: youge
出隊的元素是: youge
此時的佇列是: yaya, jack
佇列長度是: 2
隊首元素是: yaya
出隊的元素是: yaya
出隊的元素是: jack
此時的佇列是: undefined
佇列長度是: 0
隊首元素是: undefined
這樣就實現了一個單端的佇列, 後面接著會再來實現一個雙端的佇列哦.