1. 程式人生 > 其它 >佇列和雙端佇列

佇列和雙端佇列

本篇部落格的所有程式碼都整理到自己的github庫中,以便大家檢視及學習。

本篇部落格主要是從以下幾點展開

  1. 佇列資料結構
  2. 雙端數列資料結構
  3. 向佇列和雙端佇列增加元素
  4. 從佇列和雙端佇列中刪除元素
  5. 經典演算法

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