JS版數據結構第一篇(棧)

前端入行門檻低,人員良莠不齊javascript

前端就是寫頁面的前端

前端的人都不懂數據結構和算法


背景

相信你們在社區常常會聽到相似以上的話java

因爲前端上手比較快,並且平時開發時大部分寫的都是業務邏輯以及交互,經常致使咱們被一些後端人員'鄙視',這無疑是對咱們前端開發人員是不公平的,前端技術更新迭代很快,並且知識點瑣碎,想要學好前端是須要必定的持續性學習能力以及創造性和好奇心的,學好前端並非一件很容易的事情。程序員

咱們都知道 程序設計=算法+數據結構
算法

不管是前端仍是後端,做爲開發人員對這些基礎知識的掌握程度決定了你之後的技術道路發展的上限,算法和數據結構對程序員的重要程度不言而喻。後端

拋開一些偏見,咱們不可否認的是:數組

  1. 有不少非科班出身的前端同窗對數據結構的理解並非很深,甚至一些數據結構類型的定義都不清楚。
  2. 網上利用JS實現的數據結構的資料有限。

針對這種實際狀況,我將分六篇博客介紹最多見的幾種數據結構,並結合LeetCode上面的經典原題利用js方法進行實際題目的求解,包括: bash

  • 隊列
  • 鏈表
  • 矩陣
  • 二叉樹

若是你也是一名想夯實一下數據結構基礎的前端開發人員,在網上又找不到合適的資源的話,那麼小弟的博客必定會對你有所幫助。數據結構

棧(stack)

棧的定義

做爲數據結構中最簡單的一種想必你們對棧都有所瞭解,咱們先來看一下百度百科上'棧'的定義架構


其實說白了就是咱們平時講的'先進後出',只能從一端(棧頂)添加或刪除數據。定義仍是很抽象,我仍是用一個例子比喻一下我理解中的‘棧’。

棧的理解


我這裏用抖音上最近比較火的彩虹酒來舉例:

若是你去酒吧點了這杯彩虹酒,酒吧小哥哥會先拿來一個乾淨的空杯子(此時是空棧的狀態)

杯口稱爲棧頂,杯底稱爲棧底,給你製做這杯酒時他會

  1. 先倒入紅色的'烈焰紅脣',
  2. 而後是黃色的‘杏仁’雞尾酒,
  3. 最後倒入'藍色夏威夷',
這就是一個 入棧的過程,

而當你拿到這杯酒時,

  1. 將先會品嚐到藍色妖姬帶來的清爽,
  2. 而後是杏仁的清香,
  3. 最後陶醉在紅色海洋裏,
這也就是一個 出棧的過程。

注意:調酒師給你製做酒時他只會從杯子口將酒倒入杯中,你也不會在杯底打一個洞而後將酒倒入你的嘴裏。

只能從杯口(棧頂)進只能從杯口(棧頂)出,這也就是上面定義中指的'受限'。

js實現一個簡單的棧

其實對於js來說實現棧再簡單不過了,咱們只須要定義一個數組,結合Array.prototype.push方法以及Array.prototype.pop方法就實現了。

如下六行代碼對應圖下的六個過程

var arr = [2,7,1]
arr.push(8) 
arr.push(2)
arr.pop()
arr.pop()
arr.pop()複製代碼



怎麼樣,是否是很簡單?

接下來咱們用LeetCode上的例題看一下利用棧的數據結構能夠解決怎樣的問題。

例題一:棒球比賽

咱們先來看一下題目  原題地址

你如今是棒球比賽記錄員。
給定一個字符串列表,每一個字符串能夠是如下四種類型之一:
1. 整數(一輪的得分):直接表示您在本輪中得到的積分數。
2. "+"(一輪的得分):表示本輪得到的得分是前兩輪 有效 回合得分的總和。
3. "D"(一輪的得分):表示本輪得到的得分是前一輪 有效 回合得分的兩倍。
4. "C"(一個操做,這不是一個回合的分數):表示您得到的最後一個 有效 回合的分數是無效的,應該被移除。

每一輪的操做都是永久性的,可能會對前一輪和後一輪產生影響。
你須要返回你在全部回合中得分的總和。

示例:

輸入: ["5","2","C","D","+"]
輸出: 30
解釋: 
第1輪:你能夠獲得5分。總和是:5。
第2輪:你能夠獲得2分。總和是:7。
操做1:第2輪的數據無效。總和是:5。
第3輪:你能夠獲得10分(第2輪的數據已被刪除)。總數是:15。
第4輪:你能夠獲得5 + 10 = 15分。總數是:30。複製代碼

分析一下這道題目:

先傳入一個數組,而後根據數組中每一項字符串的不一樣類型計算出真實得分,最後將結果求和。

咱們能夠根據‘棧’的數據結構分如下幾個步驟進行求解:

  1. 先新建一個空數組A(即空棧) 保存真實的得分狀況
  2. 而後遍歷傳入的數組B並根據它每一項的不一樣類型計算出每一輪的得分狀況
  • 若是該項是前三種類型(‘正數’,'+',‘D’) 則按要求計算該輪得分並插入到空數組A中(即入棧)
  • 若是該項是第四種類型('C'),則將數組A最後一項拋出(即出棧)
   3. 對數組A進行遍歷求和

代碼以下:

