JAVA 数据结构与算法(五)—— 树结构之二叉树

一、树结构

1、树结构概述

(1)简介

  • 树是一种重要的非线性数据结构,它是数据元素(在树中称为结点)按分支关系组织起来的结构,很象自然界中的树那样。
  • 一棵树(tree)是由n(n>0)个元素组成的有限集合,其中:
    • 每个元素称为结点(node);
    • 有一个特定的结点,称为根结点或根(root);
    • 除根结点外,其余结点被分成m(m>=0)个互不相交的有限集合,而每个子集又都是一棵树(称为原树的子树)。
      在这里插入图片描述

(2)相关概念

  • 结点的度:结点拥有的子树的数目。如上图:结点 A 的度为3。
  • 树的度:树种各结点度的最大值。如上图:树的度为3。
  • 叶子结点:没有子结点的结点。如上图:F、I、J、K、L、M为叶子结点。
  • 孩子结点:一个结点的子树的根结点。如上图:B、C、D 为 A 的孩子结点。
  • 双亲结点:B 为 A 的子结点,那么 A 为 B 的双亲结点。
  • 兄弟结点:一个双亲结点的孩子结点互为兄弟结点。如上图:B、C、D 为兄弟结点。
  • 结点的层次:根结点为第一层,子结点为第二层,依次向下递推…。如上图:E、F、G、H的层次均为 3。
  • 树的深度:树种结点的最大深度。如上图:该树的深度为 4。
  • 路径:从根结点找到该结点的路线,如K结点的路径为A-C-G-K。
  • 森林:指若干棵互不相交的树的集合。

(3)树的遍历

  • 树的遍历是树的一种重要的运算。所谓遍历是指对树中所有结点的系统的访问,即依次对树中每个结点访问一次且仅访问一次。树的3种最重要的遍历方式分别称为前序遍历、中序遍历和后序遍历。以这3种方式遍历一棵树时,若按访问结点的先后次序将结点排列起来,就可分别得到树中所有结点的前序列表,中序列表和后序列表。相应的结点次序分别称为结点的前序、中序和后序。
  • 树的这3种遍历方式可递归地定义如下:
    • 如果T是一棵空树,那么对T进行前序遍历、中序遍历和后序遍历都是空操作,得到的列表为空表。
    • 如果T是一棵单结点树,那么对T进行前序遍历、中序遍历和后序遍历都只访问这个结点。这个结点本身就是要得到的相应列表。
    • 否则,它以n为树根,树根的子树从左到右依次为T1,T2,…,Tk,那么有:
      对T进行前序遍历是先访问树根n,然后依次前序遍历T1,T2,…,Tk。
      对T进行中序遍历是先中序遍历T1,然后访问树根n,接着依次对T2,T2,…,Tk进行中序遍历。
      对T进行后序遍历是先依次对T1,T2,…,Tk进行后序遍历,最后访问树根n。

(4)树结构的性质

  • 非空树的结点总数等于树种所有结点的度之和加 1。
  • 度为 K 的非空树的第 i 层最多有 ki-1 个结点(i >= 1)。
  • 深度为 h 的 k 叉树最多有(kh - 1)/(k - 1)个结点。
  • 具有 n 个结点的 k 叉树的最小深度为 logk(n(k-1)+1))。

2、树结构的分类

在这里插入图片描述

3、存储方式分析

(1)数组存储方式

  • 优点:通过下标方式访问元素,速度快,对于有序数组,还可使用二分查找提高检索速度。
  • 缺点:如果要检索具体某个值,或者插入值(按一定顺序)会整体移动, 效率较低。

(2)链式存储方式

  • 优点:在一定程度上对数组存储方式有优化(比如插入一个数值结点,只需要将插入结点,链接到链表中即可,删除效率也很好)。
  • 缺点:在进行检索时,效率仍然较低,比如(检索某个值,需要从头结点开始遍历)。

