二叉树(BinaryTree)的Java详细实现

关于二叉树的基本概念:二叉树基本概念

二叉树实现的方法:

 * isEmpty():判断树是否为空。
 * clear():清空二叉树。
 * add(BTNode, Type):向指定节点添加指定的孩子。
 * height():计算二叉树的高度。
 * size():计算二叉树的大小。
 * findNode():查找包含指定数据的节点是否存在。
 * findParent():查找包含指定数据的节点的父节点。
 * recursePreIterate():递归先序遍历二叉树。
 * recurseInIterate():递归中序遍历二叉树。
 * recursePostIterate():递归后序遍历二叉树。
 * circlePreIterate():循环先序遍历二叉树。
 * circleInIterate():循环中序遍历二叉树。
 * circlePostIterate():循环后序遍历二叉树。
 * layerIterate():层次遍历二叉树

首先是二叉树的节点的代码(BTNode):

/**
 * 二叉树的二叉链式存储实现的节点
 */
public class BTNode<Type>{
    /**
     * 每一个节点需要保存的内容:本身数据、左孩子、右孩子
     * data:自身数据
     * left:左孩子
     * right:右孩子
     */
    private Type data;
    private BTNode left;
    private BTNode right;

    /**
     * 三种构造方法
     */
    public BTNode(){//空构造方法
        data = null;
        left = null;
        right = null;
    }

    public BTNode(Type data){//只有数据的构造方法
        this.data = data;
        this.left = null;
        this.right = null;
    }

    //四个内容都有的构造方法
    public BTNode(Type data, BTNode left, BTNode right, BTNode parent){
        this.data = data;
        this.left = left;
        this.right = right;
    }

    /**
     * 三个属性的 get() 方法
     */
    public BTNode getLeft() {
        return left;
    }

    public BTNode getRight() {
        return right;
    }

    public Type getData() {
        return data;
    }

    /**
     * 三个属性的 set()方法
     */
    public void setData(Type data) {
        this.data = data;
    }

    public void setLeft(BTNode left) {
        this.left = left;
    }

    public void setRight(BTNode right) {
        this.right = right;
    }
}

(复制即可用)

下面是二叉树实现的代码(BinaryTree):

import stackImp.Stack;//自己实现的栈

/**
 * 二叉树的二叉链式实现
 * 三叉链式实现比二叉链式实现少一个查找父节点的方法
 * 实现过程中,需要使用的栈是我自己编写的栈,代码见我的blog文章:栈(Stack)的Java实现
 *
 * 二叉树需要实现的方法:
 *   isEmpty():判断树是否为空。
 *   clear():清空二叉树。
 *   add(BTNode, Type):向指定节点添加指定的孩子。
 *   height():计算二叉树的高度。
 *   size():计算二叉树的大小。
 *   findNode():查找包含指定数据的节点是否存在。
 *   findParent():查找包含指定数据的节点的父节点。
 *   recursePreIterate():递归先序遍历二叉树。
 *   recurseInIterate():递归中序遍历二叉树。
 *   recursePostIterate():递归后序遍历二叉树。
 *   circlePreIterate():循环先序遍历二叉树。
 *   circleInIterate():循环中序遍历二叉树。
 *   circlePostIterate():循环后序遍历二叉树。
 *   layerIterate():层次遍历二叉树。
 */
public class BinaryTree<Type> {
    /**
     * root:二叉树的根节点
     */
    private BTNode root;//根节点

    /**
     * 两种构造方法
     */
    public BinaryTree(){//空构造方法
        root = null;
    }

    public BinaryTree(Type data){//使用一个数据的构造方法
        root = new BTNode(data);
    }

    /**
     * root 的 get() 方法和 set() 方法
     */
    public BTNode getRoot() {
        return root;
    }

    public void setRoot(BTNode root) {
        this.root = root;
    }

    /**
     * 判断树是否为空
     * @return 树是否为空
     */
    public boolean isEmpty(){
        if ((root == null))//根节点为空
            return true;
        return false;
        //简单的实现: return (root == null);
    }

    /**
     * 清空二叉树
     */
    public void clear(){
        root = null;
    }

    /**
     * 向二叉树中添加数据
     * @param data 需要添加的数据
     */
    public void add(Type data,BTNode root){
        if (root.getData() == null)//当前二叉树还是空树
            this.root = new BTNode(data);//将添加的数据放入跟节点
        else
            add(root,new BTNode(data));//将添加的数据放入非空二叉树中
    }

