數據結構知否知否系列之 — 棧篇

願每次回憶,對生活都不感到負疚。——郭小川node

棧,英文 Last In First Out 簡稱 LIFO,聽從後進先出的原則,與 「隊列」 相反,在棧的頭部添加元素、刪除元素,若是棧中沒有元素就稱爲空棧。git

做者簡介:五月君,Nodejs Developer,熱愛技術、喜歡分享的 90 後青年,公衆號「Nodejs技術棧」,Github 開源項目 www.nodejs.redgithub

棧簡介

在現實生活場景中也不少例子,例如盤子疊放,從上面一個一個放置,取時也是從上面一個一個拿走,不可能從下面直接抽着拿,以下圖所示數組

圖片描述

這也是棧的典型應用,經過這個例子也可總結出棧的兩個特性:bash

  1. 僅能從棧頂端存取數據
  2. 數據存取聽從後進先出原則

棧的運行機制

關於棧的概念經過前面的學習,應該有了初步的認知,這裏從零實現一個棧進一步對棧的運行機制作一個分析,下面看下咱們實現棧須要哪些步驟:數據結構

  1. Constructor(capacity): 初始化棧內存空間,設定棧的容量
  2. isEmpty(): 檢查棧是否爲空,是否有元素
  3. isOverflow(): 檢查棧空間是否已滿,若是滿了是不能在入棧的
  4. enStack(element): 棧頂位置入棧,先判斷棧是否已滿
  5. deStack(): 棧頂位置出棧,先判斷棧元素是否爲空
  6. len(): 棧空間已有元素長度
  7. clear(): 清空棧元素,內存空間仍是保留的
  8. destroy(): 銷燬棧,同時內存也要回收(一般高級語言都會有自動回收機制,例如 C 語言這時就須要手動回收)
  9. traversing(): 遍歷輸出棧元素

初始化棧空間函數

在構造函數的 constructor 裏進行聲明,傳入 capacity 初始化棧空間同時初始化棧的頂部(top)爲 0,底部則無需關注永遠爲 0。學習

/** * * @param { Number } capacity 棧空間容量 */
constructor(capacity) {
    if (!capacity) {
        throw new Error('The capacity field is required!');
    }

    this.capacity = capacity;
    this.stack = new Array(capacity);
    this.top = 0; // 初始化棧頂爲 0 
}
複製代碼

棧空間是否爲空檢查測試

定義 isEmpty() 方法返回棧空間是否爲空,根據 top 棧頂位置進行判斷。ui

isEmpty() {
    return this.top === 0 ? true : false;
}
複製代碼

棧空間是否溢出檢查

定義 isOverflow() 方法返回棧空間是否溢出,根據棧頂位置和棧的空間容量進行判斷。

isOverflow() {
    return this.top === this.capacity;
}
複製代碼

入棧

定義 enStack(element) 方法進行入棧操做,element 爲入棧傳入的參數,入棧以前先判斷,棧是否已滿,棧未滿狀況下可進行入棧操做,最後棧位置作 ++ 操做。

/** * 入棧 * @param { * } element 入棧元素 */
enStack(element) {
    if (this.isOverflow()) {
        throw new Error('棧已滿');
    }

    this.stack[this.top] = element;
    this.top++;
}
複製代碼

出棧

定義 enStack(element) 方法進行出棧操做,首先判斷棧空間是否爲空,未空的狀況進行出棧操做,注意這裏的棧位置,因爲元素進棧以後會進行 ++ 操做,那麼在出棧時當前棧位置確定是沒有元素的,須要先作 -- 操做。

deStack() {
    if (this.isEmpty()) {
        throw new Error('棧已爲空');
    }

    this.top--;
    return this.stack[this.top];
}
複製代碼

棧元素長度

這個好判斷,根據棧的 top 位置信息便可

len() {
    return this.top;
}
複製代碼

清除棧元素

這裏有幾種實現,你也能夠把 stack 的空間進行初始化,或者把 top 棧位置設爲 0 也可。

clear() {
    this.top = 0;
}
複製代碼

棧銷燬

