4. 5.树

4.1. 5.1.基础知识

4.1.1. 5.1.1.树的概念

树状图是一种数据结构,它是由n(n>=1)个有限结点组成一个具有层次关系的集合。把它叫做“树”是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。它具有以下的特点: 每个结点有零个或多个子结点;没有父结点的结点称为根结点;每一个非根结点有且只有一个父结点;除了根结点外,每个子结点可以分为多个不相交的子树。

4.1.1.1. 5.1.1.1.层次结构

家族树示例:王二麻子的孩子和孙子们

_images/王二麻子的孩子和孙子们1.jpg

家族树示例:王小草的父母和祖父母

_images/王小草的父母和祖父母1.jpg

示例:大学组织结构图

_images/大学组织结构图1.jpg

4.1.1.2. 5.1.1.2.树的术语

前面的每个图都是树的示例。树(tree)是一组由边(edge)相连的节点(node),边表示节点间的关系。节点按层(level)组织,层表示节点的层次。最上层的单节点称为根(root)。

树中每个后继层中的节点是前一层中节点的孩子(children)。有孩子的节点称为其孩子的父节点(parent)。如下图所示,节点A是节点B、C、D和E的父节点。因为这些孩子有相同的父节点,所以它们称为兄弟(sibling)。它们也称为节点A的后代(descendant),而节点A是它们的祖先(ancestor)。节点P是节点A的后代,而A是P的祖先。注意,节点P没有孩子。这样的节点称为叶子(leaf)。

不是叶节点的节点–即有孩子的节点–称为内部节点(interior)或非叶节点(nonleaf)。这样的节点也是父节点。

_images/带层树1.jpg
  • 根节点或树根(root)
    树定点的节点称之为根节点,也叫树根
  • 节点(Node)
    树中的每个元素都称为节点
  • 子树(SubTree)
    除根节点外,其他节点也可以分为多个树的集合,叫做子树,上图中F节点和N、O、P节点共同构成一颗子树,节点B的子树。
  • 节点的度
    一个节点之间含有的子树的个数,称之为节点的度。上图中D节点的度为3,E节点的度为2,Q、R、S节点的度为0
  • 叶子节点、叶节点、终端节点
    度为0的节点叫做叶子节点,也叫叶节点、终端节点。其实就是没有子节点的节点,或者说没有子树的节点。
  • 父节点、双亲节点
  • 兄弟节点
  • 树的度
    一棵树中最大节点的度称之为树的度,即树中哪个节点的子节点最多,那么这个节点的度也就是树的度。
  • 节点的层次
    从根这一层开始,根算1层,根的子节点算2层,一直到最下面一层
  • 树的高度、深度
    树的深度是从根节点开始、自顶向下逐层累加(根节点的高度是1)助记:深度从上到下 树的高度是从叶节点开始、自底向上逐层累加(叶节点的高度是1)助记:高度由下向上 虽然树的高度和深度一样,但是具体到某个节点上,其高度和深度通常是不一样的。
  • 堂兄弟节点
  • 节点的祖先
  • 节点的子孙
  • 森林
    由m棵不相交的树组成的集合,叫做森林
4.1.1.2.1. 5.1.1.2.1.二叉树

二叉树中的每个节点最多有两个孩子。它们称为左孩子(left child)和右孩子(right child)

_images/5.5三颗二叉树1.jpg

如图所示:节点B、D、F都是左孩子,二节点C、E、G都是右孩子。该二叉树的根有两颗子树。左子树(left subtree)的根是B,而右子树(right subtree)的根是C。 二叉树中的每颗子树还是二叉树。

4.1.1.2.2. 5.1.1.2.2.满二叉树和完全二叉树

满二叉树:高度为h的二叉树中,若其所有的叶节点都在h层上且每个非叶节点都有两个孩子,则树称为满的。如图a) 既为一颗满二叉树。

完全二叉树:如果二叉树中除最后一层外的所有层都含有最多的节点,最后一层的节点从左至右填充,则树是完全的。如图b)即为一颗完全二叉树

注:满二叉树中的所有叶节点都在同一层中,且每个非叶节点都有两个孩子。完全二叉树中,到倒数第二层都是满的,且最后一层的叶节点从左至右填充。

4.1.1.2.3. 5.1.1.2.3.平衡二叉树

若二叉树中每个节点有两颗高度完全相等的子树,则树称为完全平衡树(Completely balanced)。

_images/5.6一些高度平衡的二叉树1.jpg

如图所示的满树是完全平衡树。如果树中的每个节点的子树的高度差不大于1,则树称为高度平衡的,或简称为平衡的。

满树或完全树的高度。

4.1.1.3. 5.1.1.3.二叉树的遍历

二叉树的遍历分为前序、中序、后序遍历(相对于根来讲)。

前序遍历:根–>左–>右

中序遍历:左–>根–>右

后序遍历:左–>右–>根

层序遍历

4.1.2. 5.1.2.二叉树中的节点

_images/5.7二叉树实现1.jpg

4.1.3. 5.1.3.二叉树实现

/**
 * All rights Reserved, Designed By https://www.tulingxueyuan.com/
 * @Title:  BinaryTree.java
 * @Package com.tuling.tree
 * @Description:
 * @author: 小白
 * @Date 2019年12月6日 下午10:24:12
 * @version V1.0
 * @Copyright:
 */
