线索化二叉树的创建与遍历

线索化二叉树

先看一个问题来理解什么是线索化二叉树

有一个数列存的是科大学生的面试成绩{1,3,6,8,10,14},将他构建成一颗二叉树

在这里插入图片描述

我们发现就是一个完全二叉树

问题是什么?

当我们对上面的二叉树进行中序遍历时,数列为{8,3, 10,1,14,6}但是6, 8, 10,14这几个节点的左右指针,并没有完全的利用上.如果我们希望充分的利用各个节点的左右指针,让各个节点可以指向自己的前后节点怎么办?

基本介绍

1.n个结点的二叉链表中含有n+1公式2n-(n-1)=n+1空指针域利用二叉链表中的空指针域,**存放指向该结点在某种遍历次序下的前驱和后继结点的指针(**这种附加的指针称为"线索")

这种加上了线索的二叉链表称为线索链表,相应的二叉树称为线索二叉树(Threaded BinaryTree)。根据线索性质的不同,线索二叉树可分为前序线索二叉树、中序线索二叉树和后序线索二叉树三种

怎么说呢?怎么理解进行中序遍历时,数列为{8,3, 10,1,14,6}

8这个节点没利用完全,在中序遍历时,他就可以在增加一个后继节点指向3

扫描二维码关注公众号,回复: 12928763 查看本文章

6这个节点有一个没利用,在中序遍历时,他就可以增加一个前驱节点指向1

请完成中序遍历线索化二叉树的代码

在这里插入图片描述

有一些容易误导的地方,比如说1,他如果按照中序遍历线索化二叉树,它的前驱节点不应该指向10么?但是他的左指针明明指向的是3呀

说明:

当线索化二叉树后, Node节点的属性left和right,有如下情况:

1.left指向的是左子树也可能是指向的前驱节点.比如①节点left指向的左子树,而10节点的left指向 的就是前驱结点

2.right指向的是右子树,也可能是指向后继节点,比如①节点right指向的是右子树,而10节点的right指向的是后继节点.

代码实现

package;
//线索化二叉树
//2021年1月27日15:48:29
//作者  王
public class ThreadedBinnaryTreeDemo {
    
    
	public static void main(String[] args) {
    
    
		//测试中序先锁二叉树
		HeroNode1 root = new HeroNode1(1, "tom");
		HeroNode1 node2 = new HeroNode1(3, "jack");
		HeroNode1 node3 = new HeroNode1(6, "smith");
		HeroNode1 node4 = new HeroNode1(8, "wang");
		HeroNode1 node5 = new HeroNode1(10, "huang");
		HeroNode1 node6 = new HeroNode1(14, "taotao");
		root.setLeft(node2);
		root.setRight(node3);
		node2.setLeft(node4);
		node2.setRight(node5);
		node3.setLeft(node6);
		
		ThreadedBinnaryTree binnaryTree = new ThreadedBinnaryTree();
		binnaryTree.setRoot(root);
		binnaryTree.threadedNodes();
		
		//测试10号节点
		HeroNode1 leftNode = node5.getLeft();
		System.out.println("10号节点的前驱节点是"+leftNode);
		System.out.println("10号节点的后继节点是"+node5.getRight());
	}
}

//先创建HeroNode节点
class HeroNode1{
    
    
	private int no;
	private String name;
	private HeroNode1 left;
	private HeroNode1 right;
	//用来标识这个指针指向的是子树还是前驱结点
	//如果leftType == 0 表示指向的是左子树,如果等于1则表示指向前驱结点
	//如果rightType == 0 表示指向的是右子树,如果等于1则表示指向后继结点
	private int leftType;
	private int rightType;

