堆棧的基礎知識

  • 什麼是棧
  • 棧的操做
  • 實現一個棧類
  • 棧的應用
  • 棧屬性私有化

什麼是棧

image
堆棧是隻能從列表的一端(稱爲頂部)訪問的元素列表. 一個常見的,現實生活中的例子就是自助餐廳的托盤堆. 托盤老是從頂部取走, 當托盤在清洗後放回堆棧時,它們被放置在堆棧的頂部. 堆棧被稱爲後進先出(LIFO)數據結構.git

因爲堆棧的後進先出特性, 當前不在棧頂都不能被訪問的. 假如想訪問棧底的元素必須把上面的元素移除(托盤取走)github

棧是一種類隊列的數據結構, 能夠用解決許多計算問題. 同時棧是很是高效的一種數據結構,因數據新增、刪除都只能從棧頂完成, 而且容易以及快速實現.數組

棧操做

因爲棧後入先出的特性,任何非棧頂元素不能被訪問. 要想訪問棧底的元素必須把上面的元素所有出棧才能訪問到.安全

下面棧的經常使用的兩個操做,元素入棧、出棧. 使用push操做將元素添加到堆棧中, 使用pop操做從堆棧中取出元素. 下圖所示
image數據結構

堆棧另外一個常見操做是查看堆棧頂部的元素. pop操做訪問堆棧的頂部元素,可是它將永久地從堆棧中刪除該元素. peek操做返回存儲在堆棧頂部的值,而不從堆棧中刪除它. 除pop()push()peek() 主要方法外, 棧應該包含其它的如判斷棧是否是空 isEmpty()、獲取棧的大小 size()、清空棧 clear().
下面經過代碼來實現一個棧.測試

棧的實現

經過數組來實現一個棧, 代碼以下:this

class Stack {
    constructor() {
        this.items = []
    }

    push(ele) {
        this.items.push(ele)
    }

    pop() {
        return this.items.pop()
    }

    peek() {
        return this.items[this.items.length - 1]
    }

    isEmpty() {
        return !!this.items.length
    }
    size() {
        return this.items.length
    }
    clear() {
        this.items = []
    }
    print(){
        return this.items.toString()
    }
}

下面經過一個案例來測試下定義棧類.spa

const stack = new Stack()
console.log(stack.isEmpty()) //=> true
stack.push(1) //=> 入棧 1
stack.push(2) //=> 入棧 2
stack.push(3) //=> 入棧 3

stack.pop()   //=> 出棧 3
stack.pop()   //=> 出棧 2

stack.push(4) //=> 入棧 4
console.log(stack.isEmpty()) //=> false
console.log(stack.size());   //=> 2
stack.clear();
console.log(stack.isEmpty()); //=> true

上面代碼只是簡單操做,這裏就不一一介紹了.code

棧的應用

下面經過幾個實際的例子來增長咱們對棧的理解:blog

進制轉換

把一個數字從一個基數轉爲另一個基數. 給定一個數字 n, 要把它轉爲基數b, 下面大概的步驟:

  1. n % b 取餘推入棧中
  2. n / b 取模後的值替換 n 值
  3. 重複步驟一、2,直到 n = 0
  4. 經過出棧操做獲取轉換後字符串

進制轉換,經過棧很是容易實現,下面大概實現方式

function baseConverter(num, base) {
    const stack = new Stack()
    const digits = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
    if(!(base >= 2 && base <= 32)){
       return '';
    }
    do {
        stack.push(num % base)
        num = Math.floor(num / base)
    } while (num > 0)
    let converted = ''
    while (!stack.isEmpty()) {
        converted += digits[stack.pop()]
    }
    return converted;
}

下面咱們來測試下: 把一個數字轉爲二進制、八進制

let num = 32
let base = 2
let converted = baseConverter(num, base)
console.log(`${num} converted to base ${base} is ${converted}`)

num = 125
base = 8
converted = baseConverter(num, base)
console.log(`${num} converted to base ${base} is ${converted}`)

運行輸出後結果爲:

32 converted to base 2 is 100000
125 converted to base 8 is 175

迴文

什麼是迴文:

迴文是先後拼寫相同的單詞、短語或數字。例如,「dad」是一個迴文;「racecar」是一個迴文; 1001是一個數字迴文;

咱們可使用堆棧來肯定給定的字符串是不是迴文. 拿到

  • 把字符串中的每一個字符壓入到棧中
  • 經過 pop() 方法生成新的字符串
  • 拿原字符串與新生成的字符串比較,相同則爲「迴文」

image

下面經過代碼實現判斷迴文的方法:

function isPalindrome(word) {
    const stack = new Stack()
    for (let i = 0; i < word.length; i++) {
        stack.push(word[i])
    }
    let rword = ''
    while (!stack.isEmpty()) {
        rword += stack.pop()
    }
    return word === rword
}
console.log(isPalindrome('racecar')) // true
console.log(isPalindrome('hello'))   // false

注意
實現判斷迴文方式不少, 這裏只是列舉經過棧如何實現

用堆棧模擬遞歸過程

演示如何用堆棧實現遞歸,就以階乘爲例.

5!= 5 * 4 * 3 * 2 * 1 = 120

先用遞歸方法實現, 具體以下:

function factorial(n) {
    if (n === 1) {
        return 1
    } else {
        return n * factorial(n-1)
    }
}

👇使用堆棧模擬遞歸過程:

function fact(n) {
    const stack = new Stack()
    while (n > 1) {
        stack.push(n--)
    }

    let product = 1
    while (!stack.isEmpty()) {
        product *= stack.pop()
    }
    return product
}

棧屬性私有化

私有化實際就是隱藏內部細節,只對外提供操做方法,這樣能保證代碼安全性. 下面經常使用方式:

  • 命名約定方式
class Stack {
    constructor() {
        this._items = []
    }
}

這種方式只是一種約定並不能起到保護做用,並且只能依賴使用咱們代碼的開發者所具有的常識.

  • 藉助ES模塊做用域和 Symbol 屬性
const _items = Symbol('stackItems')
class Stack {
    constructor() {
        this[_items] = []
    }
}
//...

}
這種方式其實建立一個假的私有屬性, ES2015新增的Object.getOwnPropertySymbols方法能取到類裏面聲明的全部 Symbol 屬性, 下面是破壞Stack類的例子

const stack = new Stack()\
stack.push(1)
stack.push(2)

let objectSymbols = Object.getOwnPropertySymbols(stack)
console.log(objectSymbols.length) // 1
console.log(objectSymbols) // [ Symbol(stackItems) ]
console.log(stack[objectSymbols[0]].push(1))
console.log(stack.print()) // 1,2,1
  • 利用 WeakMap 來實現是私有化
const items = new WeakMap()
class Stack {
    constructor() {
        items.set(this, [])
    }
    push(ele) {
        const s = items.get(this)
        s.push(ele)
    }
    pop() {
        const s = items.get(this)
        const r = s.pop()
        return r
    }
}

items 在 Stack 類裏是真正的私有屬性. 採用這種方式帶來問題代碼可讀性不強,並且擴展該類時沒法繼承私有屬性.

  • ECMAScript 類屬性提案

雖然在 TypeScrpit 中存在經過 private 修飾符來定義私有變量, 可是這隻能編譯時纔有效,在編譯後仍是能夠被外部訪問.

在ES草案中提供以下方式進行聲明私有化屬性

class Stack {
    #items = [];
    push(ele) {
        // 訪問
        this.#items.push(ele)
    }
}

詳細

相關文章
相關標籤/搜索