[ JavaScript ] 數據結構與算法 —— 棧

前言

JavaScript是當下最流行的編程語言之一,它可以做很多事情:

  • 數據可視化(D3.js,Three.js,Chart.js);
  • 移動端應用(React Native,Weex,AppCan,Flutter,Hybrid App,小程序);
  • 服務端(Express.js,Koa2);
  • 桌面應用(Electron,nw.js);
  • 遊戲,VR,AR(LayaAir,Egret,Turbulenz,PlayCanvas);
  • 等等。。。

而且目前大部分編程語言的高級應用都會用到數據結構與算法以及設計模式。

本篇主要有三部分

  • 什麼是棧
  • 棧的實現
  • 棧的應用

源碼地址:https://github.com/yhtx1997/S...

什麼是棧

較官方解釋

棧是一種遵從後進先出(LIFO)原則的有序集合。新添加的或待刪除的元素都保存在棧的
同一端,稱作棧頂,另一端就叫棧底。在棧裏,新元素都靠近棧頂,舊元素都接近棧底。

棧.png

個人理解

上面看不懂?沒關係,接下來我用生活中比較常見的事來解釋。
大家應該都搬過家,旅行,或者上學時住宿,這個過程中都會用到行李箱。

  • 棧:現在這個旅行箱就是我們的棧;
  • 棧裏邊的元素:我們往旅行箱放疊好的衣服或其他的東西,這些東西就是棧裏邊的元素;
  • 棧底:我們放衣服時都是是先放旅行箱的最底下,不可能說什麼東西都沒有就讓它飄着是吧,這個旅行箱最底下衣服在的位置就是棧底
  • 棧頂:相對應的最後放進去的一件衣服的位置就是棧頂;
  • 壓棧:把元素放進去(把衣服放進旅行箱)的動作就是壓棧;
  • 出棧:把元素拿出來(把衣服拿出來)的動作就是出棧;
  • 堆棧溢出:一般是指棧滿了放不下了(旅行箱滿了怎麼塞塞不下了);
  • 後進先出:放衣服時一件一件的放,但是往外拿衣服的時候是先拿放的時候最後放的,最後拿出來的是放的時候最先放的;可能有人拿衣服直接翻到最下邊然後拿出來,這個過程可以看做,先將除了棧底的元素依次出棧並保存,等拿到棧底的元素在依次壓棧,把元素放回去
  • 有序集合:額,就是一個挨着一個,有順序的,一些有相同屬性的東西

棧的實現

  • 添加元素
  • 刪除元素
  • 返回棧頂元素
  • 是否爲空
  • 元素數量
  • 清空元素
  • 打印所有元素
class Stack {
    constructor() {
        this.count = 0; // 長度
        this.items = {}; // 棧
    }
    push(element) {
        // 添加元素
    }
    pop() {
        // 刪除元素
    }
    peek() {
        // 返回棧頂元素
    }
    isEmpty() {
        // 是否爲空
    }
    size() {
       // 元素數量
    }
    clear() {
       // 清空元素
    }
    toString() {
       // 打印所有元素
    }
}

添加元素

push(element) {
    this.items[this.count] = element;
    this.count++; // 長度加1
}

刪除元素

 pop() {
    if (this.isEmpty()) { // 如果是空直接返回 undefined
        return undefined;
    }
    this.count--;
    let item = this.items[this.count];
    delete this.items[this.count];
    return item
}

返回棧頂元素

peek() {
    if (this.isEmpty()) {
        return undefined;
    }
    return this.items[this.count - 1];
}

是否爲空

isEmpty() {
    return this.count === 0;
}

元素數量

size() {
    return this.count;
}

清空元素

clear() {
    this.count = 0;
    this.items = {};
}

打印所有元素

toString() {
    if (this.isEmpty()) {
        return '';
    }
    let objString = `${this.items[0]}`;
    for (let i = 1; i < this.count; i++) {
        objString = `${objString},${this.items[i]}`;
    }
    return objString;
}

棧的應用

  • 進制轉換
  • 括號匹配檢驗
  • 迷宮求解

進制轉換

棧因爲是先進後出,所以如果將一組數據全部壓棧,再出棧並保存每次出棧的元素,這樣一來相當於將這一組元素的順序進行倒序。
十進制轉換二進制的過程就是一個數除以2,取餘數,最後將餘數結果進行倒序排列。
現在棧可以進行倒序,而進制轉換需要倒序,所以就可以將棧應用到進制的轉換中。
代碼如下:

function DecimalToBinary(number) {
    let stack = new Stack(); // 新建棧
    let rem = 0; // 餘數
    let binary = ''; // 結果
    while (number > 0) {
        rem = Math.floor(number % 2); // 取餘數
        stack.push(rem); // 將餘數壓棧
        number = Math.floor(number / 2); // 去掉餘數除以二
    }
    while (!stack.isEmpty()) { // 不爲空
        binary += stack.pop(); // 將元素全部出棧
    }
    return binary;
}

括號匹配檢驗

正常括號嵌套是這樣的

{{{[([({()})])]}}}

可以發現,在某個位置分爲了左右兩部分,右邊第一個,與左邊最後一個相對應
右邊最後一個與左邊第一個相對應
左側相當於進行了倒序,而倒序就可以用棧來解決

我們可以將所有的左側括號都依次壓入棧中,然後依次判斷右側是否與棧頂元素相匹配
但是相匹配的括號並不相等

可以採用鍵值對的形式存儲一下

{
    '}': '{',
    ']': '[',
    ')': '('
}

或者下標的形式

['{','[','(']
['}',']',')']

最終代碼如下

function parenthesesChecker(symbols) {
    let stack = new Stack(); // 新建棧
    let balanced = true; // 檢驗括號匹配是否正確
    const leftBrackets = '{[('; // 左側的括號
    const rightBrackets = '}])'; // 右側的括號
    for (let i = 0; i < symbols.length && balanced; i++) {
        current = symbols[i]; // 取單個符號
        if (leftBrackets.indexOf(current) >= 0) { // 如果是左側的括號
            stack.push(current); // 將其壓棧
        } else if (stack.isEmpty()) { // 不是左側括號,且棧爲空,括號匹配錯誤
            balancd = false;
        } else { // 不是左側括號,且棧不爲空,視爲沒有新的左側括號,剩下的都是右側括號
            let top = stack.pop();
            if (!(leftBrackets.indexOf(top) === rightBrackets.indexOf(current))) { 
            // 檢查棧頂(最後一個左側括號)符號在 leftBrackets 的位置以及當前符號在 rightBrackets 的位置,位置相同視爲配對成功
                balanced = false;
            }
        }
    }
    
    return balanced; // 返回結果
}

迷宮求解

這個比較複雜,之後會寫個小實例,敬請期待......

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章