	public HeroNode1(int no, String name) {
    
    
		super();
		this.no = no;
		this.name = name;
	}
	public int getNo() {
    
    
		return no;
	}
	public void setNo(int no) {
    
    
		this.no = no;
	}
	public String getName() {
    
    
		return name;
	}
	public void setName(String name) {
    
    
		this.name = name;
	}
	public HeroNode1 getLeft() {
    
    
		return left;
	}
	public void setLeft(HeroNode1 left) {
    
    
		this.left = left;
	}
	public HeroNode1 getRight() {
    
    
		return right;
	}
	public void setRight(HeroNode1 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;
	}
	@Override
	public String toString() {
    
    
		return "HeroNode1 [no=" + no + ", name=" + name + "]";
	}
	//编写前序遍历方法
	public void preOrder(){
    
    
		System.out.println(this);//先输出父节点
		//递归向左子树前序遍历
		if(this.left != null){
    
    
			this.left.preOrder();
		}
		//递归向右子树前序遍历
		if(this.right != null){
    
    
			this.right.preOrder();
		}
	}
	//中序遍历
	public void infixOrder(){
    
    
		//递归向左子树中序遍历
		if(this.left != null){
    
    
			this.left.infixOrder();
		}
		System.out.println(this);//输出父节点
		//向右递归中序遍历子树
		if(this.right != null){
    
    
			this.right.infixOrder();
		}
	}
	
	
	//后序遍历
	public void postOrder(){
    
    
		if(this.left != null){
    
    
			this.left.postOrder();
		}
		if(this.right != null){
    
    
			this.right.postOrder();
		}
		System.out.println(this);//输出父节点
	}
	/**
	 * 前序遍历查找
	 * @param no  待查找编号
	 * @return
	 */
	public HeroNode1 preOrderSearch(int no){
    
    
		//判断当前节点是不是
		if(this.no == no){
    
    
			return this;
		}
		//判断左子节点是否为空
		HeroNode1 resNode = null;
		if(this.left != null){
    
    
			resNode = this.left.preOrderSearch(no);
		}
		if(resNode != null){
    
    
			//说明左子树上找到了
			return resNode;
		}
		if(this.right != null){
    
    
			resNode = this.right.preOrderSearch(no);
		}
		return resNode;
	}
	//中序遍历查找
	public HeroNode1 infixOrderSearch(int no){
    
    
		HeroNode1 resNode = null;
		//先判断当前节点的左子节点
		if(this.left != null){
    
    
			resNode = this.left.infixOrderSearch(no);
		}
		if(resNode != null){
    
    
			return resNode;
		}
		if(this.no == no){
    
    
			return this;
		}
		if(this.right != null){
    
    
			resNode = this.right.infixOrderSearch(no);
		}
		return resNode;
	}
	//后序遍历查找
	public HeroNode1 postOrderSearch(int no){
    
    
		HeroNode1 resNode = null;
		if(this.left != null){
    
    
			resNode = this.left.postOrderSearch(no);
		}
		if(resNode != null){
    
    
			return resNode;
		}
		if(this.right != null){
    
    
			resNode = this.right.postOrderSearch(no);
		}
		if(resNode != null){
    
    
			return resNode;
		}
		if(this.no == no)
			return this;
		return resNode;
	}
	//递归删除节点
	//1.删除叶子结点的话,我们直接删除就OK了
	//2.如果删除的节点是非叶子节点,则要删除这个子树
	public void deleteHeroNode(int no){
    
    
		//思路如果当前节点的左子节点不为空,
		//并且左子节点就是需要删除的节点,也就是说要删除油哥,
		//我们要找到小黄,判断小黄的左子节点是不是需要删除的节点,
		//是的话就将this.left = null;没有找到就去当前节点的右子节点继续判断
		//都不是就递归左子树,再去递归右子树
		if(this.left != null && this.left.no == no){
    
    
			this.left = null;
			return;
		}
		if(this.right != null && this.right.no == no){
    
    
			this.right = null;
			return;
		}
		//向左子树递归
		if(this.left != null){
    
    
			this.left.deleteHeroNode(no);
		}
		//向右子树递归删除
		if(this.right != null){
    
    
			this.right.deleteHeroNode(no);
		}
	}
}
//定义一个ThreadedBinnaryTree二叉树
class ThreadedBinnaryTree{
    
    
	private HeroNode1 root;
	//为了实现线索化,需要增加指向当前节点的前驱结点的指针遍历
	//pre   递归线索化时  一直保留前一个节点
	private HeroNode1 pre = null;

	public void setRoot(HeroNode1 root) {
    
    
		this.root = root;
	}
	public void threadedNodes(){
    
    
		this.threadedNodes(root);
	}
	/**
	 * /编写对二叉树进行中序线索化的方法
	 * @param node    node就是需要线索化的节点
	 */
	public void threadedNodes(HeroNode1 node){
    
    
		//如果当前节点为空,则不能线索化
		if(node == null){
    
    
			return;
		}else{
    
    
			//先线索化左子树(因为是中序遍历嘛)
			threadedNodes(node.getLeft());
			//线索化当前节点{难度}
			//先处理当前节点的前驱节点
			if(node.getLeft() == null){
    
    
				//让当前节点的左指针指向前驱节点
				node.setLeft(pre);
				//修改当前节点的左指针的类型
				node.setLeftType(1);//当前节点的左指针指向的是前驱结点
			}
			//处理后继节点
			if(pre != null && pre.getRight() == null){
    
    
				//让前驱节点的右指针指向当前节点
				pre.setRight(node);
				pre.setRightType(1);//修改类型
			}
			//每处理一个节点后,让当前的节点是下一个节点的前驱节点
			pre = node; 
			//线索化右子树
			threadedNodes(node.getRight());
		}
	}
	