package com.tuling.tree;

/**
 * @ClassName BinaryTree
 * @Description
 * @Author 北京图灵学院
 * @Date 2019年12月6日 下午10:24:12
 */
public class BinaryTree {

    private TreeNode root = null;

    public BinaryTree() {
        root = new TreeNode(1, "A");
        //自己手动创建一个二叉树
        createBinaryTree(root);
    }

    /**
     *
     * @Title: createBinaryTree
     * @Description:手动创建一个二叉树
     * @param: root
     * @return: void
     * @throws
     */
    public void createBinaryTree(TreeNode root) {
        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");

        root.leftChild = nodeB;
        root.rightChild = nodeC;
        root.leftChild.leftChild = nodeD;
        root.leftChild.rightChild = nodeE;
        root.rightChild.rightChild = nodeF;

    }



    public TreeNode getRoot() {
        return root;
    }

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


    /**
     *
     * @ClassName TreeNode
     * @Description:二叉树的节点数据结构
     * @Author 北京图灵学院
     * @Date 2019年12月6日 下午10:24:35
     */
    private class TreeNode{
        private int key;
        private String data;
        private boolean isVisted;
        private TreeNode leftChild;
        private TreeNode rightChild;
        /**
         * @Title:  TreeNode
         * @Description:
         * @param:
         * @throws
         */
        public TreeNode() {
        }

        /**
         * @Title:  TreeNode
         * @Description:
         * @param: key 层序编码
         * @param: data  数据域
         * @throws
         */
        public TreeNode(int key, String data) {
            super();
            this.key = key;
            this.data = data;
            this.leftChild = null;
            this.rightChild = null;
        }

        /**
        *
        */
        @Override
        public String toString() {
            return "TreeNode [key=" + key + ", data=" + data + "]";
        }
    }
}

4.1.4. 5.1.4.二叉树前序遍历代码实现

在BinaryTree中增加如下方法:

/**
 *
 * @Title: preOrder
 * @Description:前序遍历:-->-->
 * @param:
 * @return: void
 * @throws
 */
public void preOrder() {

    if(root != null) {
        root.preOrder();
    }else {
        System.out.println("树为空!");
    }
}

在内部类 TreeNode 中增加如下方法:

/**
 * @Title: preOrder
 * @Description:前序遍历:根-->左-->右
 * @param:
 * @return: void
 * @throws
 */
public void preOrder() {
    //先打印自己,也就是根
    System.out.println(this);
    //递归遍历左子树
    if(this.leftChild!=null) {
        this.leftChild.preOrder();
    }
    //递归遍历右子树
    if(this.rightChild!=null) {
        this.rightChild.preOrder();
    }
}

运行效果:

_images/result11.png

4.1.5. 5.1.5.二叉树中序遍历代码实现

在BinaryTree中增加如下方法:

/**
     *
     * @Title: inFixOrder
     * @Description:中序遍历:左-->根-->右
     * @param:
     * @return: void
     * @throws
     */
    public void inFixOrder() {
        if(root != null) {
            root.inFixOrder();
        }else {
            System.out.println("树为空!");
        }
    }

在内部类TreeNode中增加如下方法:

/**
 * @Title: inFixOrder
 * @Description:中序遍历:左-->根-->右
 * @param:
 * @return: void
 * @throws
 */
public void inFixOrder() {
    //递归遍历左子树
    if(this.leftChild!=null) {
        this.leftChild.inFixOrder();
    }
    //打印自己
    System.out.println(this);
    //递归遍历右子树
    if(this.rightChild!=null) {
        this.rightChild.inFixOrder();
    }
}

测试效果:

_images/result21.png

4.1.6. 5.1.6.二叉树后序遍历代码实现

在BinaryTree中增加如下方法:
/**
     *
     * @Title: postOrder
     * @Description:后序遍历:左-->右-->根
     * @param:
     * @return: void
     * @throws
     */
    public void postOrder() {
        if(root!=null) {
            root.postOrder();
        }else {
            System.out.println("树为空!");
        }
    }

在内部类TreeNode中增加如下方法:

/**
 * @Title: postOrder
 * @Description:后序遍历:左-->右-->根
 * @param:
 * @return: void
 * @throws
 */
public void postOrder() {
    //递归遍历左子树
    if(this.leftChild!=null) {
        this.leftChild.postOrder();
    }
    //递归遍历右子树
    if(this.rightChild!=null) {
        this.rightChild.postOrder();
    }
    //打印自己
    System.out.println(this);
}

测试效果:

_images/result31.png

类BinaryTree完整代码如下:

/**
 * All rights Reserved, Designed By https://www.tulingxueyuan.com/
 * @Title:  BinaryTree.java
 * @Package com.tuling.tree
 * @Description:
 * @author: 小白
 * @Date 2019年12月6日 下午10:24:12
 * @version V1.0
 * @Copyright:
 */
package com.tuling.tree;

/**
 * @ClassName BinaryTree
 * @Description
 * @Author 北京图灵学院
 * @Date 2019年12月6日 下午10:24:12
 */
public class BinaryTree {

    private TreeNode root = null;

    public BinaryTree() {
        root = new TreeNode(1, "A");
        //自己手动创建一个二叉树
        createBinaryTree(root);
    }

