0x00 棧

棧是一種操作受限的線(xiàn)性表柜裸,只允許一端插入和刪除數(shù)據(jù)炼吴,相比數(shù)組和鏈表鹿鳖,棧的操作只有限制

事實(shí)上扁眯,在功能上數(shù)組和鏈表確實(shí)可以替代棧,棧的底層一般也是數(shù)組或鏈表實(shí)現(xiàn)的翅帜,但是

特定的數(shù)據(jù)結(jié)構(gòu)是對(duì)特定場(chǎng)景的抽象姻檀,而且數(shù)組或鏈表暴露了太多的接口,操作上靈活自由涝滴,但使用起來(lái)也比較不可控绣版,很容易出錯(cuò)

當(dāng)某個(gè)數(shù)據(jù)只涉及在一端插入或刪除數(shù)據(jù),并且滿(mǎn)足先進(jìn)后出歼疮、后進(jìn)先出的特性杂抽,我們就應(yīng)首選棧

0x01 棧實(shí)現(xiàn)

  • 數(shù)組實(shí)現(xiàn)
// n:棧的大小 items:棧中的元素 count:棧中元素的個(gè)數(shù)
case class ArrayStack(n:Int,items:Array[String]) {
  // 棧中元素個(gè)數(shù)
  var count=0
  // 入棧
  def push(item:String): Boolean ={
    // 棧滿(mǎn)了,返回false
    if (count == n){
      return false
    }
    items(count) = item
    count += 1
    true
  }
  // 出棧
  def pop(): String ={
    // 棧為空 返回null
    if (count == 0){
      return null
    }
    count -= 1
    items(count)
  }
  // 清空棧
  def clear(): Unit ={
    count = 0
  }
}
  • 鏈表實(shí)現(xiàn)
class LinkedStack {
  
  var head:Option[Node] = None
  var size:Int = 0
  // 入棧
  def push(value:Int): Boolean ={
    head = Option(Node(value,head))
    size += 1
    true
  }
  // 出棧
  def pop(): Option[Node] ={
    if (size==0){
      return None
    }
    val item = head
    head = head.get.next
    size -=1
    item
  }
  // 清空棧
  def clear(): Unit ={
    size = 0
    head = None
  }
}

0x02 應(yīng)用

  • 函數(shù)調(diào)用棧

操作系統(tǒng)給每個(gè)線(xiàn)程分配了獨(dú)立的內(nèi)存空間韩脏,這塊內(nèi)存被組織成棧這種數(shù)據(jù)結(jié)構(gòu)缩麸,用來(lái)保存函數(shù)調(diào)用的臨時(shí)變量,當(dāng)一個(gè)函數(shù)調(diào)用完成赡矢,就全部彈棧杭朱,然后執(zhí)行下一個(gè)函數(shù),以此來(lái)維護(hù)函數(shù)調(diào)用鏈

  • 四則運(yùn)算

我們?nèi)顺R?jiàn)的四則運(yùn)算吹散,例如 1-3*4+4/2 對(duì)于電腦來(lái)說(shuō)并不好識(shí)別執(zhí)行的先后順序弧械,一般會(huì)使用棧來(lái)計(jì)算,專(zhuān)欄中介紹了一種依靠?jī)蓚€(gè)棧實(shí)現(xiàn)的方式空民,還有一種逆波蘭表達(dá)式也叫后綴表達(dá)式的方法求解

1刃唐、兩個(gè)棧實(shí)現(xiàn)

一個(gè)棧存儲(chǔ)數(shù)字,一個(gè)棧存儲(chǔ)運(yùn)算符,從左到右遍歷運(yùn)算式唁桩,如果是數(shù)字就壓棧闭树,如果是運(yùn)算符,就與運(yùn)算符棧的棧頂元素比較荒澡,如果比棧頂運(yùn)算符優(yōu)先級(jí)高报辱,就壓棧,如果優(yōu)先級(jí)低或相同单山,則從數(shù)字棧取兩個(gè)數(shù)字碍现,從棧頂取一個(gè)操作符計(jì)算,并將結(jié)果壓入數(shù)字棧米奸,然后繼續(xù)比較棧頂運(yùn)算符和當(dāng)前運(yùn)算符的優(yōu)先級(jí)