	//前序遍历
	public void preOrder(){
    
    
		if(this.root != null){
    
    
			this.root.preOrder();
		}else{
    
    
			System.out.println("二叉树为空,无法遍历");
		}
	}
	//中序遍历
	public void infixOrder(){
    
    
		if(this.root != null){
    
    
			this.root.infixOrder();
		}else{
    
    
			System.out.println("二叉树为空,无法遍历");
		}
	}
	//后序遍历
	public void postOrder(){
    
    
		if(this.root != null){
    
    
			this.root.postOrder();
		}else{
    
    
			System.out.println("二叉树为空,无法遍历");
		}
	}
	//前序遍历查找 
	public HeroNode1 preOrderSearch(int no){
    
    
		if(root != null){
    
    
			return root.preOrderSearch(no);
		}else{
    
    
			return null;
		}
	}
	//中序遍历查找 
	public HeroNode1 infixOrderSearch(int no){
    
    
		if(root != null){
    
    
			return root.infixOrderSearch(no);
		}else{
    
    
			return null;
		}
	}
	//后续序遍历查找 
	public HeroNode1 postOrderSearch(int no){
    
    
		if(root != null){
    
    
			return root.postOrderSearch(no);
		}else{
    
    
			return null;
		}
	}
	/**
	 * 删除节点操作
	 * @param no   需要删除的节点
	 */
	public void deleteHeroNode(int no){
    
    
		if(root == null){
    
    
			System.out.println("该二叉树为空树,不能删除");
		}else{
    
    
			//如果root只有一个节点,这里立即判断root是不是要删除的节点
			//否则往下一找就没机会回来了
			if(root.getNo() == no){
    
    
				root = null;
			}else{
    
    
				//递归删除
				root.deleteHeroNode(no);
			}
		}
	}
}

线索化二叉树的遍历

当线索化二叉树后就不能在使用原来的遍历方式了,因为线索化二叉树后,我们把节点都利用起来,连接起来了,导致我们在遍历这种方式,他一直有左右节点,一直会调用自己,导致我们的栈溢出,这时需要新的方式遍历线索化二叉树,各个节点可以通过线型方式遍历,无需再用递归的方式,遍历的次序应当和中序遍历保持一致

代码

//遍历线索化二叉树的方法
	public void threadedList(){
    
    
		//定义一个变量,临时存储当前遍历的节点,从root开始
		HeroNode1 node = root;
		while(node != null){
    
    
			//循环的找到leftType = 1的节点,就是找到我们的端点
			//第一个找到的就是8节点,后面随着遍历而变化
			//因为当leftType = 1时,说明该节点是按照线索化处理后的有效节点
			while(node.getLeftType() == 0){
    
    
				node = node.getLeft();
			}
			//打印当前节点
			System.out.println(node);
			//如果当前节点的右指针,指向的是后继节点,就一直输出
			while(node.getRightType() == 1){
    
    
				//获取当前节点的后继节点
				node = node.getRight();
				System.out.println(node);
			}
			//替换这个遍历的节点
			node = node.getRight();
		}
		
	}

所有的线索化二叉树代码

package;

import org.w3c.dom.html.HTMLHRElement;

//线索化二叉树
//2021年1月27日15:48:29
//作者  王
public class ThreadedBinnaryTreeDemo {
    
    
	public static void main(String[] args) {
    
    
		//测试中序先锁二叉树
		HeroNode1 root = new HeroNode1(1, "tom");
		HeroNode1 node2 = new HeroNode1(3, "jack");
		HeroNode1 node3 = new HeroNode1(6, "smith");
		HeroNode1 node4 = new HeroNode1(8, "wang");
		HeroNode1 node5 = new HeroNode1(10, "huang");
		HeroNode1 node6 = new HeroNode1(14, "taotao");
		root.setLeft(node2);
		root.setRight(node3);
		node2.setLeft(node4);
		node2.setRight(node5);
		node3.setLeft(node6);
		
		ThreadedBinnaryTree binnaryTree = new ThreadedBinnaryTree();
		binnaryTree.setRoot(root);
		binnaryTree.threadedNodes();
		
		//测试10号节点
		HeroNode1 leftNode = node5.getLeft();
		System.out.println("10号节点的前驱节点是"+leftNode);
		System.out.println("10号节点的后继节点是"+node5.getRight());
		System.out.println("测试我们线性遍历线索化二叉树");
		binnaryTree.threadedList();
	}
}