    /**
     *
     * @Title: createBinaryTree
     * @Description:手动创建一个二叉树
     * @param: root
     * @return: void
     * @throws
     */
    public void createBinaryTree(TreeNode root) {
        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");

        root.leftChild = nodeB;
        root.rightChild = nodeC;
        root.leftChild.leftChild = nodeD;
        root.leftChild.rightChild = nodeE;
        root.rightChild.rightChild = nodeF;

    }

    /**
     *
     * @Title: preOrder
     * @Description:前序遍历:根-->左-->右
     * @param:
     * @return: void
     * @throws
     */
    public void preOrder() {

        if(root != null) {
            root.preOrder();
        }else {
            System.out.println("树为空!");
        }
    }

    /**
     *
     * @Title: inFixOrder
     * @Description:中序遍历:左-->根-->右
     * @param:
     * @return: void
     * @throws
     */
    public void inFixOrder() {
        if(root != null) {
            root.inFixOrder();
        }else {
            System.out.println("树为空!");
        }
    }

    /**
     *
     * @Title: postOrder
     * @Description:后序遍历:左-->右-->根
     * @param:
     * @return: void
     * @throws
     */
    public void postOrder() {
        if(root!=null) {
            root.postOrder();
        }else {
            System.out.println("树为空!");
        }
    }

    public TreeNode getRoot() {
        return root;
    }

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





    /**
     *
     * @ClassName TreeNode
     * @Description:二叉树的节点数据结构
     * @Author 北京图灵学院
     * @Date 2019年12月6日 下午10:24:35
     */
    private class TreeNode{
        private int key;
        private String data;
        private boolean isVisted;
        private TreeNode leftChild;
        private TreeNode rightChild;
        /**
         * @Title:  TreeNode
         * @Description:
         * @param:
         * @throws
         */
        public TreeNode() {
        }

        /**
         * @Title: postOrder
         * @Description:后序遍历:左-->右-->根
         * @param:
         * @return: void
         * @throws
         */
        public void postOrder() {
            //递归遍历左子树
            if(this.leftChild!=null) {
                this.leftChild.postOrder();
            }
            //递归遍历右子树
            if(this.rightChild!=null) {
                this.rightChild.postOrder();
            }
            //打印自己
            System.out.println(this);
        }

        /**
         * @Title: inFixOrder
         * @Description:中序遍历:左-->根-->右
         * @param:
         * @return: void
         * @throws
         */
        public void inFixOrder() {
            //递归遍历左子树
            if(this.leftChild!=null) {
                this.leftChild.inFixOrder();
            }
            //打印自己
            System.out.println(this);
            //递归遍历右子树
            if(this.rightChild!=null) {
                this.rightChild.inFixOrder();
            }
        }

        /**
         * @Title: preOrder
         * @Description:前序遍历:根-->左-->右
         * @param:
         * @return: void
         * @throws
         */
        public void preOrder() {
            //先打印自己,也就是根
            System.out.println(this);
            //递归遍历左子树
            if(this.leftChild!=null) {
                this.leftChild.preOrder();
            }
            //递归遍历右子树
            if(this.rightChild!=null) {
                this.rightChild.preOrder();
            }
        }

        /**
         * @Title:  TreeNode
         * @Description:
         * @param: key 层序编码
         * @param: data  数据域
         * @throws
         */
        public TreeNode(int key, String data) {
            super();
            this.key = key;
            this.data = data;
            this.leftChild = null;
            this.rightChild = null;
        }

        /**
        *
        */
        @Override
        public String toString() {
            return "TreeNode [key=" + key + ", data=" + data + "]";
        }
    }
}

4.2. 5.2.二叉查找树的实现

4.2.1. 5.2.1.入门知识

二叉查找树(Binary Search Tree),(又:二叉搜索树,二叉排序树)它或者是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉排序树。

4.2.2. 5.2.2.添加

4.2.2.1. 5.2.2.1.递归实现

在BinarySearchTree.java 中添加如下代码:

/**
 *
 * @Title: add
 * @Description:二叉查找树添加的方法
 * @param: data
 * @return: void
 * @throws
 */
public void add(Comparable data) {
    if(root == null) {
        root = new Node(data);
    }else {
        root.add(data);
    }
}

在内部类 Node.java 中添加如下代码:

/**
 * @Title: add
 * @Description:添加方法
 *  实现:调用 Comparable 接口的 compareTo 方法,比较对象大小
 *      1、如果当前对象小于指定对象,返回负整数
 *      2、如果当前对象等于指定对象,返回0
 *      3、如果当前对象大于指定对象,返回正整数
 *
 *     如果返回正整数,进左子树
 *     如果返回负整数或0,进右子树
 * @param: data
 * @return: void
 * @throws
 */
public void add(Comparable data) {
    if(this.data.compareTo(data) > 0) {
        if(this.left==null) {
            this.left = new Node(data);
        }else {
            this.left.add(data);
        }
    }else if(this.data.compareTo(data) <= 0) {
        if(this.right == null) {
            this.right = new Node(data);
        }else {
            this.right.add(data);
        }
    }
}

4.2.3. 5.2.3.遍历

在BinarySearchTree.java类中添加如下代码:

/**
 *
 * @Title: inFixOrder
 * @Description:中序遍历
 * @param:
 * @return: void
 * @throwss
 */
