二叉樹的遍歷(遞歸、迭代和morris多解法)

寫在前:樹的遍歷

        1
       / \
      2   3
     / \   \
    4   5   6

層次遍歷順序:[1 2 3 4 5 6]

morris順序:[1 2 4 2 5 1 3 6]

前序遍歷順序:[1 2 4 5 3 6]
中序遍歷順序:[4 2 5 1 3 6]
后序遍歷順序:[4 5 2 6 3 1]

層次遍歷使用 BFS 實(shí)現(xiàn)(另一個(gè)場景是求最短路徑)隆檀,利用的就是 BFS 一層一層遍歷的特性叶沛;

而前序蒲讯、中序、后序遍歷利用了 DFS 實(shí)現(xiàn)灰署,前序判帮、中序、后序遍只是在對(duì)節(jié)點(diǎn)訪問的順序有一點(diǎn)不同溉箕,其它都相同晦墙。

① 前序:

void dfsPre(TreeNode root) {
    if (root == null) return;
    visit(root);
    dfsIn(root.left);
    dfsIn(root.right);
}

② 中序

void dfsIn(TreeNode root) {
    if (root == null) return;
    dfsIn(root.left);
    visit(root);
    dfsIn(root.right);
}

③ 后序

void dfsPos(TreeNode root) {
    if (root == null) return;
    dfsPos(root.left);
    dfsPos(root.right);
    visit(root);
}

morris序

public static void morris(Node head) {
    if (head == null) {
        return;
    }
    Node cur = head;
    Node mostRight = null;
    
    while (cur != null) {
        mostRight = cur.left;
        // 當(dāng)前節(jié)點(diǎn)有左子樹
        if (mostRight != null) {
            //找當(dāng)前節(jié)點(diǎn)左子樹的最右節(jié)點(diǎn)
            while (mostRight.right == null && mostRight.right == cur) {
                mostRight = mostRight.right;    
            }
            if (mostRight == null) {
                mostRight.right = cur;
                cur = cur.left;
                //回到最外層的while情況,繼續(xù)判斷cur的情況
                continue;
            } else {
                mostRight.right = null;
            }
        }
        //沒有左孩子
        cur = cur.right;  
    }
}

1.非遞歸進(jìn)行二叉樹前序遍歷(144-中)

思路

法1:遞歸是使用系統(tǒng)棧约巷,迭代我們需要自己創(chuàng)建棧模擬這個(gè)過程偎痛。先序遍歷要先記錄當(dāng)前節(jié)點(diǎn)值旱捧,再依次壓如右孩子和左孩子(先進(jìn)后出)独郎。注:棧結(jié)構(gòu)底層是通過數(shù)組實(shí)現(xiàn),可以存儲(chǔ)null值枚赡。

法2:morris遍歷氓癌,記錄一個(gè)節(jié)點(diǎn)(出現(xiàn)多次)在morris序中第一次訪問的順序和只出現(xiàn)一次的節(jié)點(diǎn),即左子樹的右邊界指向null時(shí)贫橙,我們第一次到達(dá)cur節(jié)點(diǎn)贪婉。

代碼

// 1. 迭代
public List<Integer> preorderTraversal(TreeNode root) {
    List<Integer> ret = new ArrayList<>();
    Stack<TreeNode> stack = new Stack<>();
    stack.add(root);
    
    while (!stack.isEmpty()) {
        TreeNode node = stack.pop();
        if (node == null) continue;
        ret.add(node.val);
        stack.push(node.right);
        stack.push(node.left);
    }
    return ret;
}

// 2.morris
public List<Integer> preorderTraversal_1(TreeNode root) {
    List<Integer> ret = new ArrayList<>();
    if (root == null) return ret;
    TreeNode cur = root;
    TreeNode mostRight = null;
    
    while (cur != null) {
        mostRight = cur.left;
        if (cur.left != null) {     
            while (mostRight.right != null && mostRight.right != cur) {
                mostRight = mostRight.right;
            }
            if (mostRight.right == null) {
                mostRight.right = cur;
                ret.add(cur.val);   //第一次到達(dá)cur
                cur = cur.left;
                continue;
            } else {
                mostRight.right = null;
            }
        } else {
            ret.add(cur.val);      //只到達(dá)一次的點(diǎn)
        }
        cur = cur.right;
    }
    return ret;
}