    /**
     * 向非空二叉树中添加数据的具体实现
     * @param currentRoot 当前节点
     * @param addNode 需要添加的节点
     */
    private boolean add(BTNode currentRoot, BTNode addNode){
        if (currentRoot.getLeft() == null) {//当前节点的左孩子为空
            currentRoot.setLeft(addNode);//将新节点设为该节点的左孩子
            return true;
        }
        else if (currentRoot.getRight() == null){//当前节点的右孩子为空
            currentRoot.setRight(addNode);//将新节点设为该节点的右孩子
            return true;
        }
        return false;//两个节点都不为空,则不能添加
    }

    /**
     * 获取二叉树的高度
     * @return 二叉树的高度
     */
    public int height(){
        return height(root);
    }

    /**
     * 获取以根节点为 root 的二叉树的高度
     * @param root 当前根节点
     * @return 高度
     */
    private int height(BTNode root){
        if (root ==null)
            return 0;
        int depthLeft = height(root.getLeft());//递归计算左子树高度
        int depthRight = height(root.getRight());//递归计算右子树高度
        if (depthLeft >= depthRight)//左子树高度不比右子树低
            return depthLeft+1;//返回左子树高度加 1,因为需要把根节点也算进去
        else //左子树比右子树低
            return depthRight+1;//返回右子树高度加 1,需要把根节点算进去
    }

    /**
     * 计算二叉树包含的节点数
     * @return 二叉树的节点数
     */
    public int size(){
        return size(root);
    }

    /**
     * 计算根节点为 root 的二叉树的大小
     * @param root 根节点
     * @return 根节点为 root 的二叉树的大小
     */
    private int size(BTNode root){
        if (root == null)
            return 0;
        return 1 + size(root.getLeft()) + size(root.getRight());
    }

    /**
     * 查找包含指定数据的节点
     * @param data 需要查找的数据
     * @return 包含指定数据的节点
     */
    public BTNode findNode(Type data){
        return findNode(root,data);
    }

    /**
     * 查找包含指定数据的节点的详细实现。
     * @param root 当前节点
     * @param data 需要查找的数据
     * @return 包含查找数据的节点
     */
    private BTNode findNode(BTNode root, Type data){
        if (root.getData().equals(data))//当前节点的数据即为查找数据
            return root;
        else if (root.getLeft() != null)
            return findNode(root.getLeft(),data);//递归遍历左子树
        else if (root.getRight() != null)
            return findNode(root.getRight(), data);//递归遍历右子树
        return null;//没找到,返回 null
    }

    /**
     * 查找指定数据所在节点的父节点
     * @param data 指定的数据
     * @return 父节点
     */
    public BTNode findParent(Type data){
        BTNode child = new BTNode(data);
        return findParent(root, child);
    }

    /**
     * 查找父节点的详细实现
     * @param parent 父节点
     * @param child 指定的数据
     * @return 父节点
     */
    private BTNode findParent(BTNode parent, BTNode child){
        if ((parent == null) || (parent.getLeft() == null) || (parent.getRight() == null))//父节点为空,则返回空
            return null;
        if((parent.getLeft().getData() == child.getData()) ||///父节点的左孩子的数据即为指定数据
                (parent.getRight().getData() == child.getData()))//父节点的右孩子的数据即为指定数据
            return parent;//返回父节点
        BTNode p;
        if ((p=findParent(parent.getLeft(),child)) != null)//递归查找左子树
            return p;
        return findParent(parent.getRight(),child);//递归查找右子树
    }

    /**
     * 先序遍历
     */
    public void preIterate(){
        System.out.print("先序递归遍历:");
        recursePreIterate(root);
        System.out.println();

        System.out.print("先序循环遍历:");
        circlePreIterate(root);
        System.out.println();
    }

    /**
     * 递归先序遍历指定节点的树
     * @param root 树的根节点
     */
    public void recursePreIterate(BTNode root){
        if (root == null)
            return;
        System.out.print(" " + root.getData());
        recursePreIterate(root.getLeft());
        recursePreIterate(root.getRight());
    }

    /**
     * 先序循环遍历指定根节点的树
     * 需要使用栈来实现
     *
     * 这个过程可能难理解,最好的理解方式就是画图
     * 自己画一棵树,然后根据程序的每一句去走一遍,就可以很好地理解程序了。
     *
     * @param root 指定的根节点
     */
    public void circlePreIterate(BTNode root){
        Stack<BTNode<Type>> s = new Stack<>();
        BTNode<Type> current = root;//用于保存当前节点
        while ((current != null) || !s.isEmpty()){
            while (current != null){//当前节点不为空
                System.out.print(" " + current.getData());//将当前数据打印
                s.push(current);//再将当前节点入栈
                current = current.getLeft();//将current节点的左孩子设置为当前节点
            }
            //现在已经到了树的最左下角的节点
            if (!s.isEmpty()){
                current = s.pop();//栈顶元素出栈
                current = current.getRight();//将栈顶元素设置为栈顶元素的右孩子
            }
        }
    }