public void inFixOrder() {
    if(root == null) {
        System.out.println("树为空!");
    }else {
        root.inFixOrder();
    }
}

在内部类Node.java中添加如下代码:

/**
 * @Title: inFixOrder
 * @Description:中序遍历
 * @param:
 * @return: void
 * @throws
 */
public void inFixOrder() {
    //先遍历左子树
    if(this.left!=null) {
        this.left.inFixOrder();
    }
    //打印数据
    System.out.println(this);
    //遍历右子树
    if(this.right!=null) {
        this.right.inFixOrder();
    }
}

测试代码:

/**
 * All rights Reserved, Designed By https://www.tulingxueyuan.com/
 * @Title:  BinarySearchTreeDemo.java
 * @Package com.tuling.tree
 * @Description:
 * @author: 小白
 * @Date 2019年12月7日 上午1:18:33
 * @version V1.0
 * @Copyright:
 */
package com.tuling.tree;

/**
 * @ClassName BinarySearchTreeDemo
 * @Description
 * @Author 北京图灵学院
 * @Date 2019年12月7日 上午1:18:33
 */
public class BinarySearchTreeDemo {

    /**
     * @Title: main
     * @Description:
     * @param: @param args
     * @return: void
     * @throws
     */
    public static void main(String[] args) {
        BinarySearchTree bst = new BinarySearchTree();

        bst.add(25);
        bst.add(43);
        bst.add(2);
        bst.add(765);
        bst.add(23);
        bst.add(1);
        bst.add(76);

        bst.inFixOrder();

    }

}

运行效果:

_images/result41.png

4.2.4. 5.2.4.查找和获取

在BinarySearchTree.java类中添加如下代码

/**
 *
 * @Title: bstSearch
 * @Description:查找
 * @param: data
 * @param:
 * @return: Node
 * @throws
 */
public Node bstSearch(Comparable data) {
    if(root.data.compareTo(data)==0) {
        return root;
    }else {
        return root.bstSearch(data);
    }
}

在内部类Node.java中添加如下代码:

/**
 * @Title: bstSearch
 * @Description:查找
 * @param: data
 * @param:
 * @return: Node
 * @throws
 */
public Node bstSearch(Comparable data) {
    if(this.data.compareTo(data) == 0) {
        return this;
    }else if(this.data.compareTo(data)>0) {//如果要查找的对象小于当前对象,向左子树查找。
        if(this.left == null) {//如果左子树为空。
            return null;
        }
        //递归向左子树查找。
        return this.left.bstSearch(data);
    }else if(this.data.compareTo(data) <= 0) {//如果要查找的对象大于或等于当前对象,向右子树查找。
        if(this.right == null) {
            return null;
        }
        //递归向右子树查找
        return this.right.bstSearch(data);
    }
    return null;
}

4.2.5. 5.2.5.删除

_images/5.8二叉搜索树的删除1.jpg

删除功能代码如下:

/**
 *
 * @Title: del
 * @Description:删除
 * @param: @param data
 * @param: @return
 * @return: boolean
 * @throws
 */
public void del(Comparable data) {
    if(root == null) {
        return;
    }

    //先找到需要删除的节点
    Node delNode = bstSearch(data);
    //如果没有找到要删除的节点
    if(delNode==null) {
        return;
    }

    //找到要删除的节点的父节点
    Node parentNode = searchParent(delNode);

    //如果要删除的节点是叶节点
    if(delNode.left==null && delNode.right==null) {
        //判断 delNode 是父节点的左子树还是右子树
        if(parentNode.left!=null && parentNode.left.data.equals(delNode.data)) {
            parentNode.left = null;
        }else if(parentNode.right!=null && parentNode.right.data.equals(delNode.data)) {
            parentNode.right = null;
        }
    }else if(delNode.left!=null && delNode.right!=null) {//要删除的节点有两个子节点
        //获取后继节点的数据
        Comparable minNodeData = getRightTreeMin(delNode.right);
        //转移后继节点的值到当前要删除的节点
        delNode.data = minNodeData;
    }else {//删除只有一个子树的节点

        /*
         * 要删除的节点有左子树
         */
        if(delNode.left!=null) {
            if(parentNode!=null) {//要删除的节点为父节点的左子树
                if(parentNode.left!=null && parentNode.left.data.compareTo(delNode.data) == 0) {
                    parentNode.left = delNode.left;
                }else {
                    //要删除的节点为父节点的右子树
                    parentNode.right = delNode.left;
                }
            }else {
                //删除的是root
                root = delNode.left;
            }
        }else {//要删除的节点有右子树
            if(parentNode!=null) {//要删除的节点为父节点的左子树
                if(parentNode.left!=null && parentNode.left.data.compareTo(delNode.data) == 0) {
                    parentNode.left = delNode.right;
                }else {
                    //要删除的节点为父节点的右子树
                    parentNode.right = delNode.right;
                }
            }else {
                //删除的是root
                root = delNode.right;
            }
        }
    }
}

查找父节点功能代码如下:

/**
 * @Title: searchParent
 * @Description:查找要删除的节点的父节点
 * @param: @param delNode
 * @param: @return
 * @return: Node
 * @throws
 */
private Node searchParent(Node delNode) {
    if(root == null) {
        return null;
    }else {
        return this.root.searchParent(delNode);
    }
}

