欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java Morris遍歷算法及其在二叉樹中的應(yīng)用

 更新時間:2023年04月27日 09:42:03   作者:允歆辰丶  
Morris遍歷是一種基于線索二叉樹的遍歷算法,可以在不使用?;蜻f歸的情況下,實現(xiàn)二叉樹的前序、中序和后序遍歷。該算法利用二叉樹中的空指針或線索指針,將遍歷序列嵌入到原二叉樹中,實現(xiàn)了常數(shù)級別的空間復(fù)雜度,適用于對空間要求較高的場景

一.Morris遍歷

1.什么是Morris遍歷

Morris遍歷是一種用于二叉樹遍歷的算法,它可以在不使用?;蜿犃械那闆r下實現(xiàn)中序遍歷。該算法的時間復(fù)雜度為O(n),空間復(fù)雜度為O(1)。

2.基本思想

Morris遍歷的基本思想是,利用葉子節(jié)點的空指針來存儲臨時信息,以達到節(jié)省空間的目的。具體來說,對于當(dāng)前遍歷到的節(jié)點,如果它有左子節(jié)點,就找到左子樹中最右邊的節(jié)點,將其右子節(jié)點指向當(dāng)前節(jié)點,然后將當(dāng)前節(jié)點更新為其左子節(jié)點。如果它沒有左子節(jié)點,就輸出當(dāng)前節(jié)點的值,并將當(dāng)前節(jié)點更新為其右子節(jié)點。重復(fù)以上步驟,直到遍歷完整棵樹。

3.Morris遍歷的優(yōu)點和缺點

Morris遍歷的優(yōu)點是空間復(fù)雜度低O(1),但它的缺點是會改變原來的二叉樹結(jié)構(gòu),因此需要在遍歷完后還原二叉樹。此外,該算法可能會比遞歸或使用棧的算法稍微慢一些。

4.二叉樹的線索化

二叉樹的線索化,主要是利用了葉子結(jié)點中的空指針域,存放了在某種遍歷順序下的前驅(qū)或者后續(xù)結(jié)點,從而達到了線索二叉樹的目的

例如,下圖中序遍歷結(jié)果展示如下,根據(jù)中序遍歷對空指針域進行線索化

線索化的二叉樹為下, 畫在左邊表示left結(jié)點指向,畫在右邊表示right指向,例如7的前驅(qū)結(jié)點為5,那么7的left指向5,7的后繼節(jié)點為1,那么7的right指向1

由此,我們可能總結(jié)出這樣的結(jié)論:中序遍歷二叉樹的指向當(dāng)前結(jié)點的結(jié)點為當(dāng)前結(jié)點的左子樹的最右端結(jié)點,例如指向1的結(jié)點為1的左節(jié)點2的最右端結(jié)點為7,指向2結(jié)點的為2的左節(jié)點4的最右端結(jié)點4.這一點在之后的morris遍歷中很重要

具體的代碼實現(xiàn)如下:

public class InorderThreadedBinaryTree {
    private ThreadTreeNode pre = null;
    public void threadedNodes(ThreadTreeNode node) {
        //如果node==null,不能線索化
        if (node == null) {
            return;
        }
        //1、先線索化左子樹
        threadedNodes(node.left);
        //2、線索化當(dāng)前結(jié)點
        //處理當(dāng)前結(jié)點的前驅(qū)結(jié)點
        //以8為例來理解
        //8結(jié)點的.left = null,8結(jié)點的.leftType = 1
        if (node.left == null) {
            //讓當(dāng)前結(jié)點的左指針指向前驅(qū)結(jié)點
            node.left = pre;
            //修改當(dāng)前結(jié)點的左指針的類型,指向前驅(qū)結(jié)點
            node.leftType = 1;
        }
        //處理后繼結(jié)點
        if (pre != null && pre.right == null) {
            //讓當(dāng)前結(jié)點的右指針指向當(dāng)前結(jié)點
            pre.right = node;
            //修改當(dāng)前結(jié)點的右指針的類型=
            pre.rightType = 1;
        }
        //每處理一個結(jié)點后,讓當(dāng)前結(jié)點是下一個結(jié)點的前驅(qū)結(jié)點
        pre = node;
        //3、線索化右子樹
        threadedNodes(node.right);
    }
}
class ThreadTreeNode {
    int val;
    ThreadTreeNode left;
    //0為非線索化,1為線索化
    int leftType;
    ThreadTreeNode right;
    //0為非線索化,1為線索化
    int rightType;
    public ThreadTreeNode(int val) {
        this.val = val;
    }
}

