數(shù)據(jù)結(jié)構(gòu)之二叉樹(二)——二叉樹遍歷

前言

二叉樹作為一種非畴时迹基礎(chǔ)但十分重要的數(shù)據(jù)結(jié)構(gòu),在排序预吆、搜索龙填、編碼、甚至文件系統(tǒng)管理等方面都有廣泛應(yīng)用拐叉。今天岩遗,我想講講關(guān)于二叉樹遍歷的那些事兒。為了大家可以清晰地看懂下文中的代碼凤瘦,在此先定義二叉樹的數(shù)據(jù)結(jié)構(gòu)(使用java語言)宿礁。

    /**
     * 二叉樹數(shù)據(jù)結(jié)構(gòu)
     */
    private static class TreeNode {
        int val;
        TreeNode left = null;
        TreeNode right = null;

        public TreeNode(int val) {
            this.val = val;
        }
    }

深度優(yōu)先VS廣度優(yōu)先

大的方面來講呢,二叉樹有兩種遍歷方法——深度優(yōu)先遍歷和廣度優(yōu)先遍歷蔬芥。

  • 深度優(yōu)先:對每一個可能的分支路徑深入到不能再深入為止梆靖,而且每個結(jié)點只能訪問一次。要特別注意的是笔诵,二叉樹的深度優(yōu)先遍歷比較特殊返吻,可以細分為先序遍歷、中序遍歷乎婿、后序遍歷测僵。深度優(yōu)先遍歷非遞歸的通用做法是采用棧

  • 廣度優(yōu)先:從上往下對每一層依次訪問谢翎,在每一層中捍靠,從左往右(也可以從右往左)訪問結(jié)點,訪問完一層就進入下一層岳服,直到?jīng)]有結(jié)點可以訪問為止。廣度優(yōu)先遍歷非遞歸的通用做法是采用隊列希俩。

深度優(yōu)先遍歷

深度優(yōu)先遍歷根據(jù)根節(jié)點和左右子樹訪問的順序可以分為先序吊宋、中序和后序遍歷。

先序遍歷

先序遍歷:對任一子樹颜武,先訪問根璃搜,然后遍歷其左子樹,最后遍歷其右子樹鳞上。按照這個規(guī)則这吻,可以很容易的寫出先序遍歷的遞歸方法。

    /**
     * 先序遍歷二叉樹篙议,遞歸
     * @param root
     */
    public static void preOrderTraverse(TreeNode root){
        if(root == null){
            return;
        }
        System.out.print(root.val + " ");
        preOrderTraverse(root.left);
        preOrderTraverse(root.right);
    }

對于非遞歸方法唾糯,可以描述為:由根節(jié)點開始怠硼,不斷將二叉樹的節(jié)點壓入棧中,然后由棧中取出棧頂節(jié)點移怯,并且將該節(jié)點的右香璃、左(順序不可顛倒)子節(jié)點壓入棧中,循環(huán)此過程直至棧為空舟误。 該方法的java代碼如下:

    /**
     * 先序遍歷二叉樹葡秒,非遞歸
     * @param root
     */
    public static void preOrderTraverseNoRecursion(TreeNode root){
        LinkedList<TreeNode> stack = new LinkedList<TreeNode>();
        stack.push(root);
        while (!stack.isEmpty()){
            TreeNode currentNode = stack.pop();
            System.out.print(currentNode.val + " ");
            if (currentNode.right != null){
                stack.push(currentNode.right);
            }
            if (currentNode.left != null){
                stack.push(currentNode.left);
            }
        }

        System.out.print("\n");
    }

中序遍歷

中序遍歷:對任一子樹,先遍歷其左子樹嵌溢,然后訪問根眯牧,最后遍歷其右子樹。按照這個規(guī)則赖草,可以很容易的寫出中序遍歷的遞歸方法秒梳。

    /**
     * 中序遍歷二叉樹,遞歸
     * @param root
     */
    public static void inOrderTraverse(TreeNode root){
        if(root == null){
            return;
        }
        inOrderTraverse(root.left);
        System.out.print(root.val + " ");
        inOrderTraverse(root.right);
    }