//先创建HeroNode节点
class HeroNode1{
    
    
	private int no;
	private String name;
	private HeroNode1 left;
	private HeroNode1 right;
	//用来标识这个指针指向的是子树还是前驱结点
	//如果leftType == 0 表示指向的是左子树,如果等于1则表示指向前驱结点
	//如果rightType == 0 表示指向的是右子树,如果等于1则表示指向后继结点
	private int leftType;
	private int rightType;
	
	
	
	public HeroNode1(int no, String name) {
    
    
		super();
		this.no = no;
		this.name = name;
	}
	public int getNo() {
    
    
		return no;
	}
	public void setNo(int no) {
    
    
		this.no = no;
	}
	public String getName() {
    
    
		return name;
	}
	public void setName(String name) {
    
    
		this.name = name;
	}
	public HeroNode1 getLeft() {
    
    
		return left;
	}
	public void setLeft(HeroNode1 left) {
    
    
		this.left = left;
	}
	public HeroNode1 getRight() {
    
    
		return right;
	}
	public void setRight(HeroNode1 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;
	}
	@Override
	public String toString() {
    
    
		return "HeroNode1 [no=" + no + ", name=" + name + "]";
	}
	//编写前序遍历方法
	public void preOrder(){
    
    
		System.out.println(this);//先输出父节点
		//递归向左子树前序遍历
		if(this.left != null){
    
    
			this.left.preOrder();
		}
		//递归向右子树前序遍历
		if(this.right != null){
    
    
			this.right.preOrder();
		}
	}
	//中序遍历
	public void infixOrder(){
    
    
		//递归向左子树中序遍历
		if(this.left != null){
    
    
			this.left.infixOrder();
		}
		System.out.println(this);//输出父节点
		//向右递归中序遍历子树
		if(this.right != null){
    
    
			this.right.infixOrder();
		}
	}
	
	
	//后序遍历
	public void postOrder(){
    
    
		if(this.left != null){
    
    
			this.left.postOrder();
		}
		if(this.right != null){
    
    
			this.right.postOrder();
		}
		System.out.println(this);//输出父节点
	}
	/**
	 * 前序遍历查找
	 * @param no  待查找编号
	 * @return
	 */
	public HeroNode1 preOrderSearch(int no){
    
    
		//判断当前节点是不是
		if(this.no == no){
    
    
			return this;
		}
		//判断左子节点是否为空
		HeroNode1 resNode = null;
		if(this.left != null){
    
    
			resNode = this.left.preOrderSearch(no);
		}
		if(resNode != null){
    
    
			//说明左子树上找到了
			return resNode;
		}
		if(this.right != null){
    
    
			resNode = this.right.preOrderSearch(no);
		}
		return resNode;
	}
	//中序遍历查找
	public HeroNode1 infixOrderSearch(int no){
    
    
		HeroNode1 resNode = null;
		//先判断当前节点的左子节点
		if(this.left != null){
    
    
			resNode = this.left.infixOrderSearch(no);
		}
		if(resNode != null){
    
    
			return resNode;
		}
		if(this.no == no){
    
    
			return this;
		}
		if(this.right != null){
    
    
			resNode = this.right.infixOrderSearch(no);
		}
		return resNode;
	}
	//后序遍历查找
	public HeroNode1 postOrderSearch(int no){
    
    
		HeroNode1 resNode = null;
		if(this.left != null){
    
    
			resNode = this.left.postOrderSearch(no);
		}
		if(resNode != null){
    
    
			return resNode;
		}
		if(this.right != null){
    
    
			resNode = this.right.postOrderSearch(no);
		}
		if(resNode != null){
    
    
			return resNode;
		}
		if(this.no == no)
			return this;
		return resNode;
	}
	//递归删除节点
	//1.删除叶子结点的话,我们直接删除就OK了
	//2.如果删除的节点是非叶子节点,则要删除这个子树
	public void deleteHeroNode(int no){
    
    
		//思路如果当前节点的左子节点不为空,
		//并且左子节点就是需要删除的节点,也就是说要删除油哥,
		//我们要找到小黄,判断小黄的左子节点是不是需要删除的节点,
		//是的话就将this.left = null;没有找到就去当前节点的右子节点继续判断
		//都不是就递归左子树,再去递归右子树
		if(this.left != null && this.left.no == no){
    
    
			this.left = null;
			return;
		}
		if(this.right != null && this.right.no == no){
    
    
			this.right = null;
			return;
		}
		//向左子树递归
		if(this.left != null){
    
    
			this.left.deleteHeroNode(no);
		}
		//向右子树递归删除
		if(this.right != null){
    
    
			this.right.deleteHeroNode(no);
		}
	}
}
//定义一个ThreadedBinnaryTree二叉树
class ThreadedBinnaryTree{
    
    
	private HeroNode1 root;
	//为了实现线索化,需要增加指向当前节点的前驱结点的指针遍历
	//pre   递归线索化时  一直保留前一个节点
	private HeroNode1 pre = null;