在一些高級語言中都會有垃圾回收機制,例如 JS 中只要當前對象再也不持有引用,下次垃圾回收來臨時將會被回收。不清楚的能夠看看我以前寫的 Node.js 內存管理和 V8 垃圾回收機制

destroy() {
    this.stack = null;
}
複製代碼

棧元素遍歷

定義 traversing(isBottom) 方法對棧的元素進行遍歷輸出,默認爲頂部遍歷,也可傳入 isBottom 參數爲 true 從底部開始遍歷。

traversing(isBottom = false){
    const arr = [];

    if (isBottom) {
        for (let i=0; i < this.top; i++) {
            arr.push(this.stack[i])
        }
    } else {
        for (let i=this.top-1; i >= 0; i--) {
            arr.push(this.stack[i])
        }
    }

    console.log(arr.join(' | '));
}
複製代碼

作一些測試

作下測試分別看下入棧、出棧、遍歷操做,其它的功能你們在練習的過程當中可自行實踐。

const s1 = new StackStudy(4);

s1.enStack('Nodejs'); // 入棧
s1.enStack('技');
s1.enStack('術');
s1.enStack('棧');
s1.traversing() // 棧 | 術 | 技 | Nodejs
console.log(s1.deStack()); // 出棧 -> 棧
s1.traversing() // 術 | 技 | Nodejs
s1.traversing(true) // 從棧底遍歷:Nodejs | 技 | 術
複製代碼

下面經過一張圖展現以上程序的入棧、出棧過程

圖片描述

棧的運行機制源碼地址以下:

https://github.com/Q-Angelo/project-training/tree/master/algorithm/stack.js
複製代碼

JavaScript 數組實現棧

JavaScript 中提供的數組功能便可實現一個簡單的棧,使用起來也很方便,熟悉相關 API 便可,下面咱們來看下基於 JS 數組的入棧、出棧過程實現。

圖片描述

以上圖片展現了棧的初始化、入棧、出棧過程,下面咱們採用 JavaScript 原型鏈的方式實現。

初始化隊列

初始化一個存儲棧元素的數據結構,若是未傳入默認賦值空數組。

function StackStudy(elements) {
    this.elements = elements || [];
}
複製代碼

添加棧元素

實現一個 enStack 方法,向棧添加元素,注意只能是棧頭添加,使用 JavaScript 數組中的 push 方法。

StackStudy.prototype.enStack = function(element) {
    this.elements.push(element);
}
複製代碼

移除棧元素

實現一個 deStack 方法,棧尾部彈出元素,使用 JavaScript 數組中的 pop 方法(這一點是和隊列不一樣的)。

StackStudy.prototype.deStack = function() {
    return this.elements.pop();
}
複製代碼

經過 JavaScript 數組實現是很簡單的,源碼以下:

https://github.com/Q-Angelo/project-training/tree/master/algorithm/stack-js.js
複製代碼

棧的經典應用

經過對前面的講解,相信已經對棧有了必定的瞭解,那麼它能夠用來作什麼呢,本節舉幾個典型的應用案例。

十進制轉換爲二進制、八進制、十六進制

如今生活中咱們使用最多的是十進制來表示,也是人們最易懂和記得的,可是計算機在處理的時候就要轉爲二進制進行計算,在十進制與二進制的轉換過程之間通常還會用八進制或者十六進制做爲二進制的縮寫。

所以,這裏主要講解十進制、八進制、十六進制、二進制轉換過程當中在棧中的實際應用。首先你須要先了解這幾種數據類型之間的轉換規則,也不難經過一張圖來告訴你。

圖片描述

上圖中咱們用十進制整除須要轉換的數據類型(二進制、八進制、十六進制),將餘數放入棧中,明白這個原理在用代碼實現就很簡單了。

編碼

const StackStudy = require('./stack.js');
const str = '0123456789ABCDEF';

function dataConversion(num, type) {
    let x = num;
    const s1 = new StackStudy(20);

    while (x != 0) {
        s1.enStack(x % type);
        x = Math.floor(x / type);
    }

    while (!s1.isEmpty()) {
        console.log(str[s1.deStack()]);
    }

    console.log('--------------------');
    return;
}
複製代碼

