五分鐘玩轉(zhuǎn)面試考點-數(shù)據(jù)結(jié)構(gòu)-二叉樹的遍歷(人之路徑,根之輸出)

引子:五分鐘玩轉(zhuǎn)面試考點-數(shù)據(jù)結(jié)構(gòu)系列梧田,不會像那種嚴肅淳蔼、古板的教科書般的博客文章,而是將晦澀難懂的概念和知識點盡可能幽默的細說出來裁眯,或結(jié)合生活場景鹉梨,或從零開始分析。帶給大家一個嚴肅而不失風趣的數(shù)據(jù)結(jié)構(gòu)穿稳。

說到二叉樹存皂,大家是不是第一個想起來的就是二叉樹的遍歷?
好吧逢艘,不管大家是不是旦袋,反正我是...

下面就直接進入正題吧:

首先樹的遍歷方法有:前序遍歷、中序遍歷它改、后序遍歷疤孕、層級遍歷。那這么多遍歷方法央拖,他們之間有什么聯(lián)系祭阀?

人之路徑,根之輸出和二叉樹遍歷有什么關聯(lián)鲜戒?


二叉樹的遍歷的口訣:
前序遍歷:根左右
中序遍歷:左根右
后序遍歷:左右根

于是柬讨,我按照這個口訣,使用遞歸法迭代法二叉樹進行了花式遍歷袍啡。于是就有了自己的一些看法:

  1. 無論是前、中却桶、后序遍歷境输,其實遍歷的路徑都是相同的。好像是一個人型颖系,(從根節(jié)點出發(fā)嗅剖,一直到二叉樹左下位置的節(jié)點的左孩子節(jié)點(null)此為一撇,然后再遍歷其右孩子節(jié)點(null)此為一捺)嘁扼。
  2. 遍歷的區(qū)別信粮,在于輸出根時機不同,正如口訣所說:
    前序遍歷:"根左右"就是第一次執(zhí)行到根節(jié)點的時候(此時未遍歷孩子節(jié)點)趁啸,輸出根强缘;
    中序遍歷:"左根右"就是第二次執(zhí)行到根節(jié)點的時候(此時遍歷完左孩子節(jié)點)督惰,輸出根;
    后序遍歷:"根左右"就是第三次執(zhí)行到根節(jié)點的時候(此時遍歷完左右孩子節(jié)點)旅掂,輸出根赏胚;

由此我們可以看出,遍歷路徑是相同的商虐,輸出的都是根節(jié)點元素觉阅,只不過輸出時機不同!C爻怠典勇!

ps:什么?你不相信叮趴?那好割笙,咱們也寫一下代碼,看看是否符合口訣~


1. 遞歸法

1.1 前序遍歷

private static ArrayList<Integer> DLRTree(TreeNode root, ArrayList<Integer> list) {
        //返回條件是null的情況下疫向,遞歸出口咳蔚,原樣返回list數(shù)組
        if (root == null) {
            return list;
        }
        //遞歸開始【根 左 右】
        //遞歸邏輯
        list.add(root.val);  //list獲取數(shù)據(jù)
        DLRTree(root.left, list);  //遍歷左子樹
        DLRTree(root.right, list); //遍歷右子樹
        //遞歸返回式
        return list;
    }

心里默念:遞歸四要素

  1. 遞歸出口?
  2. 遞歸參數(shù)搔驼?
  3. 遞歸返回值谈火?
  4. 每次遞歸的處理邏輯?
  1. 遞歸出口:咱們是"人之路徑"舌涨。一撇在哪里終止糯耍?當左子樹為null的情況呀一捺在哪里終止囊嘉?當發(fā)現(xiàn)右子樹節(jié)點為null的情況下呀温技。所以root==null為遞歸出口。
  2. 遞歸參數(shù)父節(jié)點的左子樹節(jié)點或者右子樹節(jié)點扭粱。
  3. 遞歸返回值 :ArrayList(引用傳遞)舵鳞,可以充當返回值。
  4. 遞歸處理邏輯第一次訪問到根節(jié)點的時候輸出根琢蛤。

我想這個可以把他想成一個元素蜓堕,是不是好理解些:

image.png

1.2 中序遍歷

private static ArrayList<Integer> LDRTree(TreeNode root, ArrayList<Integer> list) {
       //返回條件是null的情況下,遞歸出口博其,原樣返回list數(shù)組
       if (root == null) {
           return list;
       }
       //遞歸開始【左 根 右】
       //遞歸邏輯
       LDRTree(root.left, list);  //遍歷左子樹
       list.add(root.val);  
       LDRTree(root.right, list); //遍歷右子樹
       //遞歸返回式
       return list;
   }

