資料結構之Stack | 讓我們一塊來學習資料結構

guojikun發表於2021-04-26

棧的介紹

棧就是和列表類似的一種資料結構,它可用來解決計算機世界裡的很多問題。棧是一種高 效的資料結構,因為資料只能在棧頂新增或刪除,所以這樣的操作很快,而且容易實現。 棧的使用遍佈程式語言實現的方方面面,從表示式求值到處理函式呼叫

棧是一種特殊的列表,棧內的元素只能通過列表的一端訪問,這一端稱為棧頂。咖啡廳內 的一摞盤子是現實世界中常見的棧的例子。只能從最上面取盤子,盤子洗淨後,也只能摞 在這一摞盤子的最上面。棧被稱為一種後入先出(LIFO,last-in-first-out)的資料結構。

由於棧具有後入先出的特點,所以任何不在棧頂的元素都無法訪問。為了得到棧底的元 素,必須先拿掉上面的元素。

對棧的兩種主要操作是將一個元素壓入棧和將一個元素彈出棧。入棧使用 push() 方法,出 棧使用 pop() 方法。圖 一演示了入棧和出棧的過程。

另一個常用的操作是預覽棧頂的元素。pop() 方法雖然可以訪問棧頂的元素,但是呼叫該方 法後,棧頂元素也從棧中被永久性地刪除了。peek() 方法則只返回棧頂元素,而不刪除它。

棧的實現

實現一個棧,當務之急是決定儲存資料的底層資料結構。這裡採用的是陣列。

我們的實現以定義 Stack 類開始:

class Stack {
    constructor() {
        this.dataStore = [];
        this.top = 0;
    }
    push() { }
    pop() { }
    peek() { }
    clear() { }
    length() { }
    empty() { }
    toString(){ }
}

我們用陣列 dataStore 儲存棧內元素,建構函式將其初始化為一個空陣列。變數 top 記錄 棧頂位置,被建構函式初始化為 0,表示棧頂對應陣列的起始位置 0。如果有元素被壓入 棧,該變數的值將隨之變化。

實現push方法

先來實現 push() 方法。當向棧中壓入一個新元素時,需要將其儲存在陣列中變數 top 所對 應的位置,然後將 top 值加 1,讓其指向陣列中下一個空位置。程式碼如下所示:

push(element) {
    this.dataStore[++this.top] = element;
}

這裡要特別注意 ++ 操作符的位置,它放在 this.top 的後面,這樣新入棧的元素就被放在 top 的當前值對應的位置,然後再將變數 top 的值加 1,指向下一個位置。

實現pop方法

pop() 方法恰好與 push() 方法相反——它返回棧頂(被刪除的)元素,同時將變數 top 的值減 1

pop() {
    return this.dataStore[this.top--];
}

實現peek方法

peek() 方法返回陣列的第 top-1 個位置的元素,即棧頂元素:

peek() {
    return this.dataStore[this.top - 1];
}

如果對一個空棧呼叫 peek() 方法,結果為 undefined。這是因為棧是空的,棧頂沒有任何 元素。

實現length方法

有時候需要知道棧記憶體儲了多少個元素。length() 方法通過返回變數 top 值的方式返回棧內的元素個數:

length() {
    return this.top
}

實現clear方法

可以將變數 top 的值設為 0,輕鬆清空一個棧:

clear() {
    this.top = 0;
}

實現empty方法

empty()方法可以知道一個Stack是否是空的

empty() {
    return this.top === 0;
}

使用Stack類解決問題

數制間的轉化

可以利用棧將一個數字從一種數制轉換成另一種數制。假設想將數字 n 轉換為以 b 為基數 的數字,實現轉換的演算法如下。

  1. 最高位為 n % b,將此位壓入棧。
  2. 使用 n/b 代替 n
  3. 重複步驟 1 和 2,直到 n 等於 0,且沒有餘數。
  4. 持續將棧內元素彈出,直到棧為空,依次將這些元素排列,就得到轉換後數字的字元 串形式

此演算法只針對基數為2~9的情況

程式碼實現

function mulBase(num, base=2) {
    let tmp = new Stack();
    while (num > 0) {
        tmp.push(num % base)
        num = Math.floor(num /= base);
    }
    let target = '';
    while(tmp.length() > 0) {
        target = target + tmp.pop();
    }
    return target;
}

console.log(mulBase(3,2)) // "11"
console.log(mulBase(3,6)) // "6"

迴文字串的判斷

迴文是指這樣一種現象:一個單詞、短語或數字,從前往後寫和從後往前寫都是一樣的。 比如,單詞“dad”、“racecar”就是迴文;如果忽略空格和標點符號,下面這個句子也是回 文,“A man, a plan, a canal: Panama”;數字 1001 也是迴文。

使用棧,可以輕鬆判斷一個字串是否是迴文。我們將拿到的字串的每個字元按從左至 右的順序壓入棧。當字串中的字元都入棧後,棧內就儲存了一個反轉後的字串,最後 的字元在棧頂,第一個字元在棧底。

function isPalindrome(word) {
    if (typeof word !== "string") {
        throw TypeError(`引數不是string型別`);
    }
    let tmp = new Stack();
    for (let element of word) {
        tmp.push(element)
    }

    let rword = '';
    while (tmp.length() > 0) {
        rword += tmp.pop()
    }
    console.log(rword);
    return word === rword;
}

console.log(isPalindrome("racecar")) // true
console.log(isPalindrome("hello")) // false

參考資料

  • 資料結構與演算法JavaScript描述

相關的系列文章

資料結構之List | 讓我們一塊來學習資料結構
二叉搜尋樹BST

相關文章