内部类Node.java中的代码如下:

/**
 * @Title: searchParent
 * @Description:递归查找要删除的节点的父节点
 * @param: @param delNode
 * @param: @return
 * @return: Node
 * @throws
 */
public Node searchParent(Node delNode) {
    //如果当前节点就是要删除节点的父节点
    if(this.left!=null && this.left.data.compareTo(delNode.data)==0 ||
            this.right!=null && this.right.data.compareTo(delNode.data) == 0) {
        return this;
    }else {//当前节点不是要删除节点的父节点,需要递归向下遍历查找。
        //如果当前对象的左子树不为空,并且要查找的对象小于当前对象,递归向左子树查找。
        if(this.left!=null && this.data.compareTo(delNode.data) > 0) {
            return this.left.searchParent(delNode);
        }else if(this.right!=null && this.data.compareTo(delNode.data) <= 0) {
            return this.right.searchParent(delNode);
        }else {
            //没有找到父节点
            return null;
        }
    }
}

二叉查找树BinarySearchTree.java类完整代码如下:

/**
 * All rights Reserved, Designed By https://www.tulingxueyuan.com/
 * @Title:  BinarySearchTree.java
 * @Package com.tuling.tree
 * @Description:
 * @author: 小白
 * @Date 2019年12月7日 上午12:46:43
 * @version V1.0
 * @Copyright:
 */
package com.tuling.tree;

/**
 * @ClassName BinarySearchTree
 * @Description
 * @Author 北京图灵学院
 * @Date 2019年12月7日 上午12:46:43
 */
public class BinarySearchTree {

    private Node root;

    /**
     *
     * @Title: add
     * @Description:二叉查找树添加的方法
     * @param: data
     * @return: void
     * @throws
     */
    public void add(Comparable data) {
        if(root == null) {
            root = new Node(data);
        }else {
            root.add(data);
        }
    }

    /**
     *
     * @Title: inFixOrder
     * @Description:中序遍历
     * @param:
     * @return: void
     * @throws
     */
    public void inFixOrder() {
        if(root == null) {
            System.out.println("树为空!");
        }else {
            root.inFixOrder();
        }
    }

    /**
     *
     * @Title: bstSearch
     * @Description:查找
     * @param: data
     * @param:
     * @return: Node
     * @throws
     */
    public Node bstSearch(Comparable data) {
        if(root == null) {
            return null;
        }else {
            return this.root.bstSearch(data);
        }

    }

    /**
     * @Title: searchParent
     * @Description:查找要删除的节点的父节点
     * @param: @param delNode
     * @param: @return
     * @return: Node
     * @throws
     */
    private Node searchParent(Node delNode) {
        if(root == null) {
            return null;
        }else {
            return this.root.searchParent(delNode);
        }
    }

    /**
     *
     * @Title: del
     * @Description:删除
     * @param: @param data
     * @param: @return
     * @return: boolean
     * @throws
     */
    public void del(Comparable data) {
        if(root == null) {
            return;
        }

        //先找到需要删除的节点
        Node delNode = bstSearch(data);
        //如果没有找到要删除的节点
        if(delNode==null) {
            return;
        }

        //找到要删除的节点的父节点
        Node parentNode = searchParent(delNode);

        //如果要删除的节点是叶节点
        if(delNode.left==null && delNode.right==null) {
            //判断 delNode 是父节点的左子树还是右子树
            if(parentNode.left!=null && parentNode.left.data.equals(delNode.data)) {
                parentNode.left = null;
            }else if(parentNode.right!=null && parentNode.right.data.equals(delNode.data)) {
                parentNode.right = null;
            }
        }else if(delNode.left!=null && delNode.right!=null) {//要删除的节点有两个子节点
            //获取后继节点的数据
            Comparable minNodeData = getRightTreeMin(delNode.right);
            //转移后继节点的值到当前要删除的节点
            delNode.data = minNodeData;
        }else {//删除只有一个子树的节点

            /*
             * 要删除的节点有左子树
             */
            if(delNode.left!=null) {
                if(parentNode!=null) {//要删除的节点为父节点的左子树
                    if(parentNode.left!=null && parentNode.left.data.compareTo(delNode.data) == 0) {
                        parentNode.left = delNode.left;
                    }else {
                        //要删除的节点为父节点的右子树
                        parentNode.right = delNode.left;
                    }
                }else {
                    //删除的是root
                    root = delNode.left;
                }
            }else {//要删除的节点有右子树
                if(parentNode!=null) {//要删除的节点为父节点的左子树
                    if(parentNode.left!=null && parentNode.left.data.compareTo(delNode.data) == 0) {
                        parentNode.left = delNode.right;
                    }else {
                        //要删除的节点为父节点的右子树
                        parentNode.right = delNode.right;
                    }
                }else {
                    //删除的是root
                    root = delNode.right;
                }
            }
        }
    }


    /**
     * @Title: getRightTreeMin
     * @Description:返回要删除的节点右子树的最小节点
     * @param: @param delNode
     * @param: @return
     * @return: Node
     * @throws
     */
    private Comparable getRightTreeMin(Node delNode) {

        Node tempNode = delNode;
        //循环向左查找即可
        while(tempNode.left!=null) {
            tempNode = tempNode.left;
        }
        //循环结束,tempNode指向要删除节点右子树的最小节点。
        //删除最小节点
        del(tempNode.data);
        return tempNode.data;
    }


