二叉樹的遍歷

現(xiàn)在決定把自己很久以前的一些文章重新markdown一下,發(fā)到簡(jiǎn)書來(lái),先從這篇二叉樹的遍歷說(shuō)起的。
大家都知道二叉樹的遍歷分為前序遍歷,中序遍歷,后序遍歷。記得大學(xué)學(xué)習(xí)這一部分的時(shí)候,覺得用遞歸就可以輕易實(shí)現(xiàn),簡(jiǎn)直太簡(jiǎn)單了,所以也就沒有認(rèn)真學(xué),不過(guò)后來(lái)面試的時(shí)候考官要寫一下二叉樹的中序遍歷,而且不能用遞歸,當(dāng)時(shí)就很尷尬了,所以現(xiàn)在把二叉樹的每種遍歷思想和方法都記錄一下,做個(gè)備忘

遞歸的解法來(lái)解決前序中序和后續(xù)的遍歷

這簡(jiǎn)直是太簡(jiǎn)單不過(guò)了

遞歸前序遍歷

public static void preTranverse(TreeNode root){
  if(root != null){
    visit(root);
    preTranverse(root.leftChild);
    preTranverse(root.rightChild);
  }
}

是不是非常言簡(jiǎn)意賅,遍歷的時(shí)候先訪問(wèn)本身,然后遍歷左節(jié)點(diǎn),接著遍歷右節(jié)點(diǎn),而且簡(jiǎn)單易懂,接下來(lái)直接寫中序遍歷和后續(xù)遍歷,思路是一樣的

遞歸中序遍歷

public static void midTranverse(TreeNode root){
  if(root != null){
    midTranverse(root.leftChild);
    visit(root);
    midTranverse(root.rightChild);
  }
}

遞歸后續(xù)遍歷

public static void postTranverse(TreeNode root){
  if(root != null){
    postTranverse(root.leftChild);
    postTranverse(root.rightChild);
    visit(root);
  }
}

這三種寫法都簡(jiǎn)單明了,一看就懂。下面的重點(diǎn)是三種遍歷方式的非遞歸實(shí)現(xiàn)

要理解非遞歸實(shí)現(xiàn),我們就要先清楚三種遍歷的邏輯過(guò)程,以中序遍歷為例:

  • 想要訪問(wèn)節(jié)點(diǎn)p,就要先訪問(wèn)p的左子節(jié)點(diǎn)p.leftChild.
  • 想要訪問(wèn)p.leftChild,同樣也要先訪問(wèn)p的左子節(jié)點(diǎn)的子節(jié)點(diǎn),p.leftChild.leftChild
  • 依次類推,當(dāng)p的左子節(jié)點(diǎn)以及左子節(jié)點(diǎn)的左子節(jié)點(diǎn)都訪問(wèn)完的時(shí)候,我們才可以訪問(wèn)P。這個(gè)時(shí)候P的指向應(yīng)該是原來(lái)的root節(jié)點(diǎn)的最后一個(gè)左子節(jié)點(diǎn)
  • 當(dāng)訪問(wèn)完P(guān)的時(shí)候,我們要借助P來(lái)對(duì)P的右節(jié)點(diǎn)進(jìn)行訪問(wèn).
  • 不斷重復(fù)上一個(gè)過(guò)程,就可以中序遍歷完整個(gè)二叉樹

知道了這些,我們就可以大致寫出代碼了,代碼中有注釋,可以參考

public static void midTranverse(TreeNode root){
  TreeNode p = root;
  //stack用來(lái)存放我們第二和第三個(gè)步驟中遍歷到的左子節(jié)點(diǎn),我們要依靠這些左子節(jié)點(diǎn)來(lái)進(jìn)入到他們對(duì)應(yīng)的右樹中去
  Stack<TreeNode> s = new Stack();
  while(p!= null || !s.isEmpty()){
    //按照思路,先讓P節(jié)點(diǎn)的所有左子節(jié)點(diǎn)入棧
    while(p!=null){
      s.push(p);
      p = p.leftChild;
    }

    //這時(shí)候P應(yīng)該為空,那么我們只能根據(jù)棧內(nèi)是否有元素來(lái)判斷是否要出棧了
    if(!s.isEmpty()){
      //注意,這里的if不能寫成while,這樣的話相當(dāng)于全部出棧了,又回到了root節(jié)點(diǎn),但是此時(shí)我們僅僅遍歷了root節(jié)點(diǎn)左子樹的所有左節(jié)點(diǎn),還沒有遍歷左子樹的所有右節(jié)點(diǎn)
      p=s.pop();
      //可以訪問(wèn)p了,因?yàn)檫@時(shí)候P指向root左子樹的最后一個(gè)左子節(jié)點(diǎn)。
      visit(p);
      //雖然P是左子樹的最后一個(gè)左子節(jié)點(diǎn),但是P可能還會(huì)有右子節(jié)點(diǎn),如果有的話,進(jìn)入p的右子節(jié)點(diǎn)進(jìn)行遍歷,如果沒有的話也沒有關(guān)系,因?yàn)樵谙麓未笱h(huán)中P為空,還會(huì)繼續(xù)取出我們?cè)瓉?lái)?xiàng)?nèi)的元素進(jìn)行遍歷
      p=p.rightChild();
    }
  }
}