github代碼

不包含括號(hào)的版本昼接,如果包含小括號(hào),可以把右括號(hào)優(yōu)先級(jí)設(shè)為最小悴晰,左括號(hào)優(yōu)先級(jí)設(shè)為最大慢睡,然后特殊處理一下左右括號(hào)
// 只有+-*/ 不包含括號(hào)
def caculate(express:String): Long ={
    val addInt = '+'.toInt
    val minusInt = '-'.toInt
    val multiInt = '*'.toInt
    val divideInt = '/'.toInt
    val symbolMap = Map(addInt->1,minusInt->1,multiInt->2,divideInt->2)
    val numStack = new LinkedStack
    val symbolStack = new LinkedStack
    // 前一個(gè)是否是數(shù)字
    var preIfNum = true
    // 數(shù)字棧棧頂放一個(gè)0 表達(dá)式第一個(gè)一定是數(shù)字,這樣不用對(duì)頭做特殊處理
    numStack.push(0)
    for (s <- express){
      var i = s.toInt - 48
      // 0-9
      if(i >= 0 && i<=9){
        // 如果前一個(gè)字符也是數(shù)字铡溪,那么將前一個(gè)*10加上當(dāng)前數(shù)字然后入棧
        if (preIfNum){
          i += 10*numStack.pop().get.data
        }
        numStack.push(i)
        preIfNum=true
      }else{
        preIfNum = false
        var preSymbol = symbolStack.pop()
        while (preSymbol.isDefined){
          // 如果當(dāng)前的運(yùn)算符優(yōu)先級(jí)小于等于上一個(gè)運(yùn)算符漂辐,則計(jì)算上一個(gè)運(yùn)算符
          if (symbolMap(preSymbol.get.data) >= symbolMap(s.toInt)){
            val a = numStack.pop().get.data
            val b = numStack.pop().get.data
            val res = preSymbol.get.data match {
              case `addInt` => b+a
              case `minusInt` => b-a
              case `multiInt` => b*a
              case `divideInt` => b/a
            }
            numStack.push(res)
            preSymbol = symbolStack.pop()
          }else{
            // 放回彈出的上一個(gè)運(yùn)算符,跳出循環(huán)
            symbolStack.push(preSymbol.get.data)
            preSymbol = None
          }
        }
        // 將當(dāng)前運(yùn)算符放入棧
        symbolStack.push(s.toInt)
      }
    }
    // 計(jì)算棧中剩下的數(shù)據(jù)
    var symbol = symbolStack.pop()
    while (symbol.isDefined){
      val a = numStack.pop().get.data
      val b = numStack.pop().get.data
      val res = symbol.get.data match {
        case `addInt` => b+a
        case `minusInt` => b-a
        case `multiInt` => b*a
        case `divideInt` => b/a
      }
      numStack.push(res)
      symbol = symbolStack.pop()
    }
    numStack.pop().get.data
 }

2棕硫、逆波蘭表達(dá)式

逆波蘭表達(dá)式又叫后綴表達(dá)式髓涯,這種方法是先將我們常見(jiàn)的中綴表達(dá)式轉(zhuǎn)換成后綴表達(dá)式,例如:(2+3)*5 => 2 3 + 5 * 運(yùn)算符在兩個(gè)計(jì)算數(shù)字后面哈扮,這樣方便計(jì)算機(jī)程序判斷計(jì)算纬纪,使用一個(gè)棧就可以完成,詳細(xì)代碼見(jiàn)GitHub代碼的caculate3方法

參考中綴表達(dá)式轉(zhuǎn)換為后綴表達(dá)式

  • 括號(hào)匹配

