重讀《學習JavaScript數據結構與算法-第三版》- 第4章 棧

定場詩

金山竹影幾千秋,雲索高飛水自流;
萬里長江飄玉帶,一輪銀月滾金球。
遠自湖北三千里,近到江南十六州;
美景一時觀不透,天緣有分畫中游。

前言

本章是重讀《學習JavaScript數據結構與算法-第三版》的系列文章,本章爲各位小夥伴分享數據結構-的故事,請讓胡哥帶你走進的世界javascript

何爲棧?棧是一種聽從後進先出(LIFO)原則的有序集合。前端

新添加或待刪除的元素都保存在棧的同一端,稱做棧頂;另外一端就叫棧底。

在棧裏,新元素都靠近棧頂,舊元素都接近棧底。java

基於數組的棧

咱們將建立一個基於數組的棧,瞭解棧的結構、運行規則
/**
 * 基於數組array的棧Stack
 * @author huxiaoshuai
 */
class Stack {
  // 初始化
  constructor () {
    this.items = []
  }
}
使用數組保存棧裏的元素

數組容許咱們在任何位置添加和刪除元素,那基於棧遵循LIFO原則,咱們對元素的插入和刪除功能進行封裝算法

方法 描述
push(element(s)) 添加一個(或多個)新元素到棧頂
pop() 移除棧頂元素,同時返回被移除的元素
peek() 返回棧頂的元素,不對棧作任何修改
isEmpty() 判斷棧是否爲空,爲空則返回true,不然返回false
clear() 移除棧的全部元素
size() 返回棧的元素個數

代碼實現

class Stack {
  // 初始化
  constructor () {
    this.items = []
  }

  /**
   * push() 添加元素到棧頂
   */ 
  push (element) {
    this.items.push(element)
  }

  /**
   * pop() 移除棧頂元素並返回
   */
  pop () {
    return this.items.pop()
  }

  /**
   * peek() 返回棧頂部元素
   */
  peek () {
    return this.items[this.items.length - 1]
  }

  /***
   * isEmpty() 檢測棧是否爲空
   */
  isEmpty () {
    return this.items.length === 0
  }

  /**
   * size() 返回棧的長度
   */
  size () {
    return this.items.length
  }
  
  /**
   * clear() 清空棧元素
   */
  clear () {
    this.items = []
  }
}

使用Stack類

const stack = new Stack()

console.log(stack.isEmpty()) // true

// 添加元素
stack.push(5)
stack.push(8)

// 輸出元素
console.log(stack.peek()) // 8

stack.push(11)
console.log(stack.size()) // 3
console.log(stack.isEmpty()) // false

stack.push(15)

基於以上棧操做的示意圖
基於以上棧操做的示意圖segmentfault

stack.pop()
stack.pop()
console.log(stack.size()) // 2

基於以上棧操做的示意圖
基於以上棧操做的示意圖數組

基於對象的棧

建立一個Stack類最簡單的方式是使用一個數組來存儲元素。在處理大量數據的時候,咱們一樣須要評估如何操做數據是最高效的。數據結構

使用數組時,大部分方法的時間複雜度是O(n)。簡單理解:O(n)的意思爲咱們須要迭代整個數組直到找到要找的那個元素,在最壞的狀況下須要迭代數組的全部位置,其中的n表明數組的長度。數組越長,所需時間會更長。另外,數組是元素的一個有序集合,爲保證元素的有序排列,會佔用更多的內存空間。架構

使用JavaScript對象存儲全部的棧元素,以實現能夠直接獲取元素,同時佔用較少的內存空間,同時保證全部的元素按照咱們的須要進行排列,遵循後進先出(LIFO)原則。框架

代碼實現

/**
 * 基於對象的Stack類
 * @author huxiaoshai
 */
class Stack {
  // 初始化
  constructor () {
    this.items = {}
    this.count = 0
  }

  /**
   * push() 向棧中添加元素
   */
  push (element) {
    this.items[this.count] = element
    this.count++
  }

  /**
   * isEmpty() 判斷是否爲空
   */
  isEmpty () {
    return this.count === 0
  }

  /**
   * size() 返回棧的長度
   */
  size () {
    return this.count
  }

