线索二叉树(Morris Traversal)
在前面的文章中总结了二叉树的一些操作,提供了二叉树前中后的递归和非递归的实现。在非递归的实现中,基本思想是利用栈来模拟递归调用遍历的过程,本质上和递归实现没有区别,空间复杂度为
- 空间复杂度为
- 二叉树的结构不能被破坏
Morris Traversal 能够解决上面的问题,James H. Morris 提出了二叉树线索化,利用结点的空链域来存储结点的前驱和后继信息。
线索二叉树结构
那么什么是线索二叉树呢?
- 如果结点有左子树,则其left域指示为其左孩子,否则指示其前驱;
- 如果结点有右子树,则其right域指示为其右孩子,否则指示其后继
对于上述的概念,我们可以用一个比较具体的例子来分析,下面是一个二叉树:
该二叉树中序遍历的结果为:4-2-8-5-1-6-3-7
, 上面提到的前驱和后继是针对遍历过程来说的,不同的遍历方式一个结点的前驱和后继可能会不同,前驱和后继被称为线索
,对二叉树以某种次序遍历使其变为线索二叉树的过程叫做线索化
。
下面是线索二叉树的结点Node,在决定 left 是指向左孩子还是前驱,right 是指向右孩子还是后继,我们是需要一个区分标志的。因此我们在 Node类中增加leftThread
和rightThread
布尔字段,其中:
- leftThread 为 true 时指向前驱,为 false 时指向该结点的左子树;
- rightThread 为 true 时指向后继,为 false 时指向该结点的右子树。
public static class Node<E extends Comparable<E>> { E item; Node<E> left; Node<E> right; boolean leftThread; boolean rightThread; public Node (E item) { this.item = item; } @Override public String toString() { return "item=" + item + " left=" + ((left != null) ? left.item : "NULL") + " right=" + ((right != null) ? right.item : "NULL") + " leftThread=" + leftThread + " rightThread=" + rightThread; } }
针对上面二叉树的中序遍历来说,我们需要先将二叉树线索化,形成线索二叉树:
二叉树线索化
了解了二叉树的线索化后,接下来就是用代码来将二叉树线索化,这里我们依然采用中序遍历,并使用递归,代码如下:
/** * 中序遍历线索化 * * @param root 二叉树根结点 */ public void inThreading(Node root) { if (root == null) { return; } // 按照中序遍历方向,先处理左子树 inThreading(root.left); // 判断当前节点的左孩子是否为空,为空的话则当前节点的左孩子指向其前驱 if (root.left == null) { root.leftThread = true; root.left = pre; // 指向前驱 } // pre的右孩子为空 if (pre != null && pre.right == null) { pre.rightThread = true; pre.right = root; // pre的右孩子指向root(后继) } pre = root; // 右子树线索化 inThreading(root.right); }
上面代码中,就是根据中序遍历的递归代码改写来的,对于任意一个结点,先访问右子树,接着访问该结点,最后访问右子树。pre代表上次访问的结点(相对于当前结点)。
中序遍历
构造完线索二叉树,我们就可以来利用Morris Traversal遍历线索二叉树了,遍历步骤如下:
- 找到中序遍历的起点
- 该结点不为空,访问该结点
- 判断当前结点的是否有后继
- 如果有,那么获取其后继结点作为当前结点
- 如果没有,说明当前结点有右子树,那么按照中序遍历访问右子树
- 重复3操作,直至线索二叉树输出完毕
代码如下:
/** * 中序遍历 * * @param root 根结点 */ public void inOrderNonRec(Node root) { if (root == null) { return; } // 查找中序遍历的起始节点 while (root != null && !root.leftThread) { root = root.left; } while (root != null) { System.out.println(root); // 如果右孩子是线索 if (root.rightThread) { root = root.right; } else { // 有右孩子 root = root.right; while (root != null && !root.leftThread) { root = root.left; } } } }
References:
- 严蔚敏,《数据结构(C语言)第二版》
- Threaded binary tree
- Morris Traversal方法遍历二叉树(非递归,不用栈,O(1)空间)
- http://www.geeksforgeeks.org/inorder-tree-traversal-without-recursion-and-without-stack/
- http://www.geeksforgeeks.org/morris-traversal-for-preorder/
- http://stackoverflow.com/questions/6478063/how-is-the-complexity-of-morris-traversal-on
title: 线索二叉树(Morris Traversal)
tags: [数据结构, 二叉树]
author: Mingshan
categories: [数据结构, 二叉树]
date: 2019-04-01
本文来自博客园,作者:mingshan,转载请注明原文链接:https://www.cnblogs.com/mingshan/p/17793613.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)