但是在實現(xiàn)Morris遍歷的時候,并不需要把結(jié)點的左節(jié)點線索化,只需要把結(jié)點的右節(jié)點進行線索化即可,具體的原因在下面進行分析.

二.中序Morris遍歷

1.中序Morris遍歷的分析

上面我們說了Morris遍歷的時候只需要線索化右節(jié)點,這里給大家進行解釋.當(dāng)我們在中序遍歷一棵樹的時候,還比如是這樣一棵樹,我們一步步的node.left來到了6這個結(jié)點,這個結(jié)點的left為空,所以我們打印6這個結(jié)點的值,此時我們需要返回上一個結(jié)點,如果我們是要中序遞歸進行遍歷的話,需要返回上一個棧,而我們Morris遍歷的時候無法進行遞歸的返回,所以這個時候我們只需要把6的right結(jié)點進行線索化,這個時候6的right指向4,我們就可以返回到4,把4這個結(jié)點進行打印,4也線索化返回了2,把2進行打印,然后進行2的right結(jié)點5,5的left結(jié)點為空,因此打印5,之后進入到5的right結(jié)點7,打印7,7的right結(jié)點也進行了線索化,進入7的right結(jié)點為1,然后打印1,進入3結(jié)點并且打印

因為最好不要改變樹的結(jié)構(gòu),所以我們在打印的時候,將線索化的結(jié)點的right結(jié)點置為空.

2.中序Morris遍歷的思路

Morris遍歷是利用了線索二叉樹的思想,在遍歷的過程中不適用棧,從而達到了空間復(fù)雜度為O(1)

具體的實現(xiàn)如下:

1.初始化當(dāng)前的結(jié)點為根結(jié)點

2.若當(dāng)前的結(jié)點的左節(jié)點為空,則輸出當(dāng)前結(jié)點,然后遍歷當(dāng)前結(jié)點的右子樹,即'curr=curr.right'

3.若當(dāng)前結(jié)點的左節(jié)點不為空,則找到當(dāng)前結(jié)點的前驅(qū)節(jié)點,即當(dāng)前結(jié)點左節(jié)點的最右側(cè)結(jié)點,記為'prev'

  • 如果'prev.right''為空,則將pre.right指向curr結(jié)點,然后遍歷當(dāng)前結(jié)點的左子樹,即'curr=curr.left'
  • 如果'prev.right''不為空,說明已經(jīng)遍歷完了當(dāng)前節(jié)點的左子樹,斷開 `prev.right` 的連接,即'prev.left=null',輸出當(dāng)前節(jié)點,然后遍歷當(dāng)前節(jié)點的右子樹,即 `curr=curr.right`.

3.具體的代碼實現(xiàn)