引用咱們在棧的運行機制裏面講解的代碼,編寫 dataConversion 方法,入棧、出棧進行遍歷輸出。代碼中定義的變量 str 是爲了十六進制會出現字母的狀況作的處理。

測試

如下運行結果徹底符合咱們的預期,你們也可用電腦自帶的計算器功能進行驗證。

// 測試八進制
dataConversion(1024, 8); // 2000

// 測試十六進制
dataConversion(1024, 16); // 400

// 測試十六進制帶字母的狀況
dataConversion(3000, 16); // BB8

// 測試二進制
dataConversion(1024, 2); // 10000000000
複製代碼

十進制轉換爲二進制、八進制、十六進制源碼地址:

https://github.com/Q-Angelo/project-training/tree/master/algorithm/stack-data-conversion.js
複製代碼

平衡園括號

這個也是另一個實際問題,在一些運算中你可能寫過以下表達式,爲了保證操做順序,使用了括號,可是要注意括號必須是平衡的,每一個左括號要對應一個右括號,不然程序將沒法正常運行

((1 + 2) * 3 * (4 + 5)) * 6
複製代碼

以上示例組成的平衡表達式

(()())
複製代碼

非平衡表達式

(()()
複製代碼

經過「棧」解決平衡園括號問題實現步驟

  • 初始化一個空棧 {1}
  • 遍歷須要檢測的符號 {2}
  • 遍歷須要檢測的平衡符號都有哪些 {3}
  • 若是字符屬於入棧的符號([ { (...)將其入棧 {3.1}
  • 若是字符屬於閉合的符號,先判斷棧空間是否爲空,空的狀況下中斷操做,不然進行出棧,若是出棧的字符也不是閉合符號對應的開放符號,檢測失敗,中斷操做跳出循環 {3.2}
  • 每一次循環完成判斷當前是否中斷,若是已經中斷操做,將不合法的字符入棧,中斷最外層字符檢測循環 {4}
  • 最後檢測棧是否爲空,若是爲空則經過,不然不經過輸出 {5}

編碼實現

能夠參照 「經過「棧」解決平衡園括號問題實現步驟」 有助於理解如下代碼

const Stack = require('./stack');
const detectionStr = '[]()'; // 定義須要檢測的平衡符號,如何還有別的符號按照這種格式定義

function test(str) {
    let isTermination = false; // 是否終止,默認 false
    let stack = new Stack(20); // 初始化棧空間 {1}

    for (let i=0; i<str.length; i++) { // {2}
        const s = str[i];
        for (let d=0; d<detectionStr.length; d+=2) { // {3}
            const enStackStr = detectionStr[d]; // 入棧字符
            const deStackStr = detectionStr[d+1]; // 出棧字符

            switch (s) {
                case enStackStr : // 入棧 {3.1}
                    stack.enStack(s);
                    break;
                case deStackStr : // 出棧 {3.2}
                    if (stack.isEmpty()) {
                        isTermination = true
                    } else {
                        const currElement = stack.deStack();
                        if (!currElement.includes(enStackStr)) { 
                            isTermination = true
                        }
                    }
                    break;
            }

            if (isTermination) break;
        }

        if (isTermination) { // 存在不匹配符號,提早終止 {4}
            stack.enStack(s);
            break;
        }
    }

    if (stack.isEmpty()) { // {5}
        console.log('檢測經過');
    } else {
        console.log('檢測不經過,檢測不經過符號:');
        stack.traversing()
    }

    return stack.isEmpty();
}
複製代碼

編碼測試

test('((()()[]])') // 檢測不經過,檢測不經過符號:](
test('()()[])') // 檢測不經過,檢測不經過符號:)
test('[()()[]') // 檢測不經過,檢測不經過符號:[
test('()()][]') // 檢測經過
複製代碼

平衡園括號問題源碼地址:

https://github.com/Q-Angelo/project-training/tree/master/algorithm/stack-balance-symbol.js
複製代碼
相關文章
相關標籤/搜索