常見(jiàn)的算法題中還有括號(hào)匹配滑肉,小括號(hào)包各、中括號(hào)、打括號(hào)靶庙,任意組合问畅,判斷是夠匹配,例如:[{()}([])]匹配惶洲,可以通過(guò)棧來(lái)實(shí)現(xiàn)
GitHub

 def isValid(s:String): Boolean ={
    val bracket = Map('('->')','['->']','{'->'}')
    val stack = mutable.Stack[Char]()
    for (c <- s){
      if (bracket.contains(c)){
        stack.push(c)
      }else{
        if(stack.isEmpty || c != bracket(stack.pop())){
          return false
        }
      }
    }
    if (stack.nonEmpty){
      return false
    }
    true
 }

0x03 課后思考

1按声、我們?cè)谥v棧的應(yīng)用時(shí),講到用函數(shù)調(diào)用棧來(lái)保存臨時(shí)變量恬吕,為什么函數(shù)調(diào)用要用棧來(lái)保存臨時(shí)變量签则,用其他數(shù)據(jù)結(jié)構(gòu)行不行?

保存臨時(shí)變量可以用很多種數(shù)據(jù)結(jié)構(gòu)铐料,使用棧只是因?yàn)榉舷冗M(jìn)后出的場(chǎng)景渐裂,對(duì)于函數(shù)調(diào)用來(lái)說(shuō)豺旬,從調(diào)用函數(shù)進(jìn)入被調(diào)用函數(shù),最主要的變化是作用域柒凉,所以只要能保證進(jìn)入調(diào)用函數(shù)后能方便的切換作用域就可以族阅,而當(dāng)進(jìn)入被調(diào)用函數(shù)時(shí),使用棧存儲(chǔ)新的函數(shù)變量膝捞,當(dāng)函數(shù)調(diào)用完成坦刀,將棧頂復(fù)位,正好可以直接回到調(diào)用函數(shù)

2蔬咬、java內(nèi)存管理中有堆棧的概念鲤遥,棧內(nèi)存用來(lái)存儲(chǔ)局部變量和方法調(diào)用,堆內(nèi)存用來(lái)存儲(chǔ)java中的對(duì)象林艘,java中的棧和我們數(shù)據(jù)結(jié)構(gòu)中的棧是不是一回事荤牍?如果不是為什么也稱(chēng)為棧呢峰尝?

按我的理解破加,java內(nèi)存管理中的棧和椖晨穑可以是一回事,因?yàn)閮?nèi)存管理中的棧也是一種組織內(nèi)存數(shù)據(jù)的方式啥酱,滿(mǎn)足先進(jìn)后出的實(shí)現(xiàn)爹凹,但是,可能提供了更多的功能懈涛,所以可以認(rèn)為是一回事

節(jié)選專(zhuān)欄評(píng)論區(qū)小鄧的評(píng)論:

課后思考問(wèn)題2是多次困擾我的一個(gè)問(wèn)題:內(nèi)存管理上的“椆渫颍”和數(shù)據(jù)結(jié)構(gòu)上的“椨锯”到底是不是一回事批钠。
按出題套路而言,問(wèn)“如果不是得封,給出理由”的問(wèn)題答案大多是“不是”埋心。高贊留言里有的說(shuō)是,有的說(shuō)不是忙上,我簡(jiǎn)單翻了翻三大本厚書(shū)《C#圖接教程》拷呆、《算法》、《深入理解計(jì)算機(jī)基礎(chǔ)》疫粥,再去翻了翻相關(guān)博客和知乎茬斧,我的回答是“不是”。
(1)首先梗逮,在內(nèi)存管理和數(shù)據(jù)結(jié)構(gòu)上项秉,都有“堆”和“棧”這兩個(gè)概念慷彤。
(2)內(nèi)存管理上的“堆”和“椔Π”怖喻,強(qiáng)調(diào)的是數(shù)據(jù)的生命周期(分配釋放是否有次序要求);數(shù)據(jù)結(jié)構(gòu)上的“堆”和“椝晁撸”锚沸,強(qiáng)調(diào)的是數(shù)據(jù)的組織。
(3)內(nèi)存管理上的“椞檠ⅲ”符合數(shù)據(jù)結(jié)構(gòu)的“椈冢”的特點(diǎn),即LIFO坠韩,兩者同名可以接受恬叹。
(4)數(shù)據(jù)結(jié)構(gòu)上的“堆”定義:它是一種數(shù)組對(duì)象,它可以被視為一科完全二叉樹(shù)結(jié)構(gòu)同眯;應(yīng)用場(chǎng)景包括堆排序绽昼,優(yōu)先隊(duì)列等。和內(nèi)存管理的“堆”定義不同(這里我不是很確定须蜗,沒(méi)有看過(guò)內(nèi)存管理上堆的實(shí)現(xiàn)細(xì)節(jié))硅确。