public class Morris {
    /**
     * 將當(dāng)前根結(jié)點中序遍歷的結(jié)果存儲到list集合中
     * @param root  根結(jié)點
     * @return  中序遍歷的結(jié)合
     */
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<>();
        TreeNode curr = root;
        while (curr != null) {
            if (curr.left == null) { // 左子樹為空,則輸出當(dāng)前節(jié)點,然后遍歷右子樹
                res.add(curr.val);  //如果要求直接打印,直接輸出System.out.println(curr.val);
                curr = curr.right;
            } else {
                // 找到當(dāng)前節(jié)點的前驅(qū)節(jié)點
                TreeNode prev = curr.left;
                while (prev.right != null && prev.right != curr) {
                    prev = prev.right;
                }
                if (prev.right == null) {
                    // 將前驅(qū)節(jié)點的右子樹連接到當(dāng)前節(jié)點
                    prev.right = curr;
                    curr = curr.left;
                } else {
                    // 前驅(qū)節(jié)點的右子樹已經(jīng)連接到當(dāng)前節(jié)點,斷開連接,輸出當(dāng)前節(jié)點,然后遍歷右子樹
                    prev.right = null;
                    res.add(curr.val);//如果要求直接打印,直接輸出System.out.println(curr.val);
                    curr = curr.right;
                }
            }
        }
        return res;
    }
}
class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;
    TreeNode(int x) {
        val = x;
    }
}

測試:

還是這樣一顆二叉樹,輸出如下:

[6, 4, 2, 5, 7, 1, 3]

三.前序Morris遍歷

1.前序Morris遍歷的思路

前序和中序的遍歷很想,只不過在打印(收集結(jié)點信息的時候不同),中序遍歷是在當(dāng)前結(jié)點的左節(jié)點為空(curr.left==null),或者當(dāng)前結(jié)點已經(jīng)被線索化(prev.right==curr)的時候進行打印,仔細觀察前序遍歷的過程,我們通過修改打印的順序即可.前序遍歷是在當(dāng)前結(jié)點的左節(jié)點為空(curr.left==null),或者當(dāng)前結(jié)點沒有被線索化(prev.right==null)的時候進行打印

具體的思路如下:

1.初始化當(dāng)前的結(jié)點為根結(jié)點

2.若當(dāng)前的結(jié)點的左節(jié)點為空,則輸出當(dāng)前結(jié)點,然后遍歷當(dāng)前結(jié)點的右子樹,即'curr=curr.right'

3.若當(dāng)前結(jié)點的左節(jié)點不為空,則找到當(dāng)前結(jié)點的前驅(qū)節(jié)點,即當(dāng)前結(jié)點左節(jié)點的最右側(cè)結(jié)點,記為'prev'

  • 如果'prev.right''為空,輸出當(dāng)前節(jié)點,然后將pre.right指向curr結(jié)點,然后遍歷當(dāng)前結(jié)點的左子樹,即'curr=curr.left'
  • 如果'prev.right''不為空,說明已經(jīng)遍歷完了當(dāng)前節(jié)點的左子樹,斷開 `prev.right` 的連接,即'prev.left=null',然后遍歷當(dāng)前節(jié)點的右子樹,即 `curr=curr.right`.

2.具體的代碼實現(xiàn)

    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<>();
        TreeNode curr = root;
        while (curr != null) {
            if (curr.left == null) { // 左子樹為空,則輸出當(dāng)前節(jié)點,然后遍歷右子樹
                res.add(curr.val);//如果要求直接打印,直接輸出System.out.println(curr.val);
                curr = curr.right;
            } else {
                // 找到當(dāng)前節(jié)點的前驅(qū)節(jié)點
                TreeNode prev = curr.left;
                while (prev.right != null && prev.right != curr) {
                    prev = prev.right;
                }
                if (prev.right == null) {
                    res.add(curr.val);//如果要求直接打印,直接輸出System.out.println(curr.val);
                    // 將前驅(qū)節(jié)點的右子樹連接到當(dāng)前節(jié)點
                    prev.right = curr;
                    curr = curr.left;
                } else {
                    // 前驅(qū)節(jié)點的右子樹已經(jīng)連接到當(dāng)前節(jié)點,斷開連接,輸出當(dāng)前節(jié)點,然后遍歷右子樹
                    prev.right = null;
                    curr = curr.right;
                }
            }
        }
        return res;
    }