訪問完左子樹再次訪問根節(jié)點的時候套才,輸出根。

1.3 后序遍歷

private static ArrayList<Integer> LRDTree(TreeNode root, ArrayList<Integer> list) {
        //返回條件是null的情況下慕淡,遞歸出口背伴,原樣返回list數(shù)組
        if (root == null) {
            return list;
        }
        //遞歸開始【左 根 右】
        //遞歸邏輯
        LRDTree(root.left, list);  //遍歷左子樹
        LRDTree(root.right, list); //遍歷右子樹
        list.add(root.val);  //在
        //遞歸返回式
        return list;
    }

訪問完左右子樹再次訪問根節(jié)點的時候,輸出根。


2. 迭代法

PS:上面只是熱身運動傻寂,明白后息尺,咱們看看使用循環(huán)怎么遍歷二叉樹?我猜是使用崎逃!為啥掷倔?因為遞歸就是棧的思想...

當然,按照人之路徑个绍,將節(jié)點壓棧勒葱,此為一撇,直至節(jié)點是null巴柿,于是我們將節(jié)點出棧凛虽,找到"人"的連接點,完成一捺广恢。當然如果"捺"上面邏輯復雜凯旋,那么還是要走人之路徑的。什么時候結(jié)束操作钉迷?當最右節(jié)點完成出棧操作至非,棧中沒有數(shù)據(jù)。就是程序遍歷完畢糠聪。

上面說了根之輸出荒椭,那么第一次訪問根時,輸出根舰蟆,為前序遍歷趣惠。第二次訪問根時,輸出根身害,為中序遍歷味悄。第三次訪問根時,輸出根塌鸯,為后序遍歷侍瑟。

2.1 前序遍歷

 //棧前序遍歷二叉樹(后進先出)
    public static void stackDLRTree(TreeNode root) {
        //借助棧,先序遍歷丙猬,根 左 右  根在第一次訪問時丢习,獲取數(shù)據(jù)
        Stack<TreeNode> stack = new Stack<TreeNode>();
        while (root != null || !stack.isEmpty()) {
            //根打印的時機是  NO.1
            while (root != null) {
                //第一次訪問根節(jié)點,輸出并放入棧
                System.out.println("前序遍歷 : " + root.val);
                stack.push(root);  //將root節(jié)點放到棧中
                root = root.left; //指針下移一位
            }
            if (!stack.isEmpty()) {
                root = stack.pop();
                root = root.right;
            }
        }
    }

第一次將根壓棧的時候淮悼,輸出根。

2.2 中序遍歷

//中序遍歷(非遞歸)
    public static void stackLDRTree(TreeNode root) {
        //創(chuàng)建棧
        Stack<TreeNode> stack = new Stack<TreeNode>();
        //打印時機為左節(jié)點遍歷完畢之后
        //棧 Root元素第一個進去揽思,若是出來的話杆故,
        while (root != null || !stack.isEmpty()) {
            while (root != null) {
                //人——“撇”數(shù)據(jù)開始壓棧久锥,直到最 左下 節(jié)點(不為null)瓤逼。
                stack.push(root);
                root = root.left;
            }
            //人——“捺”回到再次根節(jié)點钧忽,準備訪問右節(jié)點。
            //左節(jié)點訪問完畢塑径,此時,可以訪問右節(jié)點
            root = stack.pop();
            System.out.println("中序遍歷:" + root.val);
            root = root.right;  //準備完成 捺,也是按照撇的條件執(zhí)行
        }
    }

壓棧的時候是第一次操作根節(jié)點酒来,那么根節(jié)點彈出棧的時候,就是第二次操作根節(jié)點肪凛。此時輸出堰汉,便是中序遍歷。


2.3 后序遍歷

PS:壓棧第一次伟墙,彈棧第二次翘鸭。emmm,那后序遍歷怎么才是第三次呢戳葵?數(shù)據(jù)都不在棧里面了就乓!

是的,此時一個節(jié)點棧已經(jīng)不能滿足我們的需求了拱烁,無法三次操作棧中數(shù)據(jù)生蚁,于是我們可以在引入一個計數(shù)棧。同步統(tǒng)計根節(jié)點被訪問的次數(shù)戏自。計數(shù)棧數(shù)據(jù)需要和節(jié)點棧數(shù)據(jù)的位置保持一致邦投。