2.非遞歸進(jìn)行二叉樹后序遍歷(145-中)

思路

法1:反轉(zhuǎn)技巧:前序遍歷為 root -> left -> right,后序遍歷為 left -> right -> root卢肃∑S兀可以修改前序遍歷成為 root -> right -> left,那么這個(gè)順序就和后序遍歷正好相反莫湘。

法2:morris遍歷:二叉樹后序遍歷morris改寫(逆序打印右邊界)尤蒿。

1、對(duì)二叉樹遍歷中只出現(xiàn)一次的節(jié)點(diǎn)幅垮,即無左子樹的節(jié)點(diǎn)腰池,直接跳過
2、對(duì)于出現(xiàn)兩次的節(jié)點(diǎn),第一次不管示弓,當(dāng)?shù)诙蔚竭_(dá)x的時(shí)候讳侨,逆序打印左子樹的右邊界
3、遍歷完成后奏属,打印整棵樹的右邊界

代碼

// 1.反轉(zhuǎn)技巧
public List<Integer> postorderTraversal(TreeNode root) {
    List<Integer> ret = new ArrayList<>();
    Stack<TreeNode> stack = new Stack<>();
    stack.add(root);
    
    while (!stack.isEmpty()) {
        TreeNode node = stack.pop();
        if (node == null) continue;
        ret.add(node.val);
        stack.push(node.left);
        stack.push(node.right);
    }
    Collections.reverse(ret);
    return ret;
}

// 2. morris
public static void morrisPos(Node head) {
    if (head == null) {
        return;
    }
    Node cur = head;
    Node mostRight = null;
    while (cur != null) {
        mostRight = cur.left;
        if (mostRight != null) {  //存在右邊界
            while (mostRight.right != null && mostRight.right != cur) {   //沒有到達(dá)左子樹的右邊界
                mostRight = mostRight.right;
            }
            if (mostRight.right == null) {
                mostRight.right = cur;
                cur = cur.left;
                continue;
            } else {
                mostRight.right = null;  //第二次到達(dá)這個(gè)位置
                printEdge(cur.left);
            }
        }
        cur = cur.right;
    }
    printEdge(head);    //打印整棵樹的右邊界
    System.out.println();
}
public static void printEdge(Node head) {
    Node tail = reverseEdge(head);
    Node cur = tail;
    while (cur != null) {
        System.out.print(cur.value + " ");
        cur = cur.right;   //指向前一個(gè)節(jié)點(diǎn)(逆序)
    }
    reverseEdge(tail);   //打印完成將原結(jié)構(gòu)還原(逆序回去)
}

//逆序右邊界
public static Node reverseEdge(Node from) {   //逆序打印左子樹的右邊界,返回值是節(jié)點(diǎn)類型
    Node pre = null;
    Node next = null;
    while (from != null) {
        next = from.right;
        from.right = pre;   //改變指針的方向跨跨,指向前一個(gè)節(jié)點(diǎn)
        pre = from;   //記錄前一個(gè)節(jié)點(diǎn)
        from = next;   //from = from.right
    }
    return pre;   //返回左子樹右邊界的最后一個(gè)元素
}

3.非遞歸進(jìn)行二叉樹中序遍歷(94-中)

思路

法1:迭代:中序遍歷的順序是【左中右】。每到一個(gè)節(jié)點(diǎn)node拍皮,因?yàn)楦?jié)點(diǎn)的訪問在中間歹叮,先將node入棧。先遍歷左子樹铆帽,訪問node節(jié)點(diǎn)咆耿,最后訪問右子樹。訪問完node節(jié)點(diǎn)就可以出棧(已經(jīng)完成左子樹和node節(jié)點(diǎn))爹橱。注:循環(huán)進(jìn)入條件包括棧非空萨螺。