而對于中序遍歷届巩,算法可描述為:首先峦朗,找到二叉樹中最左邊的子節(jié)點,并依次將節(jié)點壓入棧中腿堤,然后取出棧頂節(jié)點阀坏,并將該節(jié)點的右節(jié)點視為新的根節(jié)點,重復(fù)上述過程笆檀,直至節(jié)點為空或者棧為空忌堂,java代碼如下:

    /**
     * 中序遍歷二叉樹,非遞歸
     * @param root
     */
    public static void inOrderTraverseNoRecursion(TreeNode root){
        LinkedList<TreeNode> stack = new LinkedList<TreeNode>();
        TreeNode currentNode = root;
        while (currentNode != null || !stack.isEmpty()){
            // 一直循環(huán)到二叉樹最左端的葉子結(jié)點(currentNode是null)
            if (currentNode != null){
                stack.push(currentNode);
                currentNode = currentNode.left;
            } else{
                currentNode = stack.pop();
                System.out.print(currentNode.val + " ");
                currentNode = currentNode.right;
            }
        }
        System.out.print("\n");
    }

后序遍歷

后序遍歷:對任一子樹酗洒,先遍歷其左子樹士修,然后遍歷其右子樹,最后再訪問根樱衷。按照這個規(guī)則棋嘲,可以很容易的寫出后序遍歷的遞歸方法。

    /**
     * 后序遍歷二叉樹矩桂,遞歸
     * @param root
     */
    public static void postOrderTraverse(TreeNode root){
        if(root == null){
            return;
        }
        postOrderTraverse(root.left);
        postOrderTraverse(root.right);
        System.out.print(root.val + " ");
    }

對于后序遍歷的非遞歸算法沸移,可描述如下:首先,找到二叉樹中最左邊的子節(jié)點侄榴,并依次將節(jié)點壓入棧中雹锣,然后取出棧頂節(jié)點,判斷該節(jié)點是否存在右子節(jié)點或者該節(jié)點是否為其父節(jié)點的右子節(jié)點癞蚕,如果為真蕊爵,則輸出節(jié)點元素,并更新當(dāng)前節(jié)點和右子節(jié)點桦山,循環(huán)該過程直至判斷條件不滿足攒射,然后將該節(jié)點的右節(jié)點視為新的根節(jié)點醋旦,重復(fù)上述過程,直至節(jié)點為空或者棧為空匆篓,java代碼如下:

    /**
     * 后序遍歷二叉樹浑度,非遞歸
     * @param root
     */
    public static void postOrderTraverseNoRecursion(TreeNode root){
        LinkedList<TreeNode> stack = new LinkedList<TreeNode>();
        TreeNode currentNode = root;
        TreeNode rightNode = null;
        while (currentNode != null || !stack.isEmpty()){
            // 一直循環(huán)到二叉樹最左端的葉子結(jié)點(currentNode是null)
            while (currentNode != null){
                stack.push(currentNode);
                currentNode = currentNode.left;
            }

            currentNode = stack.pop();
            // 當(dāng)前結(jié)點沒有右結(jié)點或上一個結(jié)點(已經(jīng)輸出的結(jié)點)是當(dāng)前結(jié)點的右結(jié)點,則輸出當(dāng)前結(jié)點
            while (currentNode.right == null || currentNode.right == rightNode){
                System.out.print(currentNode.val + " ");
                rightNode = currentNode;
                if(stack.isEmpty()){
                    System.out.println();
                    return;
                }
                currentNode = stack.pop();
            }

            stack.push(currentNode);
            currentNode = currentNode.right;
        }
    }

廣度優(yōu)先遍歷

層次遍歷

層次遍歷指的是將二叉樹按層輸出鸦概。借助隊列可以很容易的實現(xiàn)層次優(yōu)先遍歷箩张。算法描述為:由根節(jié)點開始,不斷將二叉樹的節(jié)點送至隊列中窗市,然后由隊列中取出隊列頭節(jié)點先慷,并且將該節(jié)點的左、右子節(jié)點分別送至隊列中咨察,循環(huán)此過程直至隊列為空论熙。java代碼如下:

    /**
     * 普通層次遍歷
     * @param root
     */
    public static void levelTraverse(TreeNode root){
        LinkedList<TreeNode> queue = new LinkedList<TreeNode>();
        queue.add(root);

        while (!queue.isEmpty()){
            TreeNode currentNode = queue.poll();
            System.out.print(currentNode.val + " ");
            if (currentNode.left != null){
                queue.add(currentNode.left);
            }
            if (currentNode.right != null){
                queue.add(currentNode.right);
            }
        }
        System.out.println();
    }

層次遍歷進階