(3)树存储方式

  • 能提高数据存储,读取的效率,比如利用 二叉排序树(Binary Sort Tree),既可以保证数据的检索速度,同时也可以保证数据的插入,删除,修改的速度。

二、二叉树

1、二叉树概述

(1)二叉树介绍

  • 树有很多种,每个结点最多只能有两个子结点的一种形式称为二叉树。
  • 二叉树的子结点分为左结点和右结点。
    在这里插入图片描述

(2)满二叉树

  • 如果该二叉树的所有叶子结点都在最后一层,并且结点总数=2^n-1,n为层数,则我们称为满二叉树。
    在这里插入图片描述

(3)完全二叉树

  • 如果该二叉树的所有叶子结点都在最后一层或者倒数第二层,而且最后一层的叶子结点在左边连续,倒数第二层的叶子结点在右边连续,我们称为完全二叉树。
    在这里插入图片描述

2、二叉树的遍历

(1)二叉树的遍历介绍
二叉树的遍历有三种:前序遍历、中序遍历、后序遍历。

  • 前序遍历
    前序遍历是先输父结点,再遍历左子树和右子树。
  • 中序遍历
    中序遍历先遍历左子树,再输出父结点,再遍历右子树。
  • 后序遍历
    后序遍历先遍历左子树,再遍历右子树,最后输出父结点。

分析:看输出父结点的顺序,就确定是前序,中序还是后序。

(2)二叉树的遍历分析
以下面的二叉树为例,进行遍历分析:
在这里插入图片描述

  • 前序遍历
    • 先输出当前结点(初始的时候是root结点)
    • 如果左子结点不为空,则递归继续前序遍历
    • 如果右子结点不为空,则递归继续前序遍历
    • 上述结果应为:A-B-D-G-E-C-F-H
  • 中序遍历
    • 如果当前结点的左子结点不为空,则递归中序遍历,
    • 输出当前结点
    • 如果当前结点的右子结点不为空,则递归中序遍历
    • 上述结果应为:G-D-B-E-A-C-H-F
  • 后序遍历
    • 如果当前结点的左子结点不为空,则递归后序遍历,
    • 如果当前结点的右子结点不为空,则递归后序遍历
    • 输出当前结点
    • 上述结果应为:G-D-E-B-H-F-C-A

(3)二叉树的遍历代码示例
创建一个二叉树结点类

/*结点*/
public class TreeNode {
    /*结点编号*/
    private int nodeNum;
    /*结点名称*/
    private String nodeName;
    /*左右结点,默认为空*/
    private TreeNode leftNode;
    private TreeNode rightNode;

    /*构造器*/
    public TreeNode(int nodeNum, String nodeName) {
        this.nodeNum = nodeNum;
        this.nodeName = nodeName;
    }

    /*getter、setter方法省略*/

    /*toString方法*/
    @Override
    public String toString() {
        return "TreeNode{" +
                "nodeNum=" + nodeNum +
                ", nodeName='" + nodeName + '\'' +
                '}';
    }

    /*前序遍历*/
    public void firstShow(){
        /*输出父结点*/
        System.out.println(this);

        /*递归向左子树前序遍历*/
        if(this.leftNode != null){
            this.leftNode.firstShow();
        }

        /*递归向右子树前序遍历*/
        if(this.rightNode != null){
            this.rightNode.firstShow();
        }
    }

    /*中序遍历*/
    public void midShow(){
        /*递归向左子树前序遍历*/
        if(this.leftNode != null){
            this.leftNode.midShow();
        }

        /*输出父结点*/
        System.out.println(this);

        /*递归向右子树前序遍历*/
        if(this.rightNode != null){
            this.rightNode.midShow();
        }
    }

    /*后序遍历*/
    public void lastShow(){
        /*递归向左子树前序遍历*/
        if(this.leftNode != null){
            this.leftNode.lastShow();
        }

        /*递归向右子树前序遍历*/
        if(this.rightNode != null){
            this.rightNode.lastShow();
        }

        /*输出父结点*/
        System.out.println(this);
    }
}