  /**
   * pop() 棧頂移除元素並返回
   */
  pop () {
    if (this.isEmpty()) {
      return undefined
    }
    this.count--
    let result = this.items[this.count]
    delete this.items[this.count]
    return result
  }

  /**
   * peek() 返回棧頂元素,若是爲空則返回undefined
   */
  peek () {
    if (this.isEmpty()) {
      return undefined
    }
    return this.items[this.count - 1]
  }

  /**
   * clear() 清空棧數據
   */
  clear () {
    this.items = {}
    this.count = 0
  }

  /**
   * toString() 實現相似於數組結構打印棧內容
   */
  toString () {
    if (this.isEmpty()) {
      return ''
    }
    let objStr = `${this.items[0]}`
    for (let i = 1; i < this.count; i++) {
      objStr = `${objStr},${this.items[i]}`
    }
    return objStr
  }
}

保護數據結構內部元素

私有屬性

有時候咱們須要建立供其餘開發者使用的數據結構和對象時,咱們但願保存內部元素,只有使用容許的方法才能修改內部結構。很不幸,目前JS是沒有辦法直接聲明私有屬性的,目前業內主要使用一下幾種方式實現私有屬性。學習

  1. 下劃線命名約定

    class Stack {
      constructor () {
        this._items = {}
        this._count = 0
      }
    }
    這只是約定,一種規範,並不能實際保護數據
  2. 基於ES6的限定做用域Symbol實現類

    const _items = Symbol('stackItems')
    class Stack {
      constructor () {
        this[_items] = []
      }
    }
    假的私有屬性,ES6新增的Object.getOwnPropertySymbols方法可以獲取類裏面聲明的全部Symbols屬性
  3. 基於ES6的WeakMap實現類

    /**
*/
const items = new WeakMap()
console.log(items) // WeakMap { [items unknown] }

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

  push (element) {
    const s = items.get(this)
    s.push(element)
  }

  pop () {
    const s = items.get(this)
    const r = s.pop()
    return r
  }

  toString () {
    const s = items.get(this)
    return s.toString()
  }
}

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

console.log(stack.toString()) // 1,2,3
console.log(stack.items) // undefined
```
> 使用該方式,items是Stack類裏的私有屬性,可是此種方式代碼的可讀性不強,並且在擴展該類時沒法繼承私有屬性。
  1. ECMAScript類屬性提案

    有一個關於JavaScript類中增長私有屬性的提案。經過在屬性前添加井號(#)做爲前綴來聲明私有屬性。
    class Stack {
      #count = 0
      #items = []
    }

使用棧來解決問題

棧的實際應用很是普遍。在回溯問題中,它能夠存儲訪問過的任務或路徑、撤銷的操做(後續會在討論圖和回溯問題時進一步詳細講解)。棧的使用場景有不少,如漢諾塔問題、平衡圓括號、計算機科學問題:十進制轉二進制問題

/**
 * decimalToBinary() 實現十進制轉二進制的算法
 */ 
function decimalToBinary (decNumber) {
  // 實例化棧數據結構
  const remStack = new Stack()
  let number = decNumber
  let rem;
  let binaryString = ''

  // 依次將獲取的二進制數壓入棧中
  while (number > 0) {
    rem = Math.floor(number % 2)
    remStack.push(rem)
    number = Math.floor(number / 2)
  }
  // 拼接要輸出的二進制字符串
  while (!remStack.isEmpty()) {
    binaryString += remStack.pop().toString()
  }
  return binaryString
}

console.log(decimalToBinary(10)) // 1010
console.log(decimalToBinary(23)) // 10111

後記

以上就是胡哥今天給你們分享的內容,喜歡的小夥伴記得收藏轉發、點擊右下角按鈕在看,推薦給更多小夥伴呦,歡迎多多留言交流...

胡哥有話說,一個有技術,有情懷的胡哥!京東開放平臺首席前端攻城獅。與你一塊兒聊聊大前端,分享前端系統架構,框架實現原理,最新最高效的技術實踐!

長按掃碼關注,更帥更漂亮呦!關注胡哥有話說公衆號,可與胡哥繼續深刻交流呦!

胡哥有話說

相關文章
相關標籤/搜索