法2:morris遍歷,記錄一個(gè)節(jié)點(diǎn)(出現(xiàn)多次)在morris序中第二次訪問的順序和只出現(xiàn)一次的節(jié)點(diǎn)愧驱,即左子樹的右邊界指向cur時(shí)慰技,我們第二次到達(dá)cur節(jié)點(diǎn)。

代碼

// 1.迭代
public List<Integer> inorderTraversal(TreeNode root) {
    List<Integer> ret = new ArrayList<>();
    if (root == null) return ret;
    Stack<TreeNode> stack = new Stack<>();
    TreeNode cur = root;
    
    while (cur != null || !stack.isEmpty()) {
        while (cur != null) {
            stack.push(cur);
            cur = cur.left;
        }
        // 左子樹和node節(jié)點(diǎn)已經(jīng)完成
        TreeNode node = stack.pop();
        ret.add(node.val);
        cur = node.right;
    }   
    return ret; 
}

// 2.morris
public List<Integer> inorderTraversal_1(TreeNode root) {
    List<Integer> ret = new ArrayList<>();
    if (root == null) return ret;
    TreeNode cur = root;
    TreeNode mostRight = null;
    while (cur != null) {
        mostRight = cur.left;
        if (cur.left != null) {
            while (mostRight.right != null && mostRight.right != cur) {
                mostRight = mostRight.right;    //左子樹最右邊界
            }
            if (mostRight.right == null) {
                mostRight.right = cur;
                cur = cur.left;
                continue;   //回到最外層的while情況组砚,繼續(xù)判斷cur的情況
            } else {
                mostRight.right = null;
            }
        }
        ret.add(cur.val);    //記錄只到達(dá)一次的點(diǎn)(無左孩子)和第二次到達(dá)curN巧獭!糟红!
        cur = cur.right;
    }
    return ret;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末艾帐,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子盆偿,更是在濱河造成了極大的恐慌柒爸,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件事扭,死亡現(xiàn)場離奇詭異捎稚,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)求橄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門今野,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人罐农,你說我怎么就攤上這事条霜。” “怎么了啃匿?”我有些...
    開封第一講書人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵蛔外,是天一觀的道長蛆楞。 經(jīng)常有香客問我,道長夹厌,這世上最難降的妖魔是什么豹爹? 我笑而不...
    開封第一講書人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮矛纹,結(jié)果婚禮上臂聋,老公的妹妹穿的比我還像新娘。我一直安慰自己或南,他們只是感情好孩等,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著采够,像睡著了一般肄方。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蹬癌,一...
    開封第一講書人閱讀 49,144評(píng)論 1 285
  • 那天权她,我揣著相機(jī)與錄音,去河邊找鬼逝薪。 笑死隅要,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的董济。 我是一名探鬼主播步清,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼虏肾!你這毒婦竟也來了廓啊?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤询微,失蹤者是張志新(化名)和其女友劉穎崖瞭,沒想到半個(gè)月后狂巢,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體撑毛,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年唧领,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了藻雌。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡斩个,死狀恐怖胯杭,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情受啥,我是刑警寧澤做个,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布鸽心,位于F島的核電站,受9級(jí)特大地震影響居暖,放射性物質(zhì)發(fā)生泄漏顽频。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一太闺、第九天 我趴在偏房一處隱蔽的房頂上張望糯景。 院中可真熱鬧,春花似錦省骂、人聲如沸蟀淮。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽怠惶。三九已至,卻和暖如春轧粟,著一層夾襖步出監(jiān)牢的瞬間甚疟,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來泰國打工逃延, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留览妖,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓讽膏,卻偏偏與公主長得像,于是被迫代替她去往敵國和親拄丰。 傳聞我的和親對(duì)象是個(gè)殘疾皇子府树,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

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