创建一个二叉树

/*二叉树*/
public class BinaryTree {
    private TreeNode rootNode;

    public void setRootNode(TreeNode rootNode) {
        this.rootNode = rootNode;
    }

    /*前序遍历*/
    public void first(){
        if(this.rootNode != null){
            this.rootNode.firstShow();
        }else{
            System.out.println("二叉树为空");
        }
    }

    /*中序遍历*/
    public void mid(){
        if(this.rootNode != null){
            this.rootNode.midShow();
        }else{
            System.out.println("二叉树为空");
        }
    }

    /*后序遍历*/
    public void last(){
        if(this.rootNode != null){
            this.rootNode.lastShow();
        }else{
            System.out.println("二叉树为空");
        }
    }
}

测试代码

public class BinaryTreeTest {
    public static void main(String[] args) {
        /*创建二叉树*/
        BinaryTree binaryTree = new BinaryTree();

        /*创建树结点*/
        TreeNode nodeA = new TreeNode(1, "A");
        TreeNode nodeB = new TreeNode(2, "B");
        TreeNode nodeC = new TreeNode(3, "C");
        TreeNode nodeD = new TreeNode(4, "D");
        TreeNode nodeE = new TreeNode(5, "E");
        TreeNode nodeF = new TreeNode(6, "F");
        TreeNode nodeG = new TreeNode(7, "G");
        TreeNode nodeH = new TreeNode(8, "H");

        /*将结点挂载到root结点上*/
        nodeA.setLeftNode(nodeB);
        nodeA.setRightNode(nodeC);
        nodeB.setLeftNode(nodeD);
        nodeB.setRightNode(nodeE);
        nodeC.setRightNode(nodeF);
        nodeD.setLeftNode(nodeG);
        nodeF.setLeftNode(nodeH);

        /*将root结点挂载到树上*/
        binaryTree.setRootNode(nodeA);

        System.out.println("前序遍历结果为:");
        binaryTree.first();
        System.out.println("\n中序遍历结果为:");
        binaryTree.mid();
        System.out.println("\n后序遍历结果为:");
        binaryTree.last();
    }
}

输出结果

前序遍历结果为:
TreeNode{nodeNum=1, nodeName='A'}
TreeNode{nodeNum=2, nodeName='B'}
TreeNode{nodeNum=4, nodeName='D'}
TreeNode{nodeNum=7, nodeName='G'}
TreeNode{nodeNum=5, nodeName='E'}
TreeNode{nodeNum=3, nodeName='C'}
TreeNode{nodeNum=6, nodeName='F'}
TreeNode{nodeNum=8, nodeName='H'}

中序遍历结果为:
TreeNode{nodeNum=7, nodeName='G'}
TreeNode{nodeNum=4, nodeName='D'}
TreeNode{nodeNum=2, nodeName='B'}
TreeNode{nodeNum=5, nodeName='E'}
TreeNode{nodeNum=1, nodeName='A'}
TreeNode{nodeNum=3, nodeName='C'}
TreeNode{nodeNum=8, nodeName='H'}
TreeNode{nodeNum=6, nodeName='F'}

后序遍历结果为:
TreeNode{nodeNum=7, nodeName='G'}
TreeNode{nodeNum=4, nodeName='D'}
TreeNode{nodeNum=5, nodeName='E'}
TreeNode{nodeNum=2, nodeName='B'}
TreeNode{nodeNum=8, nodeName='H'}
TreeNode{nodeNum=6, nodeName='F'}
TreeNode{nodeNum=3, nodeName='C'}
TreeNode{nodeNum=1, nodeName='A'}

3、二叉树的查找

(1)介绍
二叉树的查找分为前序查找、中序查找、后序查找。

