Stack and Queue in JavaScript(Javascript中的資料結構之棧和佇列)

lenlch發表於2019-03-11

前言: 最近因為學習資料結構與演算法,所以就寫把自己學到的東西分享出來,以及自己的一些感悟。

這篇文章主要講解Javascript中一些常見的資料結構及其實現。

Stack (棧)

棧是一種後進先出(Last In First Out/LIFO)的資料結構。

棧的實現:

實現原理: 主要是通過對 array 不同的操作方式,來達到我們想要的結果

class Stack {
   constructor() {
        this.items = []
   }
   
   is_empty() {
        // 判斷棧是否為空
        return this.items.length === 0
   }
   
   push(item) {
        // 新增到棧的頂部 
        this.items.append(item)
   }
   
   pop() {
        // 從棧的頂部刪除
        return this.items.pop()
   }
   
   peek() {
        // 查詢棧頂的元素
        return this.items[this.items.length - 1]
   }
   
   size() {
        // 返回棧的大小
        return this.items.length
   }
}
複製程式碼

棧的應用:

題目: 簡單的符號匹配。如((())) ==> true,([()]) ==> true,(([)) ==> false

/**
* 利用棧的後進先出特性,先將左半邊的符號入棧,當匹配到後半邊的時候,
* 依次出棧,依次匹配,如果能匹配上,並且最後棧為空,說明是完全匹配。
*/
function parChecker(str) {
    let s = new Stack()
    let index = 0
    while (index < str.length) {
        let sym = str[index]
        if (sym === '([{') {
            s.push(sym)
        } else {
            if (s.is_empty()) {
                return false
            } else {
                let top = s.pop()
                if (!matches(top, sym)) {
                    return false
                }
            }
        }
        index += 1
    }
    if (s.is_empty()) {
        return true
    } else {
        return false
    }
}

function matches(open, close) {
    let opens = '([{'
    let closes = ')}'
    return opens.index(open) === closes.index(close)
}

console.log(parChecker('({[()]})')) // true
console.log(parChecker('({[)})'))
複製程式碼

Queue (佇列)

佇列是一種先進先出(First In First Out/FIFO)的資料結構。

佇列的實現

實現原理:和棧一樣都是通過array來構建的,不同在於:棧是從末尾刪除,佇列是從頭部刪除

class Queue {
    constructor() {
        this.items = []
    }
    
    is_empty() {
        return this.items.length === 0
    }
    
    size() {
        return this.items.length
    }
    // 入隊
    enqueue(data) {
        this.items.push(data)
    }
    // 出隊,刪除的是頭部元素
    dequeue() {
        return this.items.shift()
    }
}
複製程式碼

佇列的應用:

著名的約瑟夫環問題。

/*
比如有5個人玩遊戲([1, 2, 3, 4, 5]),規定7個一輪迴,直至剩下最後一個人。
(拿炸金花來說,發牌一般是發給下一個人)
第一次: 2, 3, 4, 5, 1, 2, 3。 最後一張牌是發給3,所以3淘汰。[1, 2, 4, 5]
第二次: 因為3淘汰了,所以4成為發牌人。 5, 1, 2, 4, 5, 1, 2。最後是2,所以2淘汰。[1, 4, 5]
第三次: 因為2淘汰了,所以4成為發牌人。5, 1, 4, 5, 1, 4,5。最後是5,所以5淘汰 [1, 4]
第四次: 因為5淘汰了,所以1成為發牌人。4,1,4,1, 4, 1, 4,所以4淘汰,最後只剩下1.

對於這個問題,我們可以用佇列來解決。(後面我們還會說到通過單向迴圈連結串列來解決,不過略比佇列要複雜點)
思路是: 通過不斷地入隊和出隊,將每次迴圈需要刪除的元素‘移動’到佇列的最前端(也就是入隊最早的),來達到刪除他的目的
*/

function yueSeFu(list, num) {
    let q = new Queue()
    for (let i = 0; i < list.length; i++) {
        q.enqueue(list[i])
    }
    
    while (q.size() > 1) {
        for (let j = 0; j < num; j++) {
            q.enqueue(q.dequeue())
        }
        q.dequeue()
    }
    return q.dequeue()
}

console.log(yueSeFu([1, 2, 3, 4, 5], 7)) // 1
複製程式碼

雙端佇列

在佇列的基礎上,既能左邊先進先出,也能又邊先進先出。(也稱為雙端佇列)

雙端佇列的實現

實現原理: 通過對list的不同操作來達到我們想要的效果

class Deque {
    constructor() {
        this.items = []
    }
    
    is_empty() {
        return this.items.length === 0
    }
    
    size() {
        return this.items.length
    }
    
    // 在後面新增
    addRear(data) {
        this.items.push(data)
    }
    
    // 在前面新增
    addFront(data) {
        this.items.unshift(data)
    }
    
    // 在後面刪除
    removeRear() {
        return this.items.pop()
    }
    
    // 在前面刪除
    removeFront() {
        return this.items.shift()
    }
}
複製程式碼

雙端佇列的應用:

迴文字串檢查。 如'abcba' ==> true / 'abcd' ==> false

/**
通過雙端佇列就能很好的檢查一個字串是否是迴文字串。
思路:前面刪除的字串和後面刪除的字串是否一樣
*/

function palChecker(str) {
    let deque = new Deque()
    for (let i = 0; i < str.length; i++) {
        deque.addRear(str[i])
    }
    
    while (deque.size() > 1) {
        let rear = deque.removeRear()
        let front = deque.removeFront()
        
        if (rear !== front) {
            return false
        }
    }
    return true
}

console.log('abcba') // true
console.log('abcd') // false
複製程式碼

結尾:寫了一遍,自己又加深了一邊記憶。每種資料結構都有他們對應的應用場景,只有很好的理解他們,才能以最簡單的方式正確的解決最複雜的問題。

如果文章有不對的地方,歡迎指正。By the way, 最近在學Python。如果想在python中實現這些資料結構,程式碼基本上是一樣的。只是在Python中操作List的方法略有不同而已。

下次會更新連結串列(單向連結串列,單向迴圈連結串列,雙向連結串列,雙向迴圈連結串列)。謝謝!

相關文章