    /**
     * 中序遍历
     */
    public void inIterator(){
        System.out.print("中序递归遍历:");
        recurseInIterate(root);
        System.out.println();

        System.out.print("中序循环遍历:");
        circleInIterate(root);
        System.out.println();
    }

    /**
     * 递归中序遍历根节点为 root 的树
     * @param root 根节点
     */
    public void recurseInIterate(BTNode root){
        if(root == null)
            return;
        recurseInIterate(root.getLeft());//迭代遍历左子树
        System.out.print(" " + root.getData());//输出数据
        recurseInIterate(root.getRight());//迭代遍历右子树
    }

    /**
     * 循环中序遍历根节点为 root 的树
     * 需要使用栈来实现
     *
     * @param root 根节点
     */
    public void circleInIterate(BTNode root){

        BTNode<Type> current = root;//当前节点
        Stack<BTNode<Type>> s = new Stack<>();

        while ((current != null) || (!s.isEmpty())){
            while (current != null){//当前节点不为空
                s.push(current);//将当前节点入栈
                current = current.getLeft();//设置当前节点为current节点的左孩子
            }
            if (!s.isEmpty()){
                current = s.pop();//将栈顶元素出栈
                System.out.print(" " + current.getData());//打印出栈节点的数据
                current = current.getRight();//设置当前节点为current节点的右孩子
            }
        }
    }

    /**
     * 后序遍历
     */
    public void postIterate(){
        System.out.print("后序递归遍历:");
        recursePostIterate(root);
        System.out.println();

        System.out.print("后序循环遍历:");
        circlePostIterate(root);
        System.out.println();
    }

    /**
     * 后序递归遍历根节点为 root 的树
     * @param root 根节点
     */
    public void recursePostIterate(BTNode root){
        if (root == null)
            return;
        recursePostIterate(root.getLeft());
        recursePostIterate(root.getRight());
        System.out.print(" " + root.getData());
    }

    /**
     * 后序循环遍历根节点为 root 的树
     * 需要使用两个栈来实现
     * 这个过程很难理解,建议自己画棵树,根据代码,自己在树上画一遍代码的执行过程
     *
     * @param root 根节点
     */
    public void circlePostIterate(BTNode root){
        int cannot = 0;//用作标记,表示当前节点不能出栈
        int can = 1;//用作标记,表示当前节点可以出栈
        Stack<BTNode<Type>> sNode = new Stack<>();//树的节点栈
        Stack<Integer> sInt = new Stack<>();//用于判断当前节点是其父节点的左孩子还是右孩子。
        BTNode current = root;

        while ((current != null) || (!sNode.isEmpty())){//当前节点不为空或节点栈不为空
            //先将节点沿树最左枝入栈
            while (current != null){//当前节点不为空
                sNode.push(current);//将节点入栈
                sInt.push(cannot);//标记当前节点不能出栈
                current = current.getLeft();//将当前节点设置为current节点的左孩子
            }
            while ((!sNode.isEmpty())&&(sInt.top() == can)){
                //节点栈不为空,且当前节点可以出栈
                sInt.pop();//将当前节点的标记出栈
                System.out.print(" " + sNode.pop().getData());//将当前节点出栈
            }
            if ((!sNode.isEmpty()) && (sInt.top() == cannot)){
                //节点栈不为空,且栈顶节点不能出栈
                sInt.pop();//将栈顶节点的标记出栈
                sInt.push(can);//将栈顶节点的标记入栈,使当前节点可以出栈
                current = sNode.top().getRight();//将当前节点设置为栈顶节点的右孩子
            }
        }
        /**
         * 为什么需要标记一个节点是否可以出栈?
         * 答:一个节点 node 入栈时,在其后续入栈的是它的左孩子。
         *     当 node 变成栈顶元素时,还需要将其右孩子入栈,而 node 节点此时不能出栈。
         *     但 node 节点第二次变成栈顶元素时,就可以出栈了。
         *     因此需要标记一个节点是否可以出栈,从而使得节点可以在第一次变成栈顶元素时不会出栈。
         *     而在第二次变成栈顶元素时让其出栈。
         */
    }