(2)二叉树的查找分析
同样以这个二叉树为例进行分析:
在这里插入图片描述

  • 前序查找
    • 先判断当前结点是否是要查找的结点,如果是,则返回当前结点
    • 如果不是,则判断当前结点的左子结点是否为空,如果不为空则继续递归前序查找
    • 如果左递归前序查找,找到结点,则返回
    • 如果没有找到,则继续查找,判断当前的结点的右子结点是否为空,如果不空,则继续向右递归前序查找
  • 中序查找
    • 先判断当前结点的左子结点是否为空,如果不为空则继续递归中序查找
    • 如果在左子结点中没有找到,就和当前结点比较是否为要查找的结点
    • 如果不是,则继续在当前结点向右递归进行中序查找
  • 后序查找
    • 先判断当前结点的左子结点是否为空,如果不为空则继续递归后序查找
    • 如果没有找到,就判断当前结点的右子结点是否为空,不为空则进行当前结点的右递归后序查找
    • 如果没有找,则比较当前结点是否为要查找的结点

(3)二叉树的查找代码示例
创建一个结点类,表示树的结点

/*结点*/
public class TreeNode {
    /*结点编号*/
    private int nodeNum;
    /*结点名称*/
    private String nodeName;
    /*左右结点,默认为空*/
    private TreeNode leftNode;
    private TreeNode rightNode;

    /*构造器*/
    public TreeNode(int nodeNum, String nodeName) {
        this.nodeNum = nodeNum;
        this.nodeName = nodeName;
    }

    /*getter、setter方法省略*/

    /*toString方法*/
    @Override
    public String toString() {
        return "TreeNode{" +
                "nodeNum=" + nodeNum +
                ", nodeName='" + nodeName + '\'' +
                '}';
    }

    /*前序查找*/
    public TreeNode firstSearch(int num){
        TreeNode treeNode = null;

        /*比较当前结点*/
        if(this.nodeNum == num){
            return this;
        }

        /*左递归前序查找*/
        if (this.leftNode != null){
            treeNode = this.leftNode.firstSearch(num);
        }
        if(treeNode != null){
            return treeNode;
        }

        /*右递归前序查找*/
        if (this.rightNode != null){
            treeNode = this.rightNode.firstSearch(num);
        }
        if(treeNode != null){
            return treeNode;
        }
        return treeNode;
    }

    /*中序查找*/
    public TreeNode midSearch(int num){
        TreeNode treeNode = null;

        /*先左递归中序查找*/
        if(this.leftNode != null){
            treeNode = this.leftNode.midSearch(num);
        }
        if (treeNode != null){
            return treeNode;
        }

        /*与当前结点比较*/
        if(this.nodeNum == num){
            return this;
        }

        /*向右递归中序查找*/
        if (this.rightNode != null){
            treeNode = this.rightNode.midSearch(num);
        }
        return treeNode;
    }

    /*后序查找*/
    public TreeNode lastSearch(int num){
        TreeNode treeNode = null;

        /*先左递归后序查找*/
        if(this.leftNode != null){
            treeNode = this.leftNode.lastSearch(num);
        }
        if (treeNode != null){
            return treeNode;
        }

        /*向右递归后序查找*/
        if (this.rightNode != null){
            treeNode = this.rightNode.lastSearch(num);
        }
        if (treeNode != null){
            return treeNode;
        }

        /*与当前结点比较*/
        if(this.nodeNum == num){
            return this;
        }
        return treeNode;
    }
}

创建一个二叉树用于挂载结点

在这里插入代码片

测试代码