public static void stackLRDTree(TreeNode root) {
        //節(jié)點棧
        Stack<TreeNode> stackNode = new Stack<TreeNode>();
        //計數(shù)棧
        Stack<Integer> stackInt = new Stack<Integer>();
        int count = 1;      //標志位
        //開始一撇
        while (root != null || !stackNode.empty()) {
            while (root != null) {
                //1. root第一次被訪問,同步計數(shù)棧為0浦妄;
                stackNode.push(root);
                stackInt.push(0);
                root = root.left;
            }
            //棧元素不為null尼摹,并且計數(shù)棧棧頂元素此時為1,此時是第三次被訪問到剂娄,
            while (!stackNode.empty() && stackInt.peek() == count) {
                //計數(shù)棧元素移除
                stackInt.pop();
                //獲取遍歷元素
                System.out.println("后序遍歷" + stackNode.pop().val + " ");
            }
            //準備遍歷右節(jié)點
            if (!stackNode.isEmpty()) {
                //2. root被pop時蠢涝,是第二次被訪問到,同步計數(shù)棧為1
                stackInt.pop();//移除元素0
                stackInt.push(1);
                //查看棧頂元素(但不移除)回到節(jié)點
                root = stackNode.peek();
                //準備一捺
                root = root.right;
            }
        }
    }

3. 層級遍歷

層級遍歷阅懦,其實使用的是隊列的數(shù)據(jù)結(jié)構(gòu)和二,將父元素輸出的時候,將子元素放入隊列中耳胎。后進后出惯吕。

//層級遍歷(使用隊列)
    public static void levelTree(TreeNode root) {
        if (root == null) {
            return;
        }
        TreeNode treeNode = null;
        //創(chuàng)建隊列
        Queue<TreeNode> queue = new LinkedList<TreeNode>();
        queue.add(root);  //將根節(jié)點保存到隊列中
        //輸出數(shù)據(jù)(隊列元素不空的情況下)
        while (queue.size() != 0) {
            treeNode = queue.poll();   //中間元素,保存信息
            System.out.println("層級遍歷 : " + treeNode.val + " ");
            if (treeNode.left != null) {
                //將指定的元素插入此隊列
                queue.offer(treeNode.left);
            }
            if (treeNode.right != null) {
                queue.offer(treeNode.right);
            }
        }
    }
層次遍歷題目描述.png

上圖的描述是將每一層的元素均放到一個集合中怕午,那么如何確定元素位于某一層中废登?

使用層次遍歷時,借助queue隊列郁惜。每一層進入時堡距,隊列中的元素總量便是該層的節(jié)點數(shù)。

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> data = new ArrayList<>();
          if(root==null){
            return data;
        }
        java.util.Queue<TreeNode> queue = new LinkedList<>();
        queue.add(root);
        while (queue.size() != 0) {
            //獲取該層節(jié)點的數(shù)量
            int count = queue.size();
            List<Integer> list = new ArrayList<>();
            //小循環(huán),開始遍歷該層羽戒。
            while (count > 0) {
                //取出最左邊元素
                TreeNode node = queue.poll();
                //加到list中
                list.add(node.val);
                if (node.left != null) {
                    queue.add(node.left);
                }
                if (node.right != null) {
                    queue.add(node.right);
                }
                count--;
            }
            data.add(list);
        }
        return data;
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末缤沦,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子易稠,更是在濱河造成了極大的恐慌缸废,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件驶社,死亡現(xiàn)場離奇詭異企量,居然都是意外死亡,警方通過查閱死者的電腦和手機衬吆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進店門梁钾,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人逊抡,你說我怎么就攤上這事姆泻。” “怎么了冒嫡?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵拇勃,是天一觀的道長。 經(jīng)常有香客問我孝凌,道長方咆,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任蟀架,我火速辦了婚禮瓣赂,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘片拍。我一直安慰自己煌集,他們只是感情好,可當我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布捌省。 她就那樣靜靜地躺著苫纤,像睡著了一般。 火紅的嫁衣襯著肌膚如雪纲缓。 梳的紋絲不亂的頭發(fā)上卷拘,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天,我揣著相機與錄音祝高,去河邊找鬼栗弟。 笑死,一個胖子當著我的面吹牛工闺,可吹牛的內(nèi)容都是我干的乍赫。 我是一名探鬼主播颓屑,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼耿焊!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起遍搞,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤罗侯,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后溪猿,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體钩杰,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年诊县,在試婚紗的時候發(fā)現(xiàn)自己被綠了讲弄。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡依痊,死狀恐怖避除,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情胸嘁,我是刑警寧澤瓶摆,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站性宏,受9級特大地震影響群井,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜毫胜,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一书斜、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧酵使,春花似錦荐吉、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至搓劫,卻和暖如春瞧哟,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背枪向。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工勤揩, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人秘蛔。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓陨亡,卻偏偏與公主長得像傍衡,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子负蠕,可洞房花燭夜當晚...
    茶點故事閱讀 44,724評論 2 354

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