    /**
     *
     * @ClassName Node
     * @Description:二叉树的节点数据结构
     * @Author 北京图灵学院
     * @Date 2019年12月7日 上午1:46:33
     */
    class Node{
        private Comparable data;
        private Node left;
        private Node right;
        /**
         * @Title:  Node
         * @Description:
         * @param:
         * @throws
         */
        public Node() {
            super();
        }


        /**
         * @Title: searchParent
         * @Description:递归查找要删除的节点的父节点
         * @param: @param delNode
         * @param: @return
         * @return: Node
         * @throws
         */
        public Node searchParent(Node delNode) {
            //如果当前节点就是要删除节点的父节点
            if(this.left!=null && this.left.data.compareTo(delNode.data)==0 ||
                    this.right!=null && this.right.data.compareTo(delNode.data) == 0) {
                return this;
            }else {//当前节点不是要删除节点的父节点,需要递归向下遍历查找。
                //如果当前对象的左子树不为空,并且要查找的对象小于当前对象,递归向左子树查找。
                if(this.left!=null && this.data.compareTo(delNode.data) > 0) {
                    return this.left.searchParent(delNode);
                }else if(this.right!=null && this.data.compareTo(delNode.data) <= 0) {
                    return this.right.searchParent(delNode);
                }else {
                    //没有找到父节点
                    return null;
                }
            }
        }


        /**
         * @Title: bstSearch
         * @Description:查找
         * @param: data
         * @param:
         * @return: Node
         * @throws
         */
        public Node bstSearch(Comparable data) {
            if(this.data.compareTo(data) == 0) {
                return this;
            }else if(this.data.compareTo(data)>0) {//如果要查找的对象小于当前对象,向左子树查找。
                if(this.left == null) {//如果左子树为空。
                    return null;
                }
                //递归向左子树查找。
                return this.left.bstSearch(data);
            }else if(this.data.compareTo(data) <= 0) {//如果要查找的对象大于或等于当前对象,向右子树查找。
                if(this.right == null) {
                    return null;
                }
                //递归向右子树查找
                return this.right.bstSearch(data);
            }
            return null;
        }

        /**
         * @Title: inFixOrder
         * @Description:中序遍历
         * @param:
         * @return: void
         * @throws
         */
        public void inFixOrder() {
            //先遍历左子树
            if(this.left!=null) {
                this.left.inFixOrder();
            }
            //打印数据
            System.out.println(this);
            //遍历右子树
            if(this.right!=null) {
                this.right.inFixOrder();
            }
        }

        /**
         * @Title: add
         * @Description:添加方法
         *  实现:调用 Comparable 接口的 compareTo 方法,比较对象大小
         *      1、如果当前对象小于指定对象,返回负整数
         *      2、如果当前对象等于指定对象,返回0
         *      3、如果当前对象大于指定对象,返回正整数
         *
         *     如果返回正整数,进左子树
         *     如果返回负整数或0,进右子树
         * @param: data
         * @return: void
         * @throws
         */
        public void add(Comparable data) {
            if(this.data.compareTo(data) > 0) {
                if(this.left==null) {
                    this.left = new Node(data);
                }else {
                    this.left.add(data);
                }
            }else if(this.data.compareTo(data) <= 0) {
                if(this.right == null) {
                    this.right = new Node(data);
                }else {
                    this.right.add(data);
                }
            }
        }
        /**
         * @Title:  Node
         * @Description:
         * @param:  @param data
         * @throws
         */
        public Node(Comparable data) {
            super();
            this.data = data;
        }
        /**
        *
        */
        @Override
        public String toString() {
            return "Node [data=" + data + "]";
        }
    }
}

测试代码:

/**
 * All rights Reserved, Designed By https://www.tulingxueyuan.com/
 * @Title:  BinarySearchTreeDemo.java
 * @Package com.tuling.tree
 * @Description:
 * @author: 小白
 * @Date 2019年12月7日 上午1:18:33
 * @version V1.0
 * @Copyright:
 */
package com.tuling.tree;

import com.tuling.tree.BinarySearchTree.Node;

/**
 * @ClassName BinarySearchTreeDemo
 * @Description
 * @Author 北京图灵学院
 * @Date 2019年12月7日 上午1:18:33
 */
public class BinarySearchTreeDemo {

    /**
     * @Title: main
     * @Description:
     * @param: @param args
     * @return: void
     * @throws
     */
    public static void main(String[] args) {
        BinarySearchTree bst = new BinarySearchTree();

        bst.add(25);
        bst.add(43);
        bst.add(2);
        bst.add(765);
        bst.add(23);
        bst.add(1);
        bst.add(76);

        bst.inFixOrder();
        System.out.println();
//      Node searchNode = bst.bstSearch(76);
//      System.out.println(searchNode);

        System.out.println("===========删除 25 ===========");
        bst.del(2);
        bst.inFixOrder();

    }

}

4.3. 5.4.平衡查找树

4.3.1. 5.4.1.AVL树

在计算机科学中,AVL树是最先发明的自平衡二叉查找树。在AVL树中任何节点的两个子树的高度最大差别为1,所以它也被称为高度平衡树。增加和删除可能需要通过一次或多次树旋转来重新平衡这个树。