public class BinaryTreeTest {
    public static void main(String[] args) {
        /*创建二叉树*/
        BinaryTree binaryTree = new BinaryTree();

        /*创建树结点*/
        TreeNode nodeA = new TreeNode(1, "A");
        TreeNode nodeB = new TreeNode(2, "B");
        TreeNode nodeC = new TreeNode(3, "C");
        TreeNode nodeD = new TreeNode(4, "D");
        TreeNode nodeE = new TreeNode(5, "E");
        TreeNode nodeF = new TreeNode(6, "F");
        TreeNode nodeG = new TreeNode(7, "G");
        TreeNode nodeH = new TreeNode(8, "H");

        /*将结点挂载到root结点上*/
        nodeA.setLeftNode(nodeB);
        nodeA.setRightNode(nodeC);
        nodeB.setLeftNode(nodeD);
        nodeB.setRightNode(nodeE);
        nodeC.setRightNode(nodeF);
        nodeD.setLeftNode(nodeG);
        nodeF.setLeftNode(nodeH);

        /*将root结点挂载到树上*/
        binaryTree.setRootNode(nodeA);

        TreeNode firstNode = binaryTree.first(1);
        if(firstNode != null){
            System.out.println("前序查找num = 1的结果为:num = " + firstNode.getNodeNum() + "  name = " + firstNode.getNodeName());
        }else {
            System.out.println("前序查找没有找到");
        }

        TreeNode midNode = binaryTree.mid(4);
        if(midNode != null){
            System.out.println("中序查找num = 4的结果为:num = " + midNode.getNodeNum() + "  name = " + midNode.getNodeName());
        }else {
            System.out.println("中序查找没有找到");
        }

        TreeNode lastNode = binaryTree.last(7);
        if(lastNode != null){
            System.out.println("后序查找num = 7的结果为:num = " + lastNode.getNodeNum() + "  name = " + lastNode.getNodeName());
        }else {
            System.out.println("后序查找没有找到");
        }
    }
}

输出结果

前序查找num = 1的结果为:num = 1  name = A
中序查找num = 4的结果为:num = 4  name = D
后序查找num = 7的结果为:num = 7  name = G

4、二叉树删除结点

(1)删除结点规则

  • 如果删除的结点是叶子结点,则删除该结点。
  • 如果删除的结点是非叶子结点,则删除该子树。

(2)删除结点分析
仍旧以下面的二叉树为例,进行分析:
在这里插入图片描述

  • 首先判断该树是否为空树,如果至于一个root结点,则等价于将二叉树置空
  • 因为二叉树是单向的,所以在删除是需要先判断当前结点的子结点是否需要删除结点,而不是判断当前这个结点是不是需要删除的结点。
  • 如果当前结点的左子树不为空并且左子结点左子结点就是要删除的结点,就将this.leftNode置为空并返回
  • 如果当前结点的右子树不为空并且右子结点左子结点就是要删除的结点,就将this.rightNode置为空并返回
  • 如果上述步骤都没有删除结点,那么就需要继续向左子树进行递归删除,如果仍没有删除则向右子树递归

(3)删除结点代码示例
创建一个结点类,表示树的结点

/*结点*/
public class TreeNode {
    /*结点编号*/
    private int nodeNum;
    /*结点名称*/
    private String nodeName;
    /*左右结点,默认为空*/
    private TreeNode leftNode;
    private TreeNode rightNode;

    /*构造器*/
    public TreeNode(int nodeNum, String nodeName) {
        this.nodeNum = nodeNum;
        this.nodeName = nodeName;
    }

    public int getNodeNum() {
        return nodeNum;
    }

    public void setNodeNum(int nodeNum) {
        this.nodeNum = nodeNum;
    }

    public String getNodeName() {
        return nodeName;
    }

    public void setNodeName(String nodeName) {
        this.nodeName = nodeName;
    }

    public TreeNode getLeftNode() {
        return leftNode;
    }

    public void setLeftNode(TreeNode leftNode) {
        this.leftNode = leftNode;
    }

    public TreeNode getRightNode() {
        return rightNode;
    }

    public void setRightNode(TreeNode rightNode) {
        this.rightNode = rightNode;
    }

    /*toString方法*/

    @Override
    public String toString() {
        return "TreeNode{" +
                "nodeNum=" + nodeNum +
                ", nodeName='" + nodeName + '\'' +
                '}';
    }

