堆棧是隻能從列表的一端(稱爲頂部)訪問的元素列表. 一個常見的,現實生活中的例子就是自助餐廳的托盤堆. 托盤老是從頂部取走, 當托盤在清洗後放回堆棧時,它們被放置在堆棧的頂部. 堆棧被稱爲後進先出(LIFO)數據結構.git
因爲堆棧的後進先出特性, 當前不在棧頂都不能被訪問的. 假如想訪問棧底的元素必須把上面的元素移除(托盤取走)github
棧是一種類隊列的數據結構, 能夠用解決許多計算問題. 同時棧是很是高效的一種數據結構,因數據新增、刪除都只能從棧頂完成, 而且容易以及快速實現.數組
因爲棧後入先出的特性,任何非棧頂元素不能被訪問. 要想訪問棧底的元素必須把上面的元素所有出棧才能訪問到.安全
下面棧的經常使用的兩個操做,元素入棧、出棧. 使用push
操做將元素添加到堆棧中, 使用pop
操做從堆棧中取出元素. 下圖所示數據結構
堆棧另外一個常見操做是查看堆棧頂部的元素. 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
, 下面大概的步驟:
進制轉換,經過棧很是容易實現,下面大概實現方式
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是一個數字迴文;
咱們可使用堆棧來肯定給定的字符串是不是迴文. 拿到
下面經過代碼實現判斷迴文的方法:
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 = [] } }
這種方式只是一種約定並不能起到保護做用,並且只能依賴使用咱們代碼的開發者所具有的常識.
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
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 類裏是真正的私有屬性. 採用這種方式帶來問題代碼可讀性不強,並且擴展該類時沒法繼承私有屬性.
雖然在 TypeScrpit 中存在經過 private
修飾符來定義私有變量, 可是這隻能編譯時纔有效,在編譯後仍是能夠被外部訪問.
在ES草案中提供以下方式進行聲明私有化屬性
class Stack { #items = []; push(ele) { // 訪問 this.#items.push(ele) } }