4.3.1.1. 5.4.1.1.特点

AVL树本质上还是一棵二叉搜索树,它的特点是:

  1. 本身首先是一棵二叉搜索树。

2. 带有平衡条件:每个结点的左右子树的高度之差的绝对值(平衡因子)最多为1。 也就是说,AVL树,本质上是带了平衡功能的二叉查找树(二叉排序树,二叉搜索树)。

4.3.1.2. 5.4.1.2.单旋转

4.3.1.2.1. 5.4.1.2.1.右旋转
_images/19.jpg

思路分析:

  1. 回溯找到失去平衡的节点,以该节点为根,创建一个新节点
  2. 把新节点的右子树设置为当前节点的右子树
  3. 把新节点的左子树设置为当前节点的左子树的右子树
  4. 把当前节点的值换为左子节点的值
  5. 把当前节点的左子树设置为左子树的左子树
  6. 把当前节点的右子树设置为新节点

代码实现:

/**
 * @Title: rotateRight
 * @Description:右旋操作
 *  1、回溯找到失去平衡的节点,以该节点为根,创建一个新节点
 *  2、把新节点的右子树设置为当前节点的右子树
 *  3、把新节点的左子树设置为当前节点的左子树的右子树
 *  4、把当前节点的值换位左子节点的值
 *  5、把当前节点的左子树设置为左子树的左子树
 *  6、把当前节点的右子树设置为新节点
 * @param:
 * @return: void
 * @throws
 */
private void rotateRight() {
    //1、回溯找到失去平衡的节点,以该节点为根,创建一个新节点
    Node tempNode = new Node(data);
    //2、把新节点的右子树设置为当前节点的右子树
    tempNode.right = right;
    //3、把新节点的左子树设置为当前节点的左子树的右子树
    tempNode.left = left.right;
    //4、把当前节点的值换位左子节点的值
    data = left.data;
    //5、把当前节点的左子树设置为左子树的左子树
    left = left.left;
    //6、把当前节点的右子树设置为新节点
    right = tempNode;
}
4.3.1.2.2. 5.4.1.2.2.左旋转
_images/110.jpg

思路分析:

  1. 回溯找到失去平衡的节点,以该节点为根,创建一个新节点
  2. 把新节点的左子树设置为当前节点的左子树
  3. 把新节点的右子树设置为当前节点的右子树的左子树
  4. 把当前节点的值替换为右子节点的值
  5. 把当前节点的右子树设置为右子树的右子树
  6. 把当前节点的左子树设置为新节点

代码实现:

/**
 * @Title: rotateLeft
 * @Description:左旋操作:
 *  1、回溯找到失去平衡的节点,以该节点为根,创建一个新节点
 *  2、把新节点的左子树设置为当前节点的左子树
 *  3、把新节点的右子树设置为当前节点的右子树的左子树
 *  4、把当前节点的值替换为右子节点的值
 *  5、把当前节点的右子树设置为右子树的右子树
 *  6、把当前节点的左子树设置为新节点
 * @param:
 * @return: void
 * @throws
 */
private void rotateLeft() {
    System.out.println("旋转代码中的 data = " + data);
    //以根节点为参考,创建新的节点
    Node tempNode = new Node(data);
    //把新节点的左子树设置为当前节点的左子树
    tempNode.setLeft(left);
    //把新节点的右子树设置为当前节点的右子树的左子树
    tempNode.setRight(right.left);
    //把当前节点的值替换为右子树的值
    data = right.data;
    //把当前节点的右子树设置为当前节点右子树的右子树
    right = right.right;
    //把当前节点的左子树设置为新节点
    left = tempNode;
}

4.3.1.3. 5.4.1.3.双旋转

4.3.1.3.1. 5.4.1.3.1.右-左双旋转
_images/112.jpg
//先对当前节点的右孩子右旋
right.rotateRight();
//再进行左旋操作
rotateLeft();
4.3.1.3.2. 5.4.1.3.2.左-右双旋转
_images/113.jpg _images/23.jpg
//先对当前节点的左孩子进行左旋
left.rotateLeft();
//再进行右旋
rotateRight();

4.3.1.4. 5.4.1.4.实现

平衡二叉树实现增加旋转功能完整代码如下:

/**
 * All rights Reserved, Designed By https://www.tulingxueyuan.com/
 * @Title:  AVLTree.java
 * @Package com.tuling.tree
 * @Description:
 * @author: 小白
 * @Date 2019年12月8日 下午10:09:37
 * @version V1.0
 * @Copyright:
 */
package com.tuling.tree;

/**
 * @ClassName AVLTree
 * @Description
 * @Author 北京图灵学院
 * @Date 2019年12月8日 下午10:09:37
 */
public class AVLTree {

    private Node root;


    /**
     *
     * @Title: add
     * @Description:
     * @param: @param data
     * @return: void
     * @throws
     */
    public void add(int data) {
        System.out.print("当前添加数据:" + data + "    ");
        if(root == null) {
            root = new Node(data);
        }else {
            root.add(data);
        }
    }

    /**
     * @Title: rotateLeft
     * @Description:左旋操作
     * @param: node
     * @return: void
     * @throws
     */
    private Node rotateLeft(Node nodeN) {
        Node nodeC = nodeN.getRight();
        nodeN.setRight(nodeC.getLeft());
        nodeC.setLeft(nodeN);
        return nodeC;
    }

