【二叉树:3】线索二叉树
线索二叉树
- 一、相关介绍
- 1.基本介绍
- 2.相关概念
- 二、建立线索二叉树
- 1.前序线索二叉树
- 2.中序线索二叉树
- 3.后序线索二叉树
- 三、线索二叉树遍历
- 1.中序线索二叉树遍历
- 四、完整代码
一、相关介绍
1.基本介绍
线索二叉树:对节点加上线索线索后的二叉树称为线索二叉树;
建立线索二叉树:通过某种遍历方式对二叉树进行遍历,并在遍历过程中对其节点添加线索,使其变为线索二叉树;
2.相关概念
1)对于n个结点的二叉树,在二叉链存储结构中有n+1个空链域(理解:n个节点共有2n个指针域,而n个节点共消耗n-1个指针域,所以空指针域共有2n-n+1=n+1个),利用这些空链域存放在某种遍历次序下该结点的前驱结点和后继结点的指针,这些指针称为线索,加上线索的二叉树称为线索二叉树;
2)这种加上了线索的二叉链表称为线索链表,相应的二叉树称为线索二叉树(Threaded BinaryTree)。根据线索性质的不同,线索二叉树可分为前序线索二叉树、中序线索二叉树和后序线索二叉树三种;
3)前驱结点:固定遍历方式下,当前节点的前一个节点;后继结点:固定遍历方式下,当前节点的下一个节点;
下图是普通二叉树和线索二叉树的对比(其中紫线指向前驱结点、红线指向后继节点):
图1:
线索化二叉树很好地避免了二叉树中节点指针域的浪费,且对二叉树进行线索化后,每个节点都可以很方便地找到自己的前驱结点和后继节点(其中第一个节点没有前驱结点,最后一个节点没有后继节点)。
二、建立线索二叉树
我们要知道对二叉树进行线索化是通过某种遍历方式进行的,所以根据遍历方式的不同,线索二叉树可分为前序线索二叉树、中序线索二叉树和后序线索二叉树三种。比如,中序线索二叉树是二叉树通过中序遍历的方式去进行线索化,其过程是按照中序遍历的顺序为节点添加其前驱结点和后继节点。如图1中,我简单展示如何对左面的二叉树进行中序线索化:按照中序遍历的顺序,找到的第一个节点应该是4,然后是节点2,依次类推,当遍历到该节点时我们需要确定他的前驱结点和后继节点。
大家知道二叉树的每个节点有两个指针,分别指向其左、右子节点,节点结构如下:
但是大家很明显可以从图1中线索二叉树中发现,某些节点(比如1节点)的左右指针不一定指向的是前驱节点或后继节点,而是他本身指向的左右子节点,所以为了区分当前节点的左右指针具体指向,需要为节点添加两个属性:left_type、right_type,left_type=0表示左指针指向左子节点,left_type=1表示左指针指向他的前驱结点,对于right_type设置也一样。
此时我们就可以开始观察二叉树实现线索化的过程:
1.前序线索二叉树
//前序线索化public void preThreadedNode(){this.preThreadedNode(root);}public void preThreadedNode(Employee_Node node){// System.out.println(node);if(node==null){return;}// 处理当前节点// 前驱节点if(node.getLeft()==null){node.setLeft(preNode);node.setLeftType(1);}// 后继节点if(preNode!=null&&preNode.getRight()==null&&preNode.getLeft()!=node){preNode.setRight(node);preNode.setRightType(1);}preNode=node;// 左子树线索化if(node.getLeftType()==0) {preThreadedNode(node.getLeft());}// 右子树线索化preThreadedNode(node.getRight());}
2.中序线索二叉树
中序线索化是按照中序遍历二叉树的顺序为节点添加线索,过程如下:
- 1)判断当前节点是否为空,如果是直接返回;
- 2)否则,对当前节点左子树进行中序线索化;
- 3)对当前节点进行线索化(确定他的前驱结点和后继节点);
- 4)对当前节点右子树进行中序线索化;
在代码实现过程中,有两个点需要注意:
- 从一开始设置一个preNode节点,记录当前节点的前驱结点,最开始默认为null;
- 当前节点的后继节点通过下一个节点设置,比如节点4为当前节点,其前驱节点preNode=null,要想确定其后继节点为2,我们需要先移动使得当前节点为2,其preNode=4,此时preNode=4的后继节点不就正好是2吗?
// 中序线索化public void middleThreadedNode(){ //重载this.middleThreadedNode(root);}public void middleThreadedNode(Employee_Node node){ // 线索化当前节点if(node==null){return;}middleThreadedNode(node.getLeft()); //左子树线索化//当前节点线索化// 处理前驱结点if(node.getLeft()==null){node.setLeft(preNode);node.setLeftType(1); //说明left指向前驱结点}//处理后继节点:当前节点的后继节点通过下一个节点设置,因为下一个节点的前驱结点就是当前节点if(preNode!=null&&preNode.getRight()==null){preNode.setRight(node);preNode.setRightType(1); //说明right指向前驱结点}//移动preNode,令其为当前节点preNode=node;middleThreadedNode(node.getRight());//右子树线索化}
线索化过程如下:
第一步:
第二步:
依次类推…
3.后序线索二叉树
// 后续线索化public void postThreadedNode(){postThreadedNode(root);}public void postThreadedNode(Employee_Node node){if(node==null){return;}// 左子树线索化postThreadedNode(node.getLeft());// 右子树线索化postThreadedNode(node.getRight());// 当前节点线索化// 前驱节点if(node.getLeft()==null){node.setLeft(preNode);node.setLeftType(1);}if(preNode!=null&&preNode.getRight()==null){preNode.setRight(node);preNode.setRightType(1);}preNode=node;}
三、线索二叉树遍历
这里需要明确的是1)对线索二叉树进行遍历时,其遍历方式要和线索化方式对应一致,比如中序线索二叉树就要使用中序遍历。2)线索二叉树的遍历不能使用普通二叉树的遍历算法(普通二叉树的遍历通过判断某个节点的左右子节点是否为null来做递归终止条件,而线索二叉树的节点不存在空指针域),要对其进行一定的修改,因为每个节点都可以找到对应的前驱结点和后继节点,我们需要通过一种“线性”的方式去遍历二叉树。
1.中序线索二叉树遍历
// 中序遍历:不能使用原来的遍历方式,// 非递归方式public void middleThreadedOrder(){Employee_Node tempNode=root; // 临时存储当前节点while (tempNode!=null){// 第一次肯定找到的是中序遍历顺序的第一个节点8// 寻找经历线索化的第一个节点while (tempNode.getLeftType()!=1){tempNode=tempNode.getLeft();}// 输出当前节点System.out.println(tempNode);//处理后继节点,直到某个节点的right指向的是右子树为止while (tempNode.getRightType()==1){tempNode=tempNode.getRight();System.out.println(tempNode);}tempNode=tempNode.getRight(); //移动当前节点}}
四、完整代码
package com.north.tree;public class ThreadedBinaryTreeDemo {public static void main(String[] args){System.out.println("线索化二叉树...");Employee_Node root=new Employee_Node(1,"杨俊荣");Employee_Node node2=new Employee_Node(2,"周杰伦");Employee_Node node3=new Employee_Node(3,"方文山");Employee_Node node4=new Employee_Node(4,"派俊伟");Employee_Node node5=new Employee_Node(5,"弹头");Employee_Node node6=new Employee_Node(6,"黄俊郎");Employee_Node node7=new Employee_Node(7,"昆凌");// 构建二叉树模型ThreadedBinaryTree tree=new ThreadedBinaryTree();tree.setRoot(root);root.setLeft(node2);root.setRight(node3);node2.setLeft(node4);node2.setRight(node5);node3.setLeft(node6);node3.setRight(node7);// 中序线索化二叉树
// tree.middleThreadedNode();// 测试
// System.out.println("node5的前驱结点为:"+node5.getLeft());
// System.out.println("node5的后继结点为:"+node5.getRight());// 使用线索化二叉树进行中序遍历
// tree.middleThreadedOrder();// 前序线索化
// tree.preThreadedNode();//后续线索化tree.postThreadedNode();System.out.println("node5的前驱结点为:"+node5.getLeft());System.out.println("node5的后继结点为:"+node5.getRight());System.out.println("node6的前驱结点为:"+node6.getLeft());System.out.println("node6的后继结点为:"+node6.getRight());System.out.println("node7的前驱结点为:"+node7.getLeft());System.out.println("node7的后继结点为:"+node7.getRight());}
}// 构建二叉树
class ThreadedBinaryTree{private Employee_Node root; // 根节点private Employee_Node preNode; //记录当前节点的前一个节点(就中序遍历的结果中的顺序而言)public void setRoot(Employee_Node root) {this.root = root;}//前序线索化public void preThreadedNode(){this.preThreadedNode(root);}public void preThreadedNode(Employee_Node node){// System.out.println(node);if(node==null){return;}// 处理当前节点// 前驱节点if(node.getLeft()==null){node.setLeft(preNode);node.setLeftType(1);}// 后继节点if(preNode!=null&&preNode.getRight()==null&&preNode.getLeft()!=node){preNode.setRight(node);preNode.setRightType(1);}preNode=node;// 左子树线索化if(node.getLeftType()==0) {preThreadedNode(node.getLeft());}// 右子树线索化preThreadedNode(node.getRight());}// 中序线索化public void middleThreadedNode(){this.middleThreadedNode(root);}public void middleThreadedNode(Employee_Node node){ // 线索化当前节点if(node==null){return;}middleThreadedNode(node.getLeft()); //左子树线索化//当前节点线索化// 处理前驱结点if(node.getLeft()==null){node.setLeft(preNode);node.setLeftType(1); //说明left指向前驱结点}//处理后继节点:当前节点的后继节点通过下一个节点设置,因为下一个节点的前驱结点就是当前节点if(preNode!=null&&preNode.getRight()==null){preNode.setRight(node);preNode.setRightType(1); //说明right指向前驱结点}//移动preNode,令其为当前节点preNode=node;middleThreadedNode(node.getRight());//右子树线索化}// 后续线索化public void postThreadedNode(){postThreadedNode(root);}public void postThreadedNode(Employee_Node node){if(node==null){return;}// 左子树线索化postThreadedNode(node.getLeft());// 右子树线索化postThreadedNode(node.getRight());// 当前节点线索化// 前驱节点if(node.getLeft()==null){node.setLeft(preNode);node.setLeftType(1);}if(preNode!=null&&preNode.getRight()==null){preNode.setRight(node);preNode.setRightType(1);}preNode=node;}// 中序遍历:不能使用原来的遍历方式,// 非递归方式public void middleThreadedOrder(){Employee_Node tempNode=root; // 临时存储当前节点while (tempNode!=null){// 第一次肯定找到的是中序遍历顺序的第一个节点8// 寻找经历线索化的第一个节点while (tempNode.getLeftType()!=1){tempNode=tempNode.getLeft();}// 输出当前节点System.out.println(tempNode);//处理后继节点,直到某个节点的right指向的是右子树为止while (tempNode.getRightType()==1){tempNode=tempNode.getRight();System.out.println(tempNode);}tempNode=tempNode.getRight(); //移动当前节点}}}// 构建节点
class Employee_Node{private int number;private String name;private Employee_Node left; //左子节点private Employee_Node right; //右子节点private int leftType=0; // 0表示left指向左子树,1表示指向前驱节点private int rightType=0; // 0表示right指向右子树,1表示指向后继节点public Employee_Node(int number, String name) {this.number = number;this.name = name;}public int getNumber() {return number;}public void setNumber(int number) {this.number = number;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Employee_Node getLeft() {return left;}public void setLeft(Employee_Node left) {this.left = left;}public Employee_Node getRight() {return right;}public void setRight(Employee_Node right) {this.right = right;}public int getLeftType() {return leftType;}public void setLeftType(int leftType) {this.leftType = leftType;}public int getRightType() {return rightType;}public void setRightType(int rightType) {this.rightType = rightType;}@Overridepublic String toString() {return "Employee_Node{" +"number=" + number +", name='" + name+"}";}
}
【二叉树:3】线索二叉树相关推荐
- 重拾算法(3)——用458329个测试用例全面测试二叉树和线索二叉树的遍历算法
重拾算法(3)--用458329个测试用例全面测试二叉树和线索二叉树的遍历算法 在"上一篇"和"上上一篇"中,我给出了二叉树和线索二叉树的遍历算法.给出算法容易 ...
- 深入学习二叉树(二) 线索二叉树
深入学习二叉树(二) 线索二叉树 1 前言 在上一篇简单二叉树的学习中,初步介绍了二叉树的一些基础知识,本篇文章将重点介绍二叉树的一种变形--线索二叉树. 2 线索二叉树 2.1 产生背景 现有一棵结 ...
- 8.遍历二叉树、线索二叉树、森林
思考 一.什么遍历二叉树.线索二叉树.森林?(What) 0.二叉树遍历一共(4种) 1.二叉树先序遍历 2.二叉树中序遍历 3.二叉树后序遍历 4.遍历分析 5.遍历二叉树 6.二叉树层次遍历 7. ...
- 线索二叉树,画图教你秒懂线索二叉树(线索二叉树的建立和简单操作)逻辑代码分析
数据结构专升本学习,线索二叉树 前言 前面我们学习树和二叉树的一些基本操作,今天我们学习一个新的知识,学习一下线索二叉树,线索二叉树是由二叉链存储结构变化而来的(我们先得有个二叉链树,再做处理),就是 ...
- 【Java数据结构与算法】第十一章 顺序存储二叉树、线索二叉树和堆
第十一章 顺序存储二叉树.线索化二叉树.大顶堆.小顶堆和堆排序 文章目录 第十一章 顺序存储二叉树.线索化二叉树.大顶堆.小顶堆和堆排序 一.顺序存储二叉树 1.介绍 2.代码实现 二.线索二叉树 1 ...
- 数据结构入门----遍历二叉树和线索二叉树
遍历二叉树(Traversing Binary Tree) 二叉树的遍历是指从根结点出发,按照某种次序访问二叉树中的所有结点,使得每个结点被访问一次且仅被访问一次. 实际应用中,查找树中符合条件的结点 ...
- c语言线索二叉树作用,线索二叉树(C语言)
实现下面这棵树: 先序遍历: A B C D E F 中序遍历: C B D A E F 代码 #include #include #include #include typedef enum {li ...
- 二叉树的层序遍历和二叉树的线索化
先根,后子树:先左子树,后右子树 二叉树的根节点 a 入队 a 的子树,根节点 b 和 c 分别入队 然后 b 的子树的根节点入队(为空) c 的子树的根节点入队 d 的子树的根节点入队(为空) e ...
- 线索二叉树(c/c++)
线索二叉树 定义: 对于n个结点的二叉树,在二叉链存储结构中有n+1个空链域,利用这些空链域存放在某种遍历次序下该结点的前驱结点和后继结点的指针,这些指针称为线索,加上线索的二叉树称为线索二叉树. 特 ...
- 【线索二叉树详解】数据结构06(java实现)
线索二叉树 1. 线索二叉树简介 定义: 在二叉树的结点上加上线索的二叉树称为线索二叉树. 二叉树的线索化: 对二叉树以某种遍历方式(如先序.中序.后序或层次等)进行遍历,使其变为线索二叉树的过程称为 ...
最新文章
- When Cyber Security Meets Machine Learning 机器学习 安全分析 对于安全领域的总结很有用 看未来演进方向...
- C# 3.0新特性之扩展方法
- vs html自动对齐,vscode esLint 保存时 自动对齐
- PHP控制转盘抽奖代码,PHP 根据概率 实现抽奖转盘算法 代码
- Mac OS安装octave出现的问题-'error:terminal type set to 'unknown'的解决'
- PHP操作文件和目录的相关函数
- Halcon算子学习:surface_normals_object_model_3d
- RPM 的介绍和应用
- codeforces 1221 A B C D
- NLG ≠ 机器写作 | 专家专栏
- 2021 年软件开发趋势方向
- PHP+crontab 完美实现定时任务
- phpstudy之解决phpmyadmin卡顿的方法
- Eclipse阿里云镜像源配置
- 城市售票网关于使用selenium撞库的一点心得,可以在抢票时间卡进去
- r语言中怎样查看函数源代码
- 【黄啊码】七夕来了,用JQuery给你女票表白吧
- Edge浏览器出现翻译不了页面,扩展插件无法下载解决办法
- 谁小时候没有计算机课,你的童年电脑记忆里有它们吗?
- 粒子炫酷黑个人页源码