完整的代碼就是這樣,我們發(fā)現(xiàn)在外層循環(huán)的時(shí)候內(nèi)層還有循環(huán),這樣效率不是很高,不過(guò)經(jīng)過(guò)我們以上的分析,發(fā)現(xiàn)內(nèi)層循環(huán)其實(shí)是可以省略的,代碼如下

public static void midTranverse(TreeNode root){
  TreeNode p = root;
  Stack<TreeNode> s = new Stack();
  while(p!= null || !s.isEmpty()){
    if(p!=null){
      s.push(p);
      p=p.leftChild;
    }else{
      p = s.pop();
      visit(p);
      p=p.rightChild;
    }
  }
}

代碼簡(jiǎn)潔了很多,基本思路沒有變,都是當(dāng)p不為空或者棧中還有元素的時(shí)候,不斷循環(huán),當(dāng)p不為空的時(shí)候,我們要讓p入棧,并進(jìn)入p的左樹,當(dāng)p為空的時(shí)候,我們出棧,把棧頂元素賦給p,并進(jìn)入p的右樹。
這樣就可以完成整個(gè)二叉樹的中序遍歷了。

前序遍歷和中序遍歷流程差不多,只是visit調(diào)用的實(shí)際不一樣,前序遍歷是在進(jìn)入左樹之前就會(huì)調(diào)用visit方法,中序遍歷是在進(jìn)入右樹之前調(diào)用visit方法

非遞歸的后續(xù)遍歷

后續(xù)遍歷相比于前兩種遍歷困難的地方在于父節(jié)點(diǎn)必須在左右子樹都遍歷完的時(shí)候才能進(jìn)行訪問(wèn),我們需要有一個(gè)新的變量來(lái)記錄是否已經(jīng)訪問(wèn)完右子樹了。

public static void postTranverse(TreeNode root){
  TreeNode p = root;
  TreeNode lastVisited = null;
  Stack<TreeNode> s = new Stack();
  
  //我們?cè)谂袛嗍欠窨梢栽L問(wèn)一個(gè)節(jié)點(diǎn)時(shí),都需要確保該節(jié)點(diǎn)的左子樹和有子樹都已經(jīng)訪問(wèn)完了,為了能夠確定該節(jié)點(diǎn)的左右子樹都訪問(wèn)完了,先進(jìn)入他左子樹上每個(gè)節(jié)點(diǎn)是否都訪問(wèn)過(guò)
  while(p!=null){
    s.push(p);
    p=p.leftChild;
  }
  while(!s.isEmpty()){
    //取出棧頂元素
    p=s.pop();
    if(p.rightChild == null || p.rightChild == lastVisited){
      //當(dāng)p的右子樹為Null或者已經(jīng)被訪問(wèn)過(guò)的時(shí)候,我們才能訪問(wèn)p
      visit(p);
      lastVisited = p;
    }else{
      //棧頂?shù)脑噩F(xiàn)在右子樹沒有被訪問(wèn)過(guò),則再次入棧,先不訪問(wèn)該元素
      s.push(p);
      p=p.rightChild;
      //然后去判斷這個(gè)右子樹是否被訪問(wèn)過(guò)
      while(p!=null){
        s.push(p);
        p=p.leftChild;
      }
    }
  }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,460評(píng)論 6 538
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,067評(píng)論 3 423
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,467評(píng)論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,468評(píng)論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,184評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,582評(píng)論 1 325
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,616評(píng)論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,794評(píng)論 0 289
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,343評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,096評(píng)論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,291評(píng)論 1 371
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,863評(píng)論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,513評(píng)論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,941評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,190評(píng)論 1 291
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,026評(píng)論 3 396
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,253評(píng)論 2 375

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

  • 樹的概述 樹是一種非常常用的數(shù)據(jù)結(jié)構(gòu),樹與前面介紹的線性表,棧,隊(duì)列等線性結(jié)構(gòu)不同,樹是一種非線性結(jié)構(gòu) 1.樹的定...
    Jack921閱讀 4,473評(píng)論 1 31
  • 二叉樹的三種常用遍歷方式 學(xué)習(xí)過(guò)數(shù)據(jù)結(jié)構(gòu)的同學(xué)都清楚,除了層序遍歷外,二叉樹主要有三種遍歷方式: 1. 先序遍歷...
    SherlockBlaze閱讀 1,243評(píng)論 0 4
  • 樹(tree)是一種抽象數(shù)據(jù)類型(ADT)或是實(shí)作這種抽象數(shù)據(jù)類型的數(shù)據(jù)結(jié)構(gòu),用來(lái)模擬具有樹狀結(jié)構(gòu)性質(zhì)的數(shù)據(jù)集合。...
    曾大穩(wěn)丶閱讀 1,019評(píng)論 0 1
  • 二叉樹的遍歷想必大家都不陌生,主要有三種遍歷方式:前序遍歷(pre-order traversal),中序遍歷(i...
    akak18183閱讀 1,132評(píng)論 0 1
  • 前中后序的遞歸實(shí)現(xiàn) 前中后序的非遞歸標(biāo)準(zhǔn)實(shí)現(xiàn) 總結(jié) 整體的思路是這樣的: 指針p指向root,創(chuàng)建棧 當(dāng)棧不為空或...
    熊白白閱讀 386評(píng)論 0 0