    /*删除结点*/
    public void delNode(int num){
        /*先判断左子结点*/
        if(this.leftNode != null && this.leftNode.nodeNum == num){
            this.leftNode = null;
            return;
        }

        /*判断右子结点*/
        if(this.rightNode != null && this.rightNode.nodeNum == num){
            this.rightNode = null;
            return;
        }

        /*向左子树递归删除*/
        if(this.leftNode != null){
            this.leftNode.delNode(num);
        }

        /*向右子树递归删除*/
        if(this.rightNode != null){
            this.rightNode.delNode(num);
        }
    }

    /*前序遍历*/
    public void firstShow(){
        /*输出父结点*/
        System.out.println(this);

        /*递归向左子树前序遍历*/
        if(this.leftNode != null){
            this.leftNode.firstShow();
        }

        /*递归向右子树前序遍历*/
        if(this.rightNode != null){
            this.rightNode.firstShow();
        }
    }
}

创建一个二叉树用于挂载结点

/*二叉树*/
public class BinaryTree {
    private TreeNode rootNode;

    public void setRootNode(TreeNode rootNode) {
        this.rootNode = rootNode;
    }

    /*删除结点*/
    public void delete(int num){
        if(rootNode != null){
            /*判断root结点*/
            if(rootNode.getNodeNum() == num){
                rootNode = null;
            }else{
                /*递归删除*/
                rootNode.delNode(num);
            }
        }else{
            System.out.println("该树为空");
        }
    }

    /*前序遍历*/
    public void first(){
        if(this.rootNode != null){
            this.rootNode.firstShow();
        }else{
            System.out.println("二叉树为空");
        }
    }
}

测试代码

public class BinaryTreeTest {
    public static void main(String[] args) {
        /*创建二叉树*/
        BinaryTree binaryTree = new BinaryTree();

        /*创建树结点*/
        TreeNode nodeA = new TreeNode(1, "A");
        TreeNode nodeB = new TreeNode(2, "B");
        TreeNode nodeC = new TreeNode(3, "C");
        TreeNode nodeD = new TreeNode(4, "D");
        TreeNode nodeE = new TreeNode(5, "E");
        TreeNode nodeF = new TreeNode(6, "F");
        TreeNode nodeG = new TreeNode(7, "G");
        TreeNode nodeH = new TreeNode(8, "H");

        /*将结点挂载到root结点上*/
        nodeA.setLeftNode(nodeB);
        nodeA.setRightNode(nodeC);
        nodeB.setLeftNode(nodeD);
        nodeB.setRightNode(nodeE);
        nodeC.setRightNode(nodeF);
        nodeD.setLeftNode(nodeG);
        nodeF.setLeftNode(nodeH);

        /*将root结点挂载到树上*/
        binaryTree.setRootNode(nodeA);

        System.out.println("删除前:");
        binaryTree.first();
        binaryTree.delete(2);
        System.out.println("删除后:");
        binaryTree.first();
    }
}

输出结果

删除前:
TreeNode{nodeNum=1, nodeName='A'}
TreeNode{nodeNum=2, nodeName='B'}
TreeNode{nodeNum=4, nodeName='D'}
TreeNode{nodeNum=7, nodeName='G'}
TreeNode{nodeNum=5, nodeName='E'}
TreeNode{nodeNum=3, nodeName='C'}
TreeNode{nodeNum=6, nodeName='F'}
TreeNode{nodeNum=8, nodeName='H'}
删除后:
TreeNode{nodeNum=1, nodeName='A'}
TreeNode{nodeNum=3, nodeName='C'}
TreeNode{nodeNum=6, nodeName='F'}
TreeNode{nodeNum=8, nodeName='H'}

5、顺序存储二叉树

(1)介绍

  • 从数据存储来看,数组存储方式和树的存储方式可以相互转换,即数组可以转换成树,树也可以转换成数组。二叉树以数组的形式存储,但仍旧保存着二叉树的结构,成为顺序存储二叉树。
    在这里插入图片描述
  • 顺序存储二叉树的特点:
    • 顺序二叉树通常只考虑完全二叉树
    • 第n个元素的左子结点为2*n+1,如B结点为数组中的第1个元素,B的左子结点为2*1+1=3,即数组中的第三个元素D
    • 第n个元素的右子结点为2*n+2,如B结点为数组中的第1个元素,B的右子结点为2*1+2=4,即数组中的第四个元素E
    • 第n个元素的父结点为(n-1)/ 2,如B结点为数组中的第1个元素,B的父结点为(1-1)/2=0,即数组中的第0个元素A

