佇列和雙端佇列
阿新 • • 發佈:2022-03-01
本篇部落格的所有程式碼都整理到自己的github庫中,以便大家檢視及學習。
本篇部落格主要是從以下幾點展開
- 佇列資料結構
- 雙端數列資料結構
- 向佇列和雙端佇列增加元素
- 從佇列和雙端佇列中刪除元素
- 經典演算法
1. 佇列資料結構
佇列遵循先進先出(FIFO)原則的一組有序的項。佇列在尾部新增元素,在頭部移除元素,最先新增的元素一定是放在佇列的末尾。
佇列的例子在生活中很常見,在排隊買票,排隊上車等等
2. 建立佇列
接下來我們通過建立自己的類表示佇列,然後宣告以下方法
enqueue(elements)
: 向尾部追加元素(追加新的項)dequeue()
: 移除佇列中的第一項,並返回移除的元素peek()
: 返回佇列中的第一項(並不會對該項有任何操作)isEmpty()
: 判斷佇列是否為空size()
: 返回佇列中包含的元素的個數clear()
: 清空佇列toString()
: 轉為字串的方法
class Queue{ constructor(){ this.items = {} // 根據上篇文章《棧》的理解,這次我們也是用陣列的方式來放佇列中的各元素 this.count = 0 // 佇列中各項元素的個數 this.lowestCount = 0 // 用來幫助我們追蹤第一個元素的變數 } enqueue(elem){ this.items[this.count] = elem //將佇列中的最後一個位置設定為追加進來的元素 this.count++ } dequeue(){ if (this.isEmpty()) { return '' } const result = this.items[this.lowestCount] delete this.items[this.lowestCount] this.lowestCount++ return result } peek(){ if (this.isEmpty()) { return '' } return this.items[this.lowestCount] } isEmpty(){ return this.size() === 0 } size(){ return this.count - this.lowestCount } clear(){ this.items = {} this.count = 0 this.lowestCount = 0 } toString(){ if (this.isEmpty()) { return '' } let result = this.items[this.lowestCount] for (const k in this.items) { if (this.items[k] && this.items[k] !== this.items[this.lowestCount]) { result = `${result},${this.items[k]}` } } return result } } var queue = new Queue() queue.enqueue(1) queue.enqueue(2) queue.enqueue(3) queue.enqueue(4) console.log(queue) // Queue { items: [ 1, 2, 3, 4 ], count: 4, lowestCount: 0 } console.log(queue.size()) // 4 console.log(queue.isEmpty()) // false console.log(queue.dequeue()) // 1 console.log(queue) // Queue { items: [ <1 empty item>, 2, 3, 4 ], count: 4, lowestCount: 1 } console.log(queue.size()) console.log(queue.peek()) // 2 console.log(queue) // Queue { items: [ <1 empty item>, 2, 3, 4 ], count: 4, lowestCount: 1 } console.log(queue.toString()) // 2,3,4
3. 雙端佇列資料結構
雙端佇列(deque,或稱 double-end queue) 是一種允許我們同時從前端和後端新增和移除元素的特殊佇列。
最常見的生活中的例子就是大家排隊買票,當第一個人買完票後,可能會再次返回問售票員一些問題,這個時候他就不用去排隊,而排在隊尾的人可能就因為趕時間,提前離開隊伍。
雙端佇列最常見的一個應用就是儲存一系列撤銷操作
4. 建立 Deque 類
和之前一樣,我們也是在建構函式中宣告一些雙端佇列內部的屬性及方法,isEmpty
,clear
,size
,toString
。
接下來定義一些對外的方法
addFront(element)
: 向雙端佇列前面插入新的元素addBack(element)
: 向雙端佇列後面追加新的元素removeFront()
: 移除雙端佇列前面的元素,並返回移除的元素removeBack()
: 移除雙端佇列後面的元素,並返回移除的元素peekFront()
: 返回雙端佇列中的第一項元素peekBack()
: 返回雙端佇列中的最後一項元素
class Deque{
constructor(){
this.items = {}
this.count = 0
this.lowestCount = 0
}
isEmpty(){
return this.count === 0
}
clear(){
this.items = {}
this.count = 0
this.lowestCount = 0
}
size(){
return this.count - this.lowestCount
}
addFront(elem){
if (this.isEmpty()) {
this.addBack(elem)
} else if (this.lowestCount > 0) {
this.lowestCount --
this.items[this.lowestCount] = elem
} else {
for (let i = this.count; i > 0; i--) {
this.items[i] = this.items[i - 1]
}
this.count ++
this.lowestCount = 0
this.items[0] = elem
}
}
addBack(elem){
this.items[this.count] = elem
this.count++
}
removeFront(){
const result = this.items[this.lowestCount]
this.items[this.lowestCount] = undefined
this.lowestCount ++
return result
}
removeBack(){
const result = this.items[this.count]
this.count --
return result
}
peekFront(){
return this.items[this.lowestCount]
}
peekBack(){
return this.items[this.count]
}
toString(){
if (this.isEmpty()) {
return ''
}
let result = this.items[this.lowestCount]
for (const k in this.items) {
if (this.items[k] !== result && this.items[k]) {
result = `${result},${this.items[k]}`
}
}
return result
}
}
var deque = new Deque()
deque.addBack(1)
deque.addFront(0)
deque.addBack('Alice')
deque.addFront('JD')
console.log(deque) //Deque {items: { '0': 'JD', '1': 0, '2': 1, '3': 'Alice' },count: 4,lowestCount: 0 }
console.log(deque.removeFront()) // JD
console.log(deque) //Deque {items: { '0': undefined, '1': 0, '2': 1, '3': 'Alice' },count: 4, lowestCount: 1}
console.log(deque.size()) // 3
console.log(deque.toString()) // 0,1,Alice
5. 經典演算法
1. 擊鼓傳花
在思考這個題目的時候我們先了解一下迴圈佇列。而迴圈佇列最常見的例子就是擊鼓傳花。擊鼓傳花就是一群孩子們圍成圈,將花傳遞給旁邊的人,當在某一時刻,傳花停止時,花在誰的手裡誰就會被淘汰。重複這個過程,直到剩下最後一個孩子
function hotPotato(elementList,num){
const queue = new Queue()
const eliminatedList = [] // 淘汰的元素
for (let i = 0; i < elementList.length; i++) {
queue.enqueue(elementList[i])
}
while (queue.size() > 1) {
for (let i = 0; i < num; i++) {
queue.enqueue(queue.dequeue())
}
eliminatedList.push(queue.dequeue())
}
return {
eliminated:eliminatedList,
winner:queue.dequeue()
}
}
var names = ['John','Jack','Calmia','Alice','Carl']
const result = hotPotato(names,7)
// console.log(result) // { eliminated: [ 'Calmia', 'Jack', 'Carl', 'Alice' ], winner: 'John' }
result.eliminated.forEach(name => {
console.log(`${name}在遊戲中被淘汰`)
})
console.log(`勝利者是:${result.winner}`)
// Calmia在遊戲中被淘汰
// Jack在遊戲中被淘汰
// Carl在遊戲中被淘汰
// Alice在遊戲中被淘汰
// 勝利者是:John
上述的程式碼不好理解,這裡畫一幅圖,便於理解;圖中只表示了第一次迴圈的狀態
2. 迴文檢查器
最簡單的方式就是將字串反向排列後,檢查它和原字串是否一樣,若相同,則為一個迴文字串
function palindromeChecker(string) {
if (!string) { // 判空
return false
}
let deque = new Deque()
const str = string.toLowerCase() // 轉為了小寫
const tempStr = str.split(' ').join() // 去除字串中的空格
let isEqual = true
let firstChar; let lastChar
// 將字串中的第一個字串放到最後
for (let i = 0; i < tempStr.length; i++) {
deque.addBack(tempStr.charAt(i))
}
// 比較每次雙端佇列移除的項是否相等,相等則繼續迴圈,直到size為1,不想等則直接退出迴圈
while (deque.size() > 1 && isEqual) {
firstChar = deque.removeFront()
lastChar = deque.removeBack()
if (firstChar !== lastChar) {
isEqual = false
}
}
return isEqual
}
本文來自部落格園,作者:前端李墩墩,轉載請註明原文連結:https://www.cnblogs.com/bllx/p/15949239.html