測試:

    public static void main(String[] args) {
        TreeNode root = new TreeNode(1);
        root.left = new TreeNode(2);
        root.left.right = new TreeNode(5);
        root.left.right.right = new TreeNode(7);
        root.right = new TreeNode(3);
        root.left.left = new TreeNode(4);
        root.left.left.left = new TreeNode(6);
        System.out.println(preorderTraversal(root));
    }

還是這樣一顆二叉樹,輸出如下:

[1, 2, 4, 6, 5, 7, 3]

四.后序Morris遍歷

1.后序Morris遍歷的思路

后序Morris遍歷實現(xiàn)起來有一定的難度,但是基本代碼還是不變,只是在打印的地方有略微的區(qū)別,

具體的思路如下:

1.初始化當(dāng)前的結(jié)點為根結(jié)點

2.若當(dāng)前的結(jié)點的左節(jié)點為空,則輸出當(dāng)前結(jié)點,然后遍歷當(dāng)前結(jié)點的右子樹,即'curr=curr.right'

3.若當(dāng)前結(jié)點的左節(jié)點不為空,則找到當(dāng)前結(jié)點的前驅(qū)節(jié)點,即當(dāng)前結(jié)點左節(jié)點的最右側(cè)結(jié)點,記為'prev'

  • 如果'prev.right''為空,然后將pre.right指向curr結(jié)點,然后遍歷當(dāng)前結(jié)點的左子樹,即'curr=curr.left'
  • 如果'prev.right''不為空,此時進行逆序存儲,說明已經(jīng)遍歷完了當(dāng)前節(jié)點的左子樹,斷開 `prev.right` 的連接,即'prev.left=null',然后遍歷當(dāng)前節(jié)點的右子樹,即 `curr=curr.right`.

2.具體的代碼實現(xiàn)

    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<>();
        TreeNode dump = new TreeNode(0);//建立一個臨時結(jié)點
        dump.left = root;  //設(shè)置dump的左節(jié)點為root
        TreeNode curr = dump;  //當(dāng)前節(jié)點為dump
        while (curr != null) {
            if (curr.left == null) { // 左子樹為空,則輸出當(dāng)前節(jié)點,然后遍歷右子樹
                curr = curr.right;
            } else {
                // 找到當(dāng)前節(jié)點的前驅(qū)節(jié)點
                TreeNode prev = curr.left;
                while (prev.right != null && prev.right != curr) {
                    prev = prev.right;
                }
                if (prev.right == null) {
                    // 將前驅(qū)節(jié)點的右子樹連接到當(dāng)前節(jié)點
                    prev.right = curr;
                    curr = curr.left;
                } else {
                    reverseAddNodes(curr.left, prev, res);
                    // 前驅(qū)節(jié)點的右子樹已經(jīng)連接到當(dāng)前節(jié)點,斷開連接,輸出當(dāng)前節(jié)點,然后遍歷右子樹
                    prev.right = null;
                    curr = curr.right;
                }
            }
        }
        return res;
    }
    private void reverseAddNodes(TreeNode begin, TreeNode end, List<Integer> res) {
        reverseNodes(begin, end); //將begin到end的進行逆序連接
        TreeNode curr = end;
        while (true) {//將逆序連接后端begin到end添加
            res.add(curr.val);
            if (curr == begin)
                break;
            curr = curr.right;
        }
        reverseNodes(end, begin);//恢復(fù)之前的連接狀態(tài)
    }
    /**
     * 將begin到end的進行逆序連接
     *
     * @param begin
     * @param end
     */
    private void reverseNodes(TreeNode begin, TreeNode end) {
        TreeNode prev = begin;
        TreeNode curr = prev.right;
        TreeNode post;
        while (prev != end) {
            post = curr.right;
            curr.right = prev;
            prev = curr;
            curr = post;
        }
    }

測試:

    public static void main(String[] args) {
        TreeNode root = new TreeNode(1);
        root.left = new TreeNode(2);
        root.left.right = new TreeNode(5);
        root.left.right.right = new TreeNode(7);
        root.right = new TreeNode(3);
        root.left.left = new TreeNode(4);
        root.left.left.left = new TreeNode(6);
        System.out.println(postorderTraversal(root));
    }