(2)代码示例
创建一个类,用于前序遍历顺序存储二叉树的数组

public class ArrayBinaryTree {
    /*存储数据结点的数组*/
    private String[] arr;

    /*构造器*/
    public ArrayBinaryTree(String[] arr) {
        this.arr = arr;
    }

    /*前序遍历*/
    public void firstShow(int index){
        /*如果数组为空或者数组长度为0*/
        if(arr == null || arr.length == 0){
            System.out.println("数组为空");
        }
        /*数组不为空则就行遍历*/
        /*输出当前元素*/
        System.out.print(arr[index] + " ");
        /*向左递归输出*/
        if((2 * index + 1) < arr.length){
            firstShow(2 * index + 1);
        }
        /*向右递归*/
        if((2 * index + 2) < arr.length){
            firstShow(2 * index + 2);
        }
    }
}

测试类代码,定义一个数组

public class ArrayBinaryTreeTest {
    public static void main(String[] args) {
        String[] arr = {"A","B","C","D","E","F","G"};

        ArrayBinaryTree arrayBinaryTree = new ArrayBinaryTree(arr);
        System.out.println("前序遍历结果:");
        arrayBinaryTree.firstShow(0);
    }
}

输出结果

前序遍历结果:
A B D E C F G 

6、线索二叉树

(1)介绍

  • n个结点的二叉链表中含有2n-(n-1)=n+1个空指针域,利用二叉链表中的空指针域,存放指向该结点在某种遍历次序下的前驱和后继结点的指针(这种附加的指针称为"线索")。
    • 如下图,这个二叉树的空指针域为6+1=7个,即箭头所代表的位置都为空指针域。
      在这里插入图片描述
  • 这种加上了线索的二叉链表称为线索链表,相应的二叉树称为线索二叉树(Threaded BinaryTree)。根据线索性质的不同,线索二叉树可分为前序线索二叉树、中序线索二叉树和后序线索二叉树三种。
  • 一个结点的前一个结点,称为前驱结点。
  • 一个结点的后一个结点,称为后继结点。

(2)线索二叉树的遍历

  • 因为线索化后,各个结点指向有变化,因此原来的遍历方式不能使用,这时需要使用新的方式遍历线索化二叉树,各个结点可以通过线型方式遍历,因此无需使用递归方式,这样也提高了遍历的效率。

(3)代码示例
以中序线索二叉树为例进行示例
创建一个结点类,表示树的结点

/*结点*/
public class TreeNode {
    /*结点编号*/
    private int nodeNum;
    /*结点名称*/
    private String nodeName;
    /*左右结点,默认为空*/
    private TreeNode leftNode;
    private TreeNode rightNode;

    /*如果leftType=0,表示指向左子树,leftType=1表示指向前驱结点*/
    private int leftType;
    /*如果rightType=0,表示指向右子树,rightType=1表示指向后驱结点*/
    private int rightType;
    
    /*构造器*/
    public TreeNode(int nodeNum, String nodeName) {
        this.nodeNum = nodeNum;
        this.nodeName = nodeName;
    }

   	 /*getter、settet方法省略*/

    /*toString方法*/

    @Override
    public String toString() {
        return "TreeNode{" +
                "nodeNum=" + nodeNum +
                ", nodeName='" + nodeName + '\'' +
                '}';
    }
}

创建一个二叉树,用于挂在树结点

/*二叉树*/
public class ThreadedBinaryTree {
    private TreeNode rootNode;

    /*前一个结点*/
    private TreeNode prevNode = null;

    public TreeNode getRootNode() {
        return rootNode;
    }