    /**
     * 树的层次遍历,需要使用队列来实现。
     *
     * 过程的理解:首先,将根节点入队,队列不为空时,就处理队列里每个节点。
     * 然后,将队首节点出队,处理该节点;如果该节点有左右孩子,则将左右孩子都入队。
     *
     * 为什么这样就可以实现呢?
     * 答:我们从根节点开始说。根节点入队后,队列不为空了,就可以处理队列里的节点。
     *   第一次出队的是根节点,处理完根节点就会把根节点的左右孩子(如果都有)都入队。
     *   这时队列里就有了树的第二层节点,而且是从左往右依次排列的。
     *   第二次出队的是根节点的左孩子,处理完以后就会把这个节点的左右孩子(如果有)都入队。
     *   这时,队列里有两类节点:树的第二层节点(根节点的右孩子),树的第三层节点(根节点左孩子的左右孩子)。
     *   第三次出队的是根节点的右孩子,处理完该节点后就会把该节点的左右孩子(如果有)都入队。
     *   这时,队列里只有树的第三层节点,而且是从左往右依次排列。
     *   循环这个过程,直到树的最后一层,即完成了树的层次遍历。
     */
    public void layerIterate(BTNode root){

        ArrayQueue<BTNode<Type>> queue = new ArrayQueue<>();//用于保存一个节点的左右孩子
        BTNode<Type> current = root;//设置当前节点为根节点

        System.out.print("**层次遍历**:");

        if (current != null)
            queue.enQueue(current);//将根节点入队
        while (!queue.isEmpty()){
            current = queue.deQueue();//设置当前节点为队首的节点

            System.out.print(" " + current.getData());//处理当前节点

            if (current.getLeft() != null)//当前节点有左孩子
                queue.enQueue(current.getLeft());//将当前节点的左孩子入队
            if (current.getRight() != null)//当前节点有右孩子
                queue.enQueue(current.getRight());//将当前节点的右孩子入队
        }
        System.out.println();
    }
}

(复制即可用,但记得去  栈(Stack)的Java实现  copy我的栈实现代码)

下面是二叉树实现的测试代码(BinaryTreeTest):

public class BinaryTreeTest {

    private BinaryTree<Integer> tree;
    private int data;

    public static void main(String[] args) {
        BinaryTreeTest b = new BinaryTreeTest();
        b.run();
    }

    public void run(){
        tree = new BinaryTree();
        testAdd();
        testFindAndFindParent();
        testSize();
        testHeight();
        testClear();
    }

    public void testAdd(){
        System.out.println("------测试方法 add()------");
        BTNode root = new BTNode(0);
        tree.setRoot(root);
        tree.add(1*MULTI,tree.getRoot());
        tree.add(2*MULTI,tree.getRoot());
        tree.add(3*MULTI,tree.getRoot().getLeft());
        tree.add(4*MULTI,tree.getRoot().getLeft());
        tree.add(5*MULTI,tree.getRoot().getRight());
        tree.add(6*MULTI,tree.getRoot().getRight());
//        tree.getRoot().setLeft(new BTNode(1*MULTI));
//        tree.getRoot().setRight(new BTNode(2*MULTI));
//        tree.getRoot().getLeft().setLeft(new BTNode(3*MULTI));
//        tree.getRoot().getLeft().setRight(new BTNode(4*MULTI));
//        tree.getRoot().getRight().setLeft(new BTNode(5*MULTI));
//        tree.getRoot().getRight().setRight(new BTNode(6*MULTI));
        tree.preIterate();
        tree.inIterator();
        tree.postIterate();
    }

    public void testFindAndFindParent(){
        System.out.println("------测试方法 findNode()------");
        data = 5;
        BTNode parent = tree.findParent(data);
        BTNode node = tree.findNode(data);
        if (node == null)
            System.out.println("数据:" + data + " 不存在");
        else {
            System.out.println("数据:" + data);
            if (node.getLeft() != null)
                System.out.println("左孩子数据:" + node.getLeft().getData());
            if (node.getRight() != null)
                System.out.println("右孩子数据:" + node.getRight().getData());
            if (parent != null)
                System.out.println("父节点数据:" + parent.getData());
        }

        data = 35;
        parent = tree.findParent(data);
        node = tree.findNode(data);
        if (node == null)
            System.out.println("数据:" + data + " 不存在");
        else {
            System.out.println("数据:" + data);
            if (node.getLeft() != null)
                System.out.println("左孩子数据:" + node.getLeft().getData());
            if (node.getRight() != null)
                System.out.println("右孩子数据:" + node.getRight().getData());
            if (parent != null)
                System.out.println("父节点数据:" + parent.getData());
        }
    }

    public void testSize(){
        System.out.println("------测试方法 size()------");
        System.out.println("树的大小为:" + tree.size());
    }

    public void testHeight(){
        System.out.println("------测试方法 height()------");
        System.out.println("树的高度为:" + tree.height());
    }

    public void testClear(){
        System.out.println("------测试方法 clear()------");
        tree.clear();
        tree.preIterate();
        tree.inIterator();
        tree.postIterate();
    }
}

(复制即可用)

代码不够规范,望见谅~/bq 

猜你喜欢

转载自blog.csdn.net/weixin_42034276/article/details/86772522