還是這樣一顆二叉樹,輸出如下:

[6, 4, 7, 5, 2, 3, 1]

到此這篇關(guān)于Java Morris遍歷算法及其在二叉樹中的應(yīng)用的文章就介紹到這了,更多相關(guān)Java Morris遍歷算法內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

您可能感興趣的文章:

相關(guān)文章

  • 淺談Java中return和finally的問題

    淺談Java中return和finally的問題

    在Java中當(dāng)try、finally語句中包含return語句時,執(zhí)行情況到底是怎樣的,finally中的代碼是否執(zhí)行,大家眾說紛紜,有的說會執(zhí)行,有的說不會執(zhí)行,到底哪種說法正確,下面我們來詳細討論下
    2015-10-10
  • 聊聊springboot2.2.3升級到2.4.0單元測試的區(qū)別

    聊聊springboot2.2.3升級到2.4.0單元測試的區(qū)別

    這篇文章主要介紹了springboot 2.2.3 升級到 2.4.0單元測試的區(qū)別,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-10-10
  • 簡單談?wù)凷pring Ioc原理解析

    簡單談?wù)凷pring Ioc原理解析

    學(xué)習(xí)過Spring框架的人一定都會聽過Spring的IoC(控制反轉(zhuǎn)) 、DI(依賴注入)這兩個概念,對于初學(xué)Spring的人來說,總覺得IoC 、DI這兩個概念是模糊不清的,是很難理解的,今天和大家分享網(wǎng)上的一些技術(shù)大牛們對Spring框架的IOC的理解以及談?wù)勎覍pring Ioc的理解。
    2018-09-09
  • 詳解springmvc常用5種注解

    詳解springmvc常用5種注解

    在本篇里我們給大家總結(jié)了關(guān)于springmvc常用5種注解相關(guān)知識點以及實例代碼,需要的朋友們參考下。
    2019-07-07
  • java并發(fā)編程專題(二)----如何創(chuàng)建并運行java線程

    java并發(fā)編程專題(二)----如何創(chuàng)建并運行java線程

    這篇文章主要介紹了java并發(fā)編程如何創(chuàng)建并運行java線程,文中講解非常詳細,示例代碼幫助大家更好的理解和學(xué)習(xí),感興趣的朋友可以了解下
    2020-06-06
  • Java中終止線程的方法詳解

    Java中終止線程的方法詳解

    這篇文章主要介紹了Java中終止線程的方法詳解的相關(guān)資料,需要的朋友可以參考下
    2017-05-05
  • java使用@Transactional時常犯的N種錯誤

    java使用@Transactional時常犯的N種錯誤

    @Transactional是我們在用Spring時候幾乎逃不掉的一個注解,本文主要介紹了使用?@Transactional?時常犯的N種錯誤,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-01-01
  • Spring如何在xml文件中配置Bean

    Spring如何在xml文件中配置Bean

    這篇文章主要介紹了Spring如何在xml文件中配置Bean的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2019-07-07
  • SpringBoot快速設(shè)置攔截器并實現(xiàn)權(quán)限驗證的方法

    SpringBoot快速設(shè)置攔截器并實現(xiàn)權(quán)限驗證的方法

    本篇文章主要介紹了SpringBoot快速設(shè)置攔截器并實現(xiàn)權(quán)限驗證的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-01-01
  • java向多線程中傳遞參數(shù)的三種方法詳細介紹

    java向多線程中傳遞參數(shù)的三種方法詳細介紹

    但在多線程的異步開發(fā)模式下,數(shù)據(jù)的傳遞和返回和同步開發(fā)模式有很大的區(qū)別。由于線程的運行和結(jié)束是不可預(yù)料的,因此,在傳遞和返回數(shù)據(jù)時就無法象函數(shù)一樣通過函數(shù)參數(shù)和return語句來返回數(shù)據(jù)
    2012-11-11

最新評論