IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> morris算法实现二叉树遍历 -> 正文阅读

[数据结构与算法]morris算法实现二叉树遍历

在刷leetcode上二叉树相关题目时144题,看到了一种morris的实现方式,可以把实现的空间复杂度降低到O(1),解法研究半天也是一头雾水,网上找资料和视频详细学了一下。

题目:

?144.二叉树前序遍历

94. 二叉树的中序遍历

145. 二叉树的后序遍历

递归解法

对于前中后序遍历,常规的递归解题套路:

  public void preOrder(List<Integer> res, TreeNode root) {
        if (root == null) return;

        res.add(root.val);//前序
        preOrder(res, root.left);
        res.add(root.val);//中序
        preOrder(res, root.right);
        res.add(root.val);//后序
    }

对于递归解法来说,会使用一个树身高度的额外空间存储从根节点到叶子节点的元素,用于回溯的时候找到父节点,所以时间复杂度和空间复杂度均为O(n)

morris算法实现

有一种巧妙的方法可以在线性时间内,只占用常数空间来实现前序遍历。这种方法由 J. H. Morris 在 1979 年的论文「Traversing Binary Trees Simply and Cheaply」中首次提出,因此被称为 Morris 遍历。

左神的讲解就通俗易懂了,学习地址:左神讲解morris遍历

Morris 遍历的核心思想是利用树的大量空闲指针,实现空间开销的极限缩减。其规则总结如下:

1.定义当前节点为cur,初始化为根节点
2.如果当前节点cur无左子树,cur = cur.right,如果此时cur = null
3.如果当前节点cur有左子树,找到左子树的最最右侧的右子树,记为mostRight.??
??此时分两种情况:
????(1) 最右侧节点mostRight的右指针指向null, 即mostRight.right = null ,此时mostRight.right = cur, cur = cur.left;?
????(2)最右侧节点mostRight的有指针指向当前节点cur,说明是第二次访问到当前节点cur,此时设置 mostRight.right = null??,cur = cur.right;
遍历停止的条件是cur = null

?通过的代码模板如下:

    public void morris(TreeNode root) {
        if (root == null) return ;

        TreeNode cur = root;//定义当前节点,初始化为root
        TreeNode mostRight = null;//定义左子树的最右节点
        while (cur != null) {
           mostRight = cur.left;
           if (mostRight != null) {//包含左子树
               //查找左子树最右节点,且cur不是第二次访问
               while (mostRight.right != null && mostRight.right != cur) {
                   mostRight = mostRight.right;
               }
               
               if (mostRight.right == null) {//cur为第一次访问
                   mostRight.right = cur;
//                   System.out.println(cur.val + " ");
                   cur = cur.left;
                   continue;
               } 
               else {//cur为第二次访问
                   mostRight.right = null;
                   cur = cur.right;
               }
           } else { //无左子树
//               System.out.println(cur.val + " ");
               cur = cur.right;
           }
        }
    }

由于遍历过程中仅仅使用了最右子树的空闲指针的指向当前节点,用于回溯,所以无额外空间开销,空间复杂度O(1)。

前序遍历morris实现:

    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<>();
        if (root == null) {
            return res;
        }

        morrisPre(root, res);
        return res;
    }

    public void morrisPre(TreeNode root, List<Integer> res) {
        if (root == null) return ;

        TreeNode cur = root;//定义当前节点,初始化为root
        TreeNode mostRight = null;//定义左子树的最右节点
        while (cur != null) {
           mostRight = cur.left;
           if (mostRight != null) {//包含左子树
               //查找左子树最右节点,且cur不是第二次访问
               while (mostRight.right != null && mostRight.right != cur) {
                   mostRight = mostRight.right;
               }

               if (mostRight.right == null) {//cur为第一次访问
                   mostRight.right = cur;
                   res.add(cur.val);//包含左子树的情况,在访问左子树之前记录当前根节点
                   cur = cur.left;
                   continue;
               }
               else {//cur为第二次访问
                   mostRight.right = null;
                   cur = cur.right;
               }
           } else { //无左子树
               res.add(cur.val);//无左子树的情况,在访问右子树之前记录当前根节点
               cur = cur.right;
           }
        }
    }

总结:第一次访问到一个节点,马上进行保存,即为先序(morris,访问顺序是根左右)

中序遍历morris实现:

public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<>();
        if (root == null) {
            return res;
        }

        morrisIn(root, res);
        return res;
    }

    public void morrisIn(TreeNode root, List<Integer> res) {
        if (root == null) return ;

        TreeNode cur = root;//定义当前节点,初始化为root
        TreeNode mostRight = null;//定义左子树的最右节点
        while (cur != null) {
           mostRight = cur.left;
           if (mostRight != null) {//包含左子树
               //查找左子树最右节点,且cur不是第二次访问
               while (mostRight.right != null && mostRight.right != cur) {
                   mostRight = mostRight.right;
               }

               if (mostRight.right == null) {//cur为第一次访问
                   mostRight.right = cur;
                   cur = cur.left;
                   continue;
               }
               else {//cur为第二次访问
                   mostRight.right = null;
                   res.add(cur.val);//无左子树的情况,在访问右子树之前记录当前根节点
                   cur = cur.right;
               }
           } else { //无左子树
               res.add(cur.val);//无左子树的情况,在访问右子树之前记录当前根节点
               cur = cur.right;
           }
        }
    }

总结:有左子树的节点第二次访问到一个节点,马上进行保存,无左子树,访问右子树之前进行保存,简单来说就是只要一个节点要往右子树移动,就进行保存,即为中序(morris,访问顺序是根左右)

后序遍历morris实现:

    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<>();
        if (root == null) {
            return res;
        }

        morrisPost(root, res);
        return res;
    }

    public void morrisPost(TreeNode root, List<Integer> res) {
        if (root == null) return ;

        TreeNode cur = root;//定义当前节点,初始化为root
        TreeNode mostRight = null;//定义左子树的最右节点
        while (cur != null) {
           mostRight = cur.left;
           if (mostRight != null) {//包含左子树
               //查找左子树最右节点,且cur不是第二次访问
               while (mostRight.right != null && mostRight.right != cur) {
                   mostRight = mostRight.right;
               }

               if (mostRight.right == null) {//cur为第一次访问
                   mostRight.right = cur;
                   cur = cur.left;
                   continue;
               }
               else {//cur为第二次访问
                   mostRight.right = null;
                   //逆序打印当前节点左子树的有边界
                   printEdge(cur.left, res);
                   cur = cur.right;  
               }
           } else { //无左子树
               cur = cur.right;
           }
        }
        //逆序打印从根节点开始的树的有边界
        printEdge(root, res);
    }

   public void printEdge(TreeNode head, List<Integer> res) {
		TreeNode tail = reverseEdge(head);
		TreeNode cur = tail;
		while (cur != null) {
			res.add(cur.val);
			cur = cur.right;
		}
		// reverseEdge(tail);
	}

	public TreeNode reverseEdge(TreeNode root) {
		TreeNode pre = null;
		TreeNode cur = root;
		while (cur != null) {
			TreeNode next = cur.right;
			cur.right = pre;
			pre = cur;
			cur = next;
		}
		return pre;
	}

图解:

?

  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2022-02-28 15:50:37  更:2022-02-28 15:52:49 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 16:51:22-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码