    public void inFixOrder() {
        if(root == null) {
            System.out.println("树为空!");
        }else {
            root.inFixOrder();
        }
    }

    public Node getRoot() {
        return root;
    }

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

    class Node{
        private Node left,right;
        private int data;
        /**
         * @Title:  Node
         * @Description:
         * @param:  @param data
         * @throws
         */
        public Node(int data) {
            this.data = data;
        }

        /**
         * @Title: add
         * @Description:
         * @param: data
         * @return: void
         * @throws
         */
        public void add(int data) {
            if(this.data > data) {
                if(this.left == null) {
                    this.left = new Node(data);
                }else {
                    this.left.add(data);
                }
            }else if(this.data < data) {
                if(this.right == null) {
                    this.right = new Node(data);
                }else {
                    this.right.add(data);
                }
            }
            //TODO:
            //做完添加操作,

            if(leftHeight() - rightHeight() > 1) {//如果左子树的高度大于右子树的高度,需要右旋操作。
                if(left!=null && this.left.rightHeight()>left.leftHeight()) {
                    System.out.print("左旋再右旋 -- " + left.data);
                    //先对当前节点的左孩子进行左旋
                    left.rotateLeft();
                    //再进行右旋
                    rotateRight();
                }else {
                    System.out.print("右旋" + data);
                    //直接右旋即可
                    rotateRight();
                }

            }

            if(rightHeight() - leftHeight() > 1) {//如果右子树的高度大于左子树的高度,需要左旋操作
                if(right!=null && right.leftHeight()>right.rightHeight()) {
                    System.out.print("右旋再左旋  -- " + right.data );
                    //先对象当前节点的右孩子右旋
                    right.rotateRight();
                    //再进行左旋操作
                    rotateLeft();
                }else {
                    System.out.print("左旋  -- " + data);
                    //直接左旋节课
                    rotateLeft();
                }
            }

        }

        /**
         * @Title: rotateRight
         * @Description:右旋操作
         *  1、回溯找到失去平衡的节点,以该节点为根,创建一个新节点
         *  2、把新节点的右子树设置为当前节点的右子树
         *  3、把新节点的左子树设置为当前节点的左子树的右子树
         *  4、把当前节点的值换位左子节点的值
         *  5、把当前节点的左子树设置为左子树的左子树
         *  6、把当前节点的右子树设置为新节点
         * @param:
         * @return: void
         * @throws
         */
        private void rotateRight() {
            //1、回溯找到失去平衡的节点,以该节点为根,创建一个新节点
            Node tempNode = new Node(data);
            //2、把新节点的右子树设置为当前节点的右子树
            tempNode.right = right;
            //3、把新节点的左子树设置为当前节点的左子树的右子树
            tempNode.left = left.right;
            //4、把当前节点的值换位左子节点的值
            data = left.data;
            //5、把当前节点的左子树设置为左子树的左子树
            left = left.left;
            //6、把当前节点的右子树设置为新节点
            right = tempNode;
        }

        /**
         * @Title: rotateLeft
         * @Description:左旋操作:
         *  1、回溯找到失去平衡的节点,以该节点为根,创建一个新节点
         *  2、把新节点的左子树设置为当前节点的左子树
         *  3、把新节点的右子树设置为当前节点的右子树的左子树
         *  4、把当前节点的值替换为右子节点的值
         *  5、把当前节点的右子树设置为右子树的右子树
         *  6、把当前节点的左子树设置为新节点
         * @param:
         * @return: void
         * @throws
         */
        private void rotateLeft() {
            System.out.println("旋转代码中的 data = " + data);
            //以根节点为参考,创建新的节点
            Node tempNode = new Node(data);
            //把新节点的左子树设置为当前节点的左子树
            tempNode.setLeft(left);
            //把新节点的右子树设置为当前节点的右子树的左子树
            tempNode.setRight(right.left);
            //把当前节点的值替换为右子树的值
            data = right.data;
            //把当前节点的右子树设置为当前节点右子树的右子树
            right = right.right;
            //把当前节点的左子树设置为新节点
            left = tempNode;
        }

        /**
         * @Title: rightHeight
         * @Description:
         * @param: @return
         * @return: int
         * @throws
         */
        private int rightHeight() {
            if(right == null) {
                return 0;
            }
            return height(right);
        }

        /**
         * @Title: height
         * @Description:
         * @param:
         * @return: int
         * @throws
         */
        private int height(Node node) {
            if(node == null) return 0;
            return 1+Math.max(height(node.left), height(node.right));
        }

        /**
         * @Title: leftHeight
         * @Description:
         * @param: @return
         * @return: int
         * @throws
         */
        private int leftHeight() {
            if(left == null)return 0;
            return height(left);
        }

        /**
         * @Title: inFixOrder
         * @Description:
         * @param:
         * @return: void
         * @throws
         */
        public void inFixOrder() {
            if(this.left!=null) {
                this.left.inFixOrder();
            }
            System.out.println(this.data);
            if(this.right!=null) {
                this.right.inFixOrder();
            }
        }

        public Node getLeft() {
            return left;
        }

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

        public Node getRight() {
            return right;
        }

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

        public int getData() {
            return data;
        }

        public void setData(int data) {
            this.data = data;
        }

    }
}