	public void setRoot(HeroNode1 root) {
    
    
		this.root = root;
	}
	public void threadedNodes(){
    
    
		this.threadedNodes(root);
	}
	//遍历线索化二叉树的方法
	public void threadedList(){
    
    
		//定义一个变量,临时存储当前遍历的节点,从root开始
		HeroNode1 node = root;
		while(node != null){
    
    
			//循环的找到leftType = 1的节点,就是找到我们的端点
			//第一个找到的就是8节点,后面随着遍历而变化
			//因为当leftType = 1时,说明该节点是按照线索化处理后的有效节点
			while(node.getLeftType() == 0){
    
    
				node = node.getLeft();
			}
			//打印当前节点
			System.out.println(node);
			//如果当前节点的右指针,指向的是后继节点,就一直输出
			while(node.getRightType() == 1){
    
    
				//获取当前节点的后继节点
				node = node.getRight();
				System.out.println(node);
			}
			//替换这个遍历的节点
			node = node.getRight();
            //如果我们不替换的话,就会一值输出,卡死
		}
		
	}
	
	/**
	 * /编写对二叉树进行中序线索化的方法
	 * @param node    node就是需要线索化的节点
	 */
	public void threadedNodes(HeroNode1 node){
    
    
		//如果当前节点为空,则不能线索化
		if(node == null){
    
    
			return;
		}else{
    
    
			//先线索化左子树(因为是中序遍历嘛)
			threadedNodes(node.getLeft());
			//线索化当前节点{难度}
			//先处理当前节点的前驱节点
			if(node.getLeft() == null){
    
    
				//让当前节点的左指针指向前驱节点
				node.setLeft(pre);
				//修改当前节点的左指针的类型
				node.setLeftType(1);//当前节点的左指针指向的是前驱结点
			}
			//处理后继节点
			if(pre != null && pre.getRight() == null){
    
    
				//让前驱节点的右指针指向当前节点
				pre.setRight(node);
				pre.setRightType(1);//修改类型
			}
			//每处理一个节点后,让当前的节点是下一个节点的前驱节点
			pre = node; 
			//线索化右子树
			threadedNodes(node.getRight());
		}
	}
	
	//前序遍历
	public void preOrder(){
    
    
		if(this.root != null){
    
    
			this.root.preOrder();
		}else{
    
    
			System.out.println("二叉树为空,无法遍历");
		}
	}
	//中序遍历
	public void infixOrder(){
    
    
		if(this.root != null){
    
    
			this.root.infixOrder();
		}else{
    
    
			System.out.println("二叉树为空,无法遍历");
		}
	}
	//后序遍历
	public void postOrder(){
    
    
		if(this.root != null){
    
    
			this.root.postOrder();
		}else{
    
    
			System.out.println("二叉树为空,无法遍历");
		}
	}
	//前序遍历查找 
	public HeroNode1 preOrderSearch(int no){
    
    
		if(root != null){
    
    
			return root.preOrderSearch(no);
		}else{
    
    
			return null;
		}
	}
	//中序遍历查找 
	public HeroNode1 infixOrderSearch(int no){
    
    
		if(root != null){
    
    
			return root.infixOrderSearch(no);
		}else{
    
    
			return null;
		}
	}
	//后续序遍历查找 
	public HeroNode1 postOrderSearch(int no){
    
    
		if(root != null){
    
    
			return root.postOrderSearch(no);
		}else{
    
    
			return null;
		}
	}
	/**
	 * 删除节点操作
	 * @param no   需要删除的节点
	 */
	public void deleteHeroNode(int no){
    
    
		if(root == null){
    
    
			System.out.println("该二叉树为空树,不能删除");
		}else{
    
    
			//如果root只有一个节点,这里立即判断root是不是要删除的节点
			//否则往下一找就没机会回来了
			if(root.getNo() == no){
    
    
				root = null;
			}else{
    
    
				//递归删除
				root.deleteHeroNode(no);
			}
		}
	}
	
}

猜你喜欢

转载自blog.csdn.net/qq_22155255/article/details/113268939