參考:
(1)數(shù)據(jù)結(jié)構(gòu)之“堆”:http://www.cnblogs.com/Jason-Damon/archive/2012/04/18/2454649.html
(2)專(zhuān)題 | 堆、棧簡(jiǎn)介:https://zhuanlan.zhihu.com/p/45597548
(3)知乎| 為什么c++中要分為heap(堆)和stack(棧)? - Milo Yip的回答:https://www.zhihu.com/question/281940376/answer/424990646
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末明肮,一起剝皮案震驚了整個(gè)濱河市菱农,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌柿估,老刑警劉巖循未,帶你破解...
    沈念sama閱讀 210,978評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異秫舌,居然都是意外死亡的妖,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門(mén)足陨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)嫂粟,“玉大人,你說(shuō)我怎么就攤上這事墨缘⌒呛纾” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,623評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵镊讼,是天一觀的道長(zhǎng)宽涌。 經(jīng)常有香客問(wèn)我,道長(zhǎng)蝶棋,這世上最難降的妖魔是什么卸亮? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,324評(píng)論 1 282
  • 正文 為了忘掉前任,我火速辦了婚禮嚼松,結(jié)果婚禮上嫡良,老公的妹妹穿的比我還像新娘锰扶。我一直安慰自己,他們只是感情好寝受,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布坷牛。 她就那樣靜靜地躺著,像睡著了一般很澄。 火紅的嫁衣襯著肌膚如雪京闰。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,741評(píng)論 1 289
  • 那天甩苛,我揣著相機(jī)與錄音蹂楣,去河邊找鬼。 笑死讯蒲,一個(gè)胖子當(dāng)著我的面吹牛痊土,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播墨林,決...
    沈念sama閱讀 38,892評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼赁酝,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了旭等?” 一聲冷哼從身側(cè)響起酌呆,我...
    開(kāi)封第一講書(shū)人閱讀 37,655評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎搔耕,沒(méi)想到半個(gè)月后隙袁,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,104評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡弃榨,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年菩收,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片惭墓。...
    茶點(diǎn)故事閱讀 38,569評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡坛梁,死狀恐怖而姐,靈堂內(nèi)的尸體忽然破棺而出腊凶,到底是詐尸還是另有隱情,我是刑警寧澤拴念,帶...
    沈念sama閱讀 34,254評(píng)論 4 328
  • 正文 年R本政府宣布钧萍,位于F島的核電站,受9級(jí)特大地震影響政鼠,放射性物質(zhì)發(fā)生泄漏风瘦。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評(píng)論 3 312
  • 文/蒙蒙 一公般、第九天 我趴在偏房一處隱蔽的房頂上張望万搔。 院中可真熱鬧胡桨,春花似錦、人聲如沸瞬雹。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,725評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)酗捌。三九已至呢诬,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間胖缤,已是汗流浹背尚镰。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,950評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留哪廓,地道東北人狗唉。 一個(gè)月前我還...
    沈念sama閱讀 46,260評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像涡真,于是被迫代替她去往敵國(guó)和親敞曹。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容