    public void setRootNode(TreeNode rootNode) {
        this.rootNode = rootNode;
    }

    /*对二叉树进行中序线索化*/
    public void threadedNodes(TreeNode treeNode){
        /*判断要线索化的结点是否为空*/
        if(treeNode == null){
            return;
        }

        /*线索化左子树*/
        threadedNodes(treeNode.getLeftNode());

        /*线索化当前结点*/
        /*处理当前结点的前驱结点*/
        if(treeNode.getLeftNode() == null){
            /*让当前结点的做指针指向前驱结点*/
            treeNode.setLeftNode(prevNode);
            /*修改当前结点的左指针的类型,指向前驱结点*/
            treeNode.setLeftType(1);
        }
        /*当前结点的处理后继结点*/
        if(prevNode != null && prevNode.getRightNode() == null){
            /*让前驱结点的右指针指向当前结点*/
            prevNode.setRightNode(treeNode);
            /*修改当前结点的右指针的类型*/
            prevNode.setRightType(1);
        }
        /*每处理一个结点后,让当前结点是下一个结点的前驱结点*/
        prevNode = treeNode;

        /*线索化右子树*/
        threadedNodes(treeNode.getRightNode());
    }

    /*遍历线索二叉树*/
    public void ThreadedsShow(){
        /*定义一个变量存储当前遍历的结点,从root开始*/
        TreeNode treeNode = rootNode;
        while(treeNode != null){
            /*循环找到leftTpye==1的结点*/
            while(treeNode.getLeftType() == 0){
                treeNode = treeNode.getLeftNode();
            }
            System.out.println(treeNode);

            /*如果当前结点的右指针是指向后继结点就一直输出*/
            while(treeNode.getRightType() == 1){
                treeNode = treeNode.getRightNode();
                System.out.println(treeNode);
            }
            /*替换遍历的结点*/
            treeNode = treeNode.getRightNode();
        }
    }
}

测试代码

public class ThreadedBinaryTreeTest {
    public static void main(String[] args) {
        /*创建二叉树*/
        TreeNode nodeA = new TreeNode(1, "A");
        TreeNode nodeB = new TreeNode(2, "B");
        TreeNode nodeC = new TreeNode(3, "C");
        TreeNode nodeD = new TreeNode(4, "D");
        TreeNode nodeE = new TreeNode(5, "E");
        TreeNode nodeF = new TreeNode(6, "F");

        /*结点挂载*/
        nodeA.setLeftNode(nodeB);
        nodeA.setRightNode(nodeC);
        nodeB.setLeftNode(nodeD);
        nodeB.setRightNode(nodeE);
        nodeC.setLeftNode(nodeF);

        ThreadedBinaryTree threadedBinaryTree = new ThreadedBinaryTree();
        threadedBinaryTree.setRootNode(nodeA);
        threadedBinaryTree.threadedNodes(threadedBinaryTree.getRootNode());

        TreeNode leftNode = nodeE.getLeftNode();
        System.out.println("nodeE的前驱结点为" + leftNode);
        TreeNode rightNode = nodeE.getRightNode();
        System.out.println("nodeE的后继结点为" + rightNode);

        System.out.println("\n遍历线索二叉树:");
        threadedBinaryTree.ThreadedsShow();
    }
}

输出结果

nodeE的前驱结点为TreeNode{nodeNum=2, nodeName='B'}
nodeE的后继结点为TreeNode{nodeNum=1, nodeName='A'}

遍历线索二叉树:
TreeNode{nodeNum=4, nodeName='D'}
TreeNode{nodeNum=2, nodeName='B'}
TreeNode{nodeNum=5, nodeName='E'}
TreeNode{nodeNum=1, nodeName='A'}
TreeNode{nodeNum=6, nodeName='F'}
TreeNode{nodeNum=3, nodeName='C'}
发布了104 篇原创文章 · 获赞 58 · 访问量 7502

猜你喜欢

转载自blog.csdn.net/baidu_27414099/article/details/104420801