除了一般的層次遍歷,這里再介紹一種面試中成阌考的層次遍歷——按二叉樹的層次結(jié)構(gòu)分行輸出元素脓诡。要實現(xiàn)這種需求,只需要兩個指針記錄當(dāng)前行和下一行最右的節(jié)點即可媒役。java代碼如下:

    /**
     * 按行打印二叉樹
     * 算法描述:
     * 1. 初始化:將根節(jié)點傳入隊列祝谚,last、nlast指針均指向根節(jié)點酣衷,
     *    其中交惯,last指針指向當(dāng)前行最右側(cè)的元素,nlast指針指向下一行最右側(cè)的元素
     * 2. 循環(huán)判斷隊列是否為空穿仪,如果不為空席爽,則:
     *    2.1: 獲取隊列頭元素p(將其在隊列內(nèi)刪除)并打印
     *    2.2: 將該節(jié)點的左右子樹分別壓入隊列,并更新nlast指針
     *    2.3: 判斷l(xiāng)ast指針與p是否相等啊片,如果相等只锻,則換行,并且更新last = nlast
     * @param root
     * @return
     */
    public static void printTreeByRow(TreeNode root){
        LinkedList<TreeNode> queue = new LinkedList<TreeNode>();
        queue.add(root);
        
        TreeNode last = root;
        TreeNode nLast = null;
        
        while (!queue.isEmpty()){
            TreeNode p = queue.poll();
            System.out.print(p.val + " ");

            if (p.left != null){
                queue.add(p.left);
                nLast = p.left;
            }
            if (p.right != null){
                queue.add(p.right);
                nLast = p.right;
            }
            // 當(dāng)last == p時代表該換行了
            if(last == p){
                last = nLast;
                System.out.print("\n");
            }
        }
    }

總結(jié)

本篇博文主要介紹了二叉樹的遍歷方法——深度優(yōu)先和廣度優(yōu)先紫谷,掌握二叉樹的遍歷方法對于棧和隊列的使用也很有幫助齐饮。

推薦閱讀

寫在最后

歡迎大家關(guān)注我的個人博客復(fù)旦猿

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末碴里,一起剝皮案震驚了整個濱河市沈矿,隨后出現(xiàn)的幾起案子上真,更是在濱河造成了極大的恐慌咬腋,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,723評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件睡互,死亡現(xiàn)場離奇詭異根竿,居然都是意外死亡陵像,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評論 2 382
  • 文/潘曉璐 我一進店門寇壳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來醒颖,“玉大人,你說我怎么就攤上這事壳炎∨⑶福” “怎么了?”我有些...
    開封第一講書人閱讀 152,998評論 0 344
  • 文/不壞的土叔 我叫張陵匿辩,是天一觀的道長腰耙。 經(jīng)常有香客問我,道長铲球,這世上最難降的妖魔是什么挺庞? 我笑而不...
    開封第一講書人閱讀 55,323評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮稼病,結(jié)果婚禮上选侨,老公的妹妹穿的比我還像新娘。我一直安慰自己然走,他們只是感情好援制,可當(dāng)我...
    茶點故事閱讀 64,355評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著丰刊,像睡著了一般隘谣。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上啄巧,一...
    開封第一講書人閱讀 49,079評論 1 285
  • 那天寻歧,我揣著相機與錄音,去河邊找鬼秩仆。 笑死码泛,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的澄耍。 我是一名探鬼主播噪珊,決...
    沈念sama閱讀 38,389評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼齐莲!你這毒婦竟也來了痢站?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,019評論 0 259
  • 序言:老撾萬榮一對情侶失蹤选酗,失蹤者是張志新(化名)和其女友劉穎阵难,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體芒填,經(jīng)...
    沈念sama閱讀 43,519評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡呜叫,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,971評論 2 325
  • 正文 我和宋清朗相戀三年空繁,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片朱庆。...
    茶點故事閱讀 38,100評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡盛泡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出娱颊,到底是詐尸還是另有隱情傲诵,我是刑警寧澤,帶...
    沈念sama閱讀 33,738評論 4 324
  • 正文 年R本政府宣布箱硕,位于F島的核電站掰吕,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏颅痊。R本人自食惡果不足惜殖熟,卻給世界環(huán)境...
    茶點故事閱讀 39,293評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望斑响。 院中可真熱鬧菱属,春花似錦、人聲如沸舰罚。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽营罢。三九已至赏陵,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間饲漾,已是汗流浹背蝙搔。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留考传,地道東北人吃型。 一個月前我還...
    沈念sama閱讀 45,547評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像僚楞,于是被迫代替她去往敵國和親勤晚。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,834評論 2 345

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