export default (arr) => {  
    // 新建空棧,保存處理後的結果 
        let result = []  
    // 上一輪的數據 
        let pre1  
    // 上上輪的數據 
        let pre2  
    // 對傳進來的數組進行遍歷,遍歷的目的是處理得分 
    arr.forEach(item => {    
        switch (item) {      
            case 'C':        
                if (result.length) {          
                    result.pop()        
                }        
                break      
            case 'D':        
                pre1 = result.pop()        
                result.push(pre1, pre1 * 2)       
                break      
            case '+':       
                pre1 = result.pop()       
                pre2 = result.pop()      
                result.push(pre2, pre1, pre2 + pre1)      
                break     
            default:
                // *1是爲了將item轉爲number類型 
                result.push(item * 1)    
        }  
    })
     //利用reduce進行求和 
     return result.reduce((prev, cur) =>  prev + cur )}複製代碼

例題二:最大矩形

原題地址

給定一個僅包含 0 和 1 的二維二進制矩陣,找出只包含 1 的最大矩形,並返回其面積。

示例:

輸入:
[
  ["1","0","1","0","0"],
  ["1","0","1","1","1"],
  ["1","1","1","1","1"],
  ["1","0","0","1","0"]
]
輸出: 6複製代碼

實現思路:

將二維數組arr1的每一項2個及以上連續1的索引範圍轉換成新的二維數組arr2,如

[  
    ['1', '1', '0', '1', '1'],
    ['1', '1', '1', '1', '1'],
    ['1', '1', '0', '1', '1']
]複製代碼

能夠轉換爲:

[ 
    [[0,1],[3,4]],
    [[0,4]],
    [[0,1],[3,4]]
]複製代碼

這樣的話咱們先寫一個函數,功能是查找二維數組裏面每相鄰兩項的交集,接受兩個參數,分別是須要求交集的數組和已經遍歷的行數。

函數內部這樣實現的:

函數先將接收的數組的最後兩項推出(出棧兩次),而後將兩項遍歷取交集,

  • 若是有交集則將取到的交集入棧,再次遞歸,傳入此時的數組和n
  • 沒有交集則將數組刪除最後一項(出棧),而後將新的數組傳入函數
每次取到交集時要根據交集數組的長度和已遍歷的行數計算此時矩形面積,最後將最大的矩形面積返回。

更多的註釋寫在代碼裏,我把源碼發出來供你們參考

export default arr => {
  // 用來保存處理事後的二維數組 
  let changeArr = []
  // 將傳入的二維數組根據每一項的連續1的索引位置遍歷 
  arr.forEach((item, i) => {
    //每一項的索引 
    let itemArr = []    
    let str = item.join('')    
    let reg = /1{2,}/g
    // 連續1的匹配結果 
    let execRes = reg.exec(str)
    //若是不爲null就繼續匹配 
    while (execRes) {
      // 將第i項匹配到的第j組1的起止位置推入 
      itemArr.push([execRes.index, execRes.index + execRes[0].length - 1])      
      execRes = reg.exec(str)    
    }
    // 將第i項的匹配結果推入 
    changeArr.push(itemArr)  
  })
  // 用來保存面積 
  let area = []  
  // 取交集函數 
  let find = (arr, n = 1) => {    
      // 淺拷貝arr數組 防止因爲引用致使每次執行函數被改變 
      let copyArr = [].concat(arr)    
      // 數組最後一項 
      let last = copyArr.pop()    
      // 數組倒數第二項 
      let next = copyArr.pop()
      // 最後一項和倒數第二項的每一項 
      let lastItem, nextItem
      // 取到的交集數組 
      let recuArr = []
      // 做爲每次取到交集的臨時變量 
      let item = []
      // 已遍歷的行數 
      n++
      // 將每相鄰兩項的每一個區間分別取交集 
      for (let i = 0; i < last.length; i++) {      
          lastItem = last[i]      
          for (let j = 0; j < next.length; j++) {        
            nextItem = next[j]        
            // 開始取交集 
            // 如有交集 
            if (!(Math.max(...lastItem) <= Math.min(...nextItem) ||                   
                    Math.max(...nextItem) <= Math.min(...lastItem))) {           
                item = [Math.max(lastItem[0], nextItem[0]), Math.min(lastItem[1], nextItem[1])]          
                recuArr.push(item)
            // 求此時交集的面積並推入area 
            area.push((item[1] - item[0] + 1) * n)        
            }      
         }    
      }    
      // 若遍歷完了全部狀況都沒有交集,則返回false
      if (recuArr.length === 0) {
         return false
      } else {
         // 有交集,繼續遞歸遍歷
         if (copyArr.length > 0) {
           copyArr.push(recuArr)
           find(copyArr, n)
         }
      }
    }
    //將數組一直遞減,每一行都做爲第一行找一次交集
    while (changeArr.length > 1) {
        find(changeArr)
        changeArr.pop()
    }
    //最後將保存面積的矩形數組的最大值返回
    return area.length === 0 ? 0 : Math.max(...area)
  }複製代碼

參考

  1. 百度百科
  2. 今日頭條視頻架構前端負責人銀國徽老師的《js數據結構與算法》
  3. leetCode

總結

做爲一種比較簡單的數據結構,棧在js中很常見,也比較容易理解,咱們能夠利用js原生的數組API就能夠實現。

下一篇我將介紹隊列的使用方法。

相關文章
相關標籤/搜索