一、使用树这种结构的原因:

数组存储方式的分析

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

链式存储方式的分析

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

树存储方式的分析

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

二、二叉树

➢树有很多种,每个节点最多只能有两个子节点的一种形式称为二叉树。
➢二叉树的子节点分为左节点和右节点。
➢如果该二叉树的所有叶子节点都在最后一层,并且结点总数=2^n-1,n为层.数,则我们称为满二叉树
➢如果该二叉树的所有叶子节点都在最后一层或者倒数第二层,而且最后一层的叶子节点在左边连续,倒数第二层的叶子节点在右边连续,我们称为完全二叉树

前序遍历:先输出父节点,再遍历左子树和右子树
中序遍历:先遍历左子树,再输出父节点,再遍历右子树
后序遍历:先遍历左子树,再遍历右子树,最后输出父节点
小结:看输出父节点的顺序,就确定是前序,中序还是后序

package com.xudong.DataStructures;

public class BinaryTreeDemo {
    public static void main(String[] args) {
        //创建一个二叉树
        BinaryTree binaryTree = new BinaryTree();
        //创建需要的节点
        HeroNode1 root = new HeroNode1(1, "宋江");
        HeroNode1 node2 = new HeroNode1(2, "吴用");
        HeroNode1 node3 = new HeroNode1(3, "卢俊义");
        HeroNode1 node4 = new HeroNode1(4, "林冲");
        HeroNode1 node5 = new HeroNode1(5, "关胜");

        //方式一:手动创建二叉树
        root.setLeft(node2);
        root.setRight(node3);
        node3.setRight(node4);
        node3.setLeft(node5);
        binaryTree.setRoot(root);

        //测试
        System.out.println("前序遍历:");
        binaryTree.preOrder();
        System.out.println("中序遍历:");
        binaryTree.infixOrder();
        System.out.println("后序遍历:");
        binaryTree.postOrder();
        System.out.println("======================");

        //前序查找
        System.out.println("前序查找:");
        HeroNode1 resNode = binaryTree.preOrderSearch(5);
        if (resNode != null){
            System.out.printf("找到了信息为no= %d name= %s 的英雄。\n",resNode.getNo(),resNode.getName());
        }else {
            System.out.println("没有找到该英雄!");
        }
        //中序查找
        System.out.println("中序查找:");
        resNode = binaryTree.infixOrderSearch(5);
        if (resNode != null){
            System.out.printf("找到了信息为no= %d name= %s 的英雄。\n",resNode.getNo(),resNode.getName());
        }else {
            System.out.println("没有找到该英雄!");
        }
        //后序查找
        System.out.println("后序查找:");
        resNode = binaryTree.postOrderSearch(5);
        if (resNode != null){
            System.out.printf("找到了信息为no= %d name= %s 的英雄。\n",resNode.getNo(),resNode.getName());
        }else {
            System.out.println("没有找到该英雄!");
        }

        System.out.println("====================");
        //删除节点
        System.out.println("删除前,前序遍历:");
        binaryTree.preOrder();
        binaryTree.delNode(5);
        System.out.println("删除后,前序遍历:");
        binaryTree.preOrder();
        System.out.println("--------------------");
        System.out.println("删除前,中序遍历:");
        binaryTree.infixOrder();
        binaryTree.delNode(4);
        System.out.println("删除后,中序遍历:");
        binaryTree.infixOrder();
        System.out.println("--------------------");
        System.out.println("删除前,后序遍历:");
        binaryTree.postOrder();
        binaryTree.delNode(3);
        System.out.println("删除后,后序遍历:");
        binaryTree.postOrder();

    }
}

//定义BinaryTree二叉树
class BinaryTree{
    private HeroNode1 root;

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

    //前序遍历
    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;
        }
    }

    //删除节点
    public void delNode(int no){
        if (root != null){
            //立即判断root是不是要删除的节点
            if (root.getNo() == no){
                root = null;
            }else {
                //递归删除
                root.delNode(no);
            }
        }else {
            System.out.println("空树!不能删除");
        }
    }
}



//创建heroNode节点
class HeroNode1{
    private int no;
    private String name;
    private HeroNode1 left;
    private HeroNode1 right;

    public HeroNode1(int no, String name) {
        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;
    }

    @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);
    }

    //前序遍历查找
    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);
        }
        if (resNode != null){//说明在右子树找到
            return resNode;
        }
        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);
        }
        if (resNode != null){//说明在右子树找到
            return resNode;
        }
        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.如果删除的是叶子结点,则删除该节点
    //2.如果删除的节点是非叶子节点,则删除该子数
    public void delNode(int no){
        //向左节点递归删除
        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.delNode(no);
        }
        //向右子树递归删除
        if (this.right != null){
            this.right.delNode(no);
        }
    }
}

三、顺序存储二叉树

●从数据存储来看,数组存储方式和树的存储方式可以相互转换,即数组可以转换成树,树也可以转换成数组。

顺序存储二叉树的特点:

➢顺序二叉树通常只考虑完全二叉树
➢第n个元素的左子节点カ为【2n+ 1】
➢第n个元素的右子节点カ【2
n+2】
➢第n个元素的父子节点为【(n-1)/2】

package com.xudong;

public class arrBinaryTreeDemo {
    public static void main(String[] args) {
        int[] arr = {1,2,3,4,5,6,7};
        ArrBinaryTree arrBinaryTree = new ArrBinaryTree(arr);
        arrBinaryTree.preOrder();
    }
}

//实现顺序存储二叉树遍历
class ArrBinaryTree{
    private int[] arr;//存储数据节点的数组

    public ArrBinaryTree(int[] arr) {
        this.arr = arr;
    }

    //重载perOrder
    public void preOrder(){
        this.preOrder(0);
    }

    //顺序存储二叉树的前序遍历
    public void preOrder(int index){
        if (arr == null || arr.length == 0){
            System.out.println("数组为空!");
        }
        //输出当前元素
        System.out.println(arr[index]);
        //向左递归遍历
        if ((index * 2 + 1) < arr.length){
            preOrder(2 * index + 1);
        }
        //向右递归遍历
        if ((index * 2 + 2) < arr.length){
            preOrder(2 * index + 2);
        }
    }
}

四、线索化二叉树

➢n个结点的二叉链表中含有n+1【公式2n-(n-1)=n+1】个空指针域。利用二叉链表中的空指针域,存放指向该结点在某种遍历次序下的前驱和后继节点的指针(这种附加的指针称为"线索")
➢这种加上了线索的二叉链表称为线索链表,相应的二叉树称为线索二叉树(Threaded BinaryTree)。根据线索性质的不同,线索二叉树可分为前序线索二叉树、 中序线索二叉树和后序线索二叉树三种
➢一个结点的前一个节点,称为前驱节点
➢一个结点的后一个节点,称为后继节点

线索化二叉树应用案例

遍历线索化二叉树

说明:对前面的中序线索化的二叉树,进行遍历
分析:因为线索化后,各个结点指向有变化,因此原来的遍历方式不能使用,这时需要使用新的方式遍历线索化二叉树,各个节点可以通过线型方式遍历,因此无需使用递归方式,这样也提高了遍历的效率。遍历的次序应当和中序遍历保持一致

package com.xudong.DataStructures;

public class ThreadedBinaryTreeDemo {
    public static void main(String[] args) {
        HeroNode3 root = new HeroNode3(1, "Tom");
        HeroNode3 node2 = new HeroNode3(3, "jack");
        HeroNode3 node3 = new HeroNode3(6, "smith");
        HeroNode3 node4 = new HeroNode3(8, "mary");
        HeroNode3 node5 = new HeroNode3(10, "king");
        HeroNode3 node6 = new HeroNode3(14, "dim");

        //手动创建线索二叉树
        root.setLeft(node2);
        root.setRight(node3);
        node2.setLeft(node4);
        node2.setRight(node5);
        node3.setLeft(node6);

        ThreadedBinaryTree threadedBinaryTree = new ThreadedBinaryTree();
        threadedBinaryTree.setRoot(root);
        threadedBinaryTree.threadedNodes();

        //测试10号节点
        HeroNode3 leftNode = node5.getLeft();
        HeroNode3 rightNode = node5.getRight();
        System.out.println("10号节点的前驱节点是 =" + leftNode);
        System.out.println("10号节点的后继节点是 =" + rightNode);

        //遍历线索化二叉树
        System.out.println("使用线索化的方式遍历线索化二叉树:");
        threadedBinaryTree.threadedList();
    }
}

//
//定义ThreadedBinaryTree二叉树
class ThreadedBinaryTree{
    private HeroNode3 root;
    //在递归线索化时,pre总保留前一个节点
    private HeroNode3 pre = null;

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

    //重载threadedNodes方法
    public void threadedNodes(){
        this.threadedNodes(root);
    }

    //对二叉树进行中序线索化的方法.node就是当前需要线索化的节点
    public void threadedNodes(HeroNode3 node){
        //如果node == null,不能线索化
        if (node == null){
            return;
        }
        //1.线索话左子树
        threadedNodes(node.getLeft());

        //2.线索话当前节点
        //处理当前节点的前驱结点
        if (node.getLeft() == null){
            //让当前节点的左指针指向前驱节点
            node.setLeft(pre);
            //修改当前节点的左指针的类型,指向前驱节点
            node.setLeftType(1);
        }
        //处理后继节点
        if (pre != null && pre.getRight() == null){
            //让前驱节点的右指针指向当前节点
            pre.setRight(node);
            //修改前驱节点的右指针类型
            pre.setRightType(1);
        }
        //每处理一个节点后,让当前节点是下一个节点的前驱节点
        pre = node;

        //3.线索话右子树
        threadedNodes(node.getRight());
    }

    //线索化的遍历线索二叉树
    public void threadedList(){
        //定义一个变量,存储当前遍历的节点
        HeroNode3 node = root;
        while (node != null){
            //当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();
        }
    }
}

//创建heroNode节点
class HeroNode3{
    private int no;
    private String name;
    private HeroNode3 left;
    private HeroNode3 right;

    //如果leftType == 0 表示指向的是左子树,如果是 1 则表示指向前驱节点
    //如果rightType == 0 表示指向的是右子树,如果是 1 则表示指向后继节点
    private int leftType;
    private int rightType;

    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;
    }

    public HeroNode3(int no, String name) {
        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 HeroNode3 getLeft() {
        return left;
    }

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

    public HeroNode3 getRight() {
        return right;
    }

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

    @Override
    public String toString() {
        return "HeroNode3{" +
                "no=" + no +
                ", name='" + name + '\'' +
                '}';
    }
}

五、赫夫曼树

●给定n个权值作为n个叶子结点,构造一棵二叉树,若该树的带权路径长度(wpl)达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(HuffmanTree),还有的书翻译为霍夫曼树。
●赫夫曼树是带权路径长度最短的树,权值较大的结点离根较近。

路径和路径长度:在一棵树中,从一个结点往下可以达到的孩子或孙子结点之间的通路,称为路径。通路中分支的数目称为路径长度。若规定根结点的层数为1,则从根结点到第L层结点的路径长度为L-1
结点的权及带权路径长度:若将树中结点赋给一个有着某种含义的数值,则这个数值称为该结点的权。结点的带权路径长度为:从根结点到该结点之间的路径长度与该结点的权的乘积
树的带权路径长度:树的带权路径长度规定为所有叶子结点的带权路径长度之和,记为WPL(weighted path length) ,权值越大的结点离根结点越近的二叉树才是最优二叉树。
WPL最小的就是赫夫曼树

构成赫夫曼树的步骤

1)从小到大进行排序, 将每一个数据, 每个数据都是一个节点,每个节点可以看成是一颗最简单的二叉树
2)取出根节 点权值最小的两颗二叉树
3)组成一颗新的二叉树,该新的二叉树的根节点的权值是前面两颗二叉树根节点权值的和
4)再将这颗新的二叉树,以根节点的权值大小再次排序,不断重复1-2-3-4的步骤,直到数列中,所有的数据都被处理,就得到一颗赫夫曼树

package com.xudong.DataStructures;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class HuffmanTreeDemo {
    public static void main(String[] args) {
        int arr[] = {13,7,8,3,29,6,1};
        Node root = createHuffmanTree(arr);

        preOrder(root);

    }

    //前序遍历的方法
    public static void preOrder(Node root){
        if (root != null){
            root.preOrder();
        }else {
            System.out.println("这是个空树!");
        }
    }

    //创建赫夫曼树

    /**
     * @param arr 需要创建成赫夫曼树的数组
     * @return 创建好后的赫夫曼树的root节点
     */
    public static Node createHuffmanTree(int[] arr){
        //将arr的每个元素构成一个Node放入ArrayList中
        List<Node> nodes = new ArrayList<>();
        for (int value : arr){
            nodes.add(new Node(value));
        }

        while (nodes.size() > 1){
            //排序
            Collections.sort(nodes);

            //1.取出权值最小的节点(二叉树)
            Node leftNode = nodes.get(0);
            //2.取出权值第二小的节点(二叉树)
            Node rightNode = nodes.get(1);
            //3.创建一个新的二叉树
            Node parent = new Node(leftNode.value + rightNode.value);
            parent.left = leftNode;
            parent.right = rightNode;
            //4.从ArrayList删除处理过的二叉树
            nodes.remove(leftNode);
            nodes.remove(rightNode);
            //5.将parent加入到nodes
            nodes.add(parent);
        }
        //返回赫夫曼树root节点
        return nodes.get(0);
    }
}

//创建节点类,让Node对象持续进行Collections集合排序
class Node implements Comparable<Node>{
    int value;//节点权值
    Node left;//指向左子节点
    Node right;//指向右子节点

    //前序遍历
    public void preOrder(){
        System.out.println(this);
        if (this.left != null){
            this.left.preOrder();
        }
        if (this.right != null){
            this.right.preOrder();
        }
    }

    public Node(int value) {
        this.value = value;
    }

    @Override
    public String toString() {
        return "Node{" +
                "value=" + value +
                '}';
    }

    @Override
    public int compareTo(Node o) {
        //从小到大排序(从大到小加负号)
        return (this.value - o.value);
    }

}

六、二叉排序树

二叉排序树BST:(Binary Sort(Search) Tree),对于二叉排序树的任何一个非叶子节点,要求左子节点的值比当前节点的值小,右子节点的值比当前节点的值大。
特别说明:如果有相同的值,可以将该节点放在左子节点或右子节点

package com.xudong.DataStructures;

public class BinarySortTreeDemo {
    public static void main(String[] args) {
        int[] arr = {7,3,10,12,5,1,9,2};
        BinarySortTree binarySortTree = new BinarySortTree();
        //添加节点
        for (int i = 0; i < arr.length; i++) {
            binarySortTree.add(new Node2(arr[i]));
        }
        //中序遍历二叉排序树
        System.out.println("中序遍历二叉排序树:");
        binarySortTree.infixOrder();

        //删除叶子结点
        binarySortTree.delNode(2);
        binarySortTree.delNode(5);
        binarySortTree.delNode(9);
        binarySortTree.delNode(12);
        binarySortTree.delNode(7);
        binarySortTree.delNode(3);
        binarySortTree.delNode(10);
        System.out.println("删除节点后:");
        binarySortTree.infixOrder();
    }
}

//创建二叉排序树
class BinarySortTree{
    private Node2 root;

    //查找要删除的节点
    public Node2 search(int value){
        if (root == null){
            return null;
        }else {
            return root.search(value);
        }
    }
    //查找父节点
    public Node2 searchParent(int value){
        if (root == null){
            return null;
        }else {
            return root.searchParent(value);
        }
    }
    //删除右树的最小节点
    public int delRightTreeMin(Node2 node){
        Node2 target = node;
        //循环查找左子节点,就找到最小值
        while (target.left != null){
            target = target.left;
        }
        //此时target就指向了最小节点
        delNode(target.value);
        return target.value;
    }

    //删除节点
    public void delNode(int value){
        if (root == null){
            return;
        }else {
            //找到要删除的节点
            Node2 targetNode = search(value);
            //如果没有找到要删除的节点
            if (targetNode == null){
                return;
            }
            //如果发现二叉树只有一个节点
            if (root.left == null && root.right == null){
                root = null;
                return;
            }
            //找到targetNode的父节点
            Node2 parent = searchParent(value);

            //(一)如果要删除的节点是叶子结点
            if (targetNode.left == null && targetNode.right == null){
                //若targetNode是父节点的左子节点
                if (parent.left != null && parent.left.value == value){
                    parent.left = null;
                }else if (parent.right != null && parent.right.value == value){//若targetNode是父节点的右子节点
                    parent.right = null;
                }
            }else if (targetNode.left != null && targetNode.right != null){//(三)删除右两颗子树的节点
                int minVal = delRightTreeMin(targetNode.right);
                targetNode.value = minVal;
            }else {//(二)删除只有一颗子树的节点
                //如果要删除的节点有左子节点
                if (targetNode.left != null){
                    if (parent != null){
                        //如果targetNode 是 Parent的左子节点
                        if (parent.left.value == value){
                            parent.left = targetNode.left;
                        }else {//如果targetNode 是 Parent的右子节点
                            parent.right = targetNode.left;
                        }
                    }else {
                        root = targetNode.left;
                    }
                }else {//如果要删除的节点有右子节点
                    if (parent != null){
                        //如果targetNode 是 Parent的左子节点
                        if (parent.left.value == value){
                            parent.left = targetNode.right;
                        }else {//如果targetNode 是 Parent的右子节点
                            parent.right = targetNode.right;
                        }
                    }else {
                        root = targetNode.right;
                    }
                }
            }
        }
    }

    //添加节点
    public void add(Node2 node){
        if (root == null){
            root = node;
        }else {
            root.add(node);
        }
    }
    //中序查找
    public void infixOrder(){
        if (root != null){
            root.infixOrder();
        }else {
            System.out.println("二叉排序树为空!");
        }
    }
}


//创建Node节点
class Node2{
    int value;
    Node2 left;
    Node2 right;

    public Node2(int value) {
        this.value = value;
    }

    //-------------删除节点-----------------
    //查找要删除的节点
    public Node2 search(int value){
        if (value == this.value){//找到就是该节点
            return this;
        }else if (value < this.value){//如果查找的值小于当前节点,则向左递归查找
            //若左子节点为空
            if (this.left == null){
                return null;
            }
            return this.left.search(value);
        }else {//如果查找的值不小于当前节点,则向右子树递归查找
            if (this.right == null){
                return null;
            }
            return this.right.search(value);
        }
    }
    //查找要删除节点的父节点
    public Node2 searchParent(int value){//返回的是要删除节点的父节点
        //如果当前节点就是要删除的节点的父节点,那么返回
        if ((this.left != null && this.left.value == value) || (this.right != null && this.right.value == value)){
            return this;
        }else {
            //如果查找的值小于当前节点的值,并且当前节点的左子节点不为空
            if (value < this.value && this.left != null){
                return this.left.searchParent(value);//向左子树递归查找
            }else if (value >= this.value && this.right != null){
                return this.right.searchParent(value);//向右子树递归查找
            }else {
                return null;//没有找到就返回
            }
        }

    }
    //



    //以二叉排序树的方式添加节点
    public void add(Node2 node){
        if (node == null){
            return;
        }
        //判断当前节点的值与当前子树根节点的关系
        if (node.value < this.value){
            //如果当前左子树节点为空
            if (this.left == null){
                this.left = node;
            }else {
                //递归的向左子树添加
                this.left.add(node);
            }
        }else {
            //如果当前右子树节点为空
            if (this.right == null){
                this.right = node;
            }else {
                //递归的向右子树添加
                this.right.add(node);
            }
        }
    }

    @Override
    public String toString() {
        return "Node2{" +
                "value=" + value +
                '}';
    }

    //中序遍历
    public void infixOrder(){
        //递归向左子树中序遍历
        if (this.left != null){
            this.left.infixOrder();
        }
        //输出父节点
        System.out.println(this);
        //递归向右子树中序遍历
        if (this.right != null){
            this.right.infixOrder();
        }
    }
}

七、平衡二叉树(AVL树)

平衡二叉树介绍

在二叉排序树的基础上实现
平衡二叉树也叫平衡二叉搜索树(Self-balancing binary searchtree)又被称为AVL树,可以保证查询效率较高
●具有以下特点:它是一棵空树或它的根节点左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。平衡二叉树的常用实现方法有红黑树、AVL、替罪羊树、Treap、 伸展树等。

左旋转

右旋转

双旋转

➢当符合右旋转条件时,它的左子树的右子树高度大于它左子树的高度时,就先对当前节点的左节点进行左旋转,再对当前节点进行右旋转
➢当符合左旋转条件时,它的右子树的左子树高度大于它右子树的高度时,就先对当前节点的右节点进行右旋转,再对当前节点进行左旋转

package com.xudong.DataStructures;

public class AVLTreeDemo {
    public static void main(String[] args) {
        //int[] arr = {4,3,6,5,7,8};
        //int[] arr = {10,12,8,9,7,6};
        int[] arr = {10,11,7,6,8,9};
        AVLTree avlTree = new AVLTree();
        //添加节点
        for (int i = 0; i < arr.length; i++) {
            avlTree.add(new Node3(arr[i]));
        }

        System.out.println("中序遍历:");
        avlTree.infixOrder();

        System.out.println("在平衡处理之后:");
        System.out.println("树的高度:" + avlTree.getRoot().height());
        System.out.println("树的左子树高度:" + avlTree.getRoot().leftHeight());
        System.out.println("树的右子树高度:" + avlTree.getRoot().rightHeight());
        System.out.println("当前根节点:" + avlTree.getRoot());

    }
}

//创建AVL树
class AVLTree{
    private Node3 root;

    public Node3 getRoot() {
        return root;
    }

    //查找要删除的节点
    public Node3 search(int value){
        if (root == null){
            return null;
        }else {
            return root.search(value);
        }
    }
    //查找父节点
    public Node3 searchParent(int value){
        if (root == null){
            return null;
        }else {
            return root.searchParent(value);
        }
    }
    //删除右树的最小节点
    public int delRightTreeMin(Node3 node){
        Node3 target = node;
        //循环查找左子节点,就找到最小值
        while (target.left != null){
            target = target.left;
        }
        //此时target就指向了最小节点
        delNode(target.value);
        return target.value;
    }

    //删除节点
    public void delNode(int value){
        if (root == null){
            return;
        }else {
            //找到要删除的节点
            Node3 targetNode = search(value);
            //如果没有找到要删除的节点
            if (targetNode == null){
                return;
            }
            //如果发现二叉树只有一个节点
            if (root.left == null && root.right == null){
                root = null;
                return;
            }
            //找到targetNode的父节点
            Node3 parent = searchParent(value);

            //(一)如果要删除的节点是叶子结点
            if (targetNode.left == null && targetNode.right == null){
                //若targetNode是父节点的左子节点
                if (parent.left != null && parent.left.value == value){
                    parent.left = null;
                }else if (parent.right != null && parent.right.value == value){//若targetNode是父节点的右子节点
                    parent.right = null;
                }
            }else if (targetNode.left != null && targetNode.right != null){//(三)删除右两颗子树的节点
                int minVal = delRightTreeMin(targetNode.right);
                targetNode.value = minVal;
            }else {//(二)删除只有一颗子树的节点
                //如果要删除的节点有左子节点
                if (targetNode.left != null){
                    if (parent != null){
                        //如果targetNode 是 Parent的左子节点
                        if (parent.left.value == value){
                            parent.left = targetNode.left;
                        }else {//如果targetNode 是 Parent的右子节点
                            parent.right = targetNode.left;
                        }
                    }else {
                        root = targetNode.left;
                    }
                }else {//如果要删除的节点有右子节点
                    if (parent != null){
                        //如果targetNode 是 Parent的左子节点
                        if (parent.left.value == value){
                            parent.left = targetNode.right;
                        }else {//如果targetNode 是 Parent的右子节点
                            parent.right = targetNode.right;
                        }
                    }else {
                        root = targetNode.right;
                    }
                }
            }
        }
    }

    //添加节点
    public void add(Node3 node){
        if (root == null){
            root = node;
        }else {
            root.add(node);
        }
    }
    //中序查找
    public void infixOrder(){
        if (root != null){
            root.infixOrder();
        }else {
            System.out.println("二叉排序树为空!");
        }
    }
}


//创建Node节点
class Node3{
    int value;
    Node3 left;
    Node3 right;


    public Node3(int value) {
        this.value = value;
    }

    //找到以根节点为节点的树高度
    public int height(){
        return Math.max(left == null ? 0 : left.height(),right == null ? 0 : right.height()) + 1;
    }

    //返回左子树的高度
    public int leftHeight(){
        if (left == null){
            return 0;
        }
        return left.height();
    }
    //返回左子树的高度
    public int rightHeight(){
        if (right == null){
            return 0;
        }
        return right.height();
    }

    //左旋转的方法
    private void leftRotate(){
        //创建新的节点,以当前根节点的值
        Node3 newNode = new Node3(value);
        //把新节点的左子树设置成当前节点的左子树
        newNode.left = left;
        //把新的节点的右子树设置成当前节点右子树的左子树
        newNode.right = right.left;
        //把当前结点的值替换成右子节点的值
        value = right.value;
        //把当前节点的右子树设置成当前节点右子树的右子树
        right = right.right;
        //把当前节点的左子节点设置成新的节点
        left = newNode;
    }
    //右旋转
    private void rightRotate(){
        Node3 newNode = new Node3(value);
        newNode.right = right;
        newNode.left = left.right;
        value = left.value;
        left = left.left;
        right = newNode;
    }

    //-------------删除节点-----------------
    //查找要删除的节点
    public Node3 search(int value){
        if (value == this.value){//找到就是该节点
            return this;
        }else if (value < this.value){//如果查找的值小于当前节点,则向左递归查找
            //若左子节点为空
            if (this.left == null){
                return null;
            }
            return this.left.search(value);
        }else {//如果查找的值不小于当前节点,则向右子树递归查找
            if (this.right == null){
                return null;
            }
            return this.right.search(value);
        }
    }
    //查找要删除节点的父节点
    public Node3 searchParent(int value){//返回的是要删除节点的父节点
        //如果当前节点就是要删除的节点的父节点,那么返回
        if ((this.left != null && this.left.value == value) || (this.right != null && this.right.value == value)){
            return this;
        }else {
            //如果查找的值小于当前节点的值,并且当前节点的左子节点不为空
            if (value < this.value && this.left != null){
                return this.left.searchParent(value);//向左子树递归查找
            }else if (value >= this.value && this.right != null){
                return this.right.searchParent(value);//向右子树递归查找
            }else {
                return null;//没有找到就返回
            }
        }

    }
    //



    //以二叉排序树的方式添加节点
    public void add(Node3 node){
        if (node == null){
            return;
        }
        //判断当前节点的值与当前子树根节点的关系
        if (node.value < this.value){
            //如果当前左子树节点为空
            if (this.left == null){
                this.left = node;
            }else {
                //递归的向左子树添加
                this.left.add(node);
            }
        }else {
            //如果当前右子树节点为空
            if (this.right == null){
                this.right = node;
            }else {
                //递归的向右子树添加
                this.right.add(node);
            }
        }
        //当添加完一个节点后,(右-左)子树高度差大于1时,左旋转
        if (rightHeight() - leftHeight() > 1){
            //如果它的右子树的左子树高度大于它的右子树高度
            if (right != null && right.leftHeight() > right.rightHeight()){
                //先对当前节点的右节点(右子树)进行右旋转
                right.rightRotate();
                //再对当前节点进行左旋转
                leftRotate();
            }else {
                leftRotate();
            }
            return;
        }
        //当添加完一个节点后,(左-右)子树高度差大于1时,左旋转
        if (leftHeight() - rightHeight() > 1){
            //如果它的左子树的右子树高度大于它的左子树高度
            if (left != null && left.rightHeight() > left.leftHeight()){
                //先对当前节点的左节点(左子树)进行左旋转
                left.leftRotate();
                //再对当前节点进行右旋转
                rightRotate();
            }else {
                rightRotate();
            }
        }
    }

    @Override
    public String toString() {
        return "Node2{" +
                "value=" + value +
                '}';
    }

    //中序遍历
    public void infixOrder(){
        //递归向左子树中序遍历
        if (this.left != null){
            this.left.infixOrder();
        }
        //输出父节点
        System.out.println(this);
        //递归向右子树中序遍历
        if (this.right != null){
            this.right.infixOrder();
        }
    }
}

八、多路查找树

●二叉树需要加载到内存的,如果二叉树的节点少,没有什么问题,但是如果二叉树的节点很多(此如1亿),就存在如下问题

问题1:在构建叉树时,需要多次进行1/o操作海量数据存在数据库或文件中),节点海量,构建=叉树时,速度有影响
问题2:节点海量,也会造成二叉树的高度很大,会降低操作速度

1. 多叉树

●在二叉树中,每个节点有数据项,最多有两个子节点。如果允许每个节点可以有更多的数据项和更多的子节点,就是多叉树(multiwaytree)
●后面的2-3树,2-3-4树就是多叉树,多叉树通过重新组织节点,减少树的高度,能对二叉树进行优化。

2. B树

●如图B树通过重新组织节点,降低了树的高度

●文件系统及数据库系统的设计者利用了磁盘预读原理,将一个节点的大小设为等于一个页(页得大小通常为4k),这样每个节点只需要一次I/O就可以完全载入
●将树的度M设置为1024,在600亿个元素中最多只需要4次I/O操作就可以读取到想要的元素, B树(广泛应用于文件存储系统以及数据库系统中)

3. B+树

B+树是B树的变体,也是一种多路搜索树

4.B*树

B*树是B+树的变体,在B+树的非根和非叶子节点再增加指向兄弟的指针

5. 2-3树

2-3树是最简单的B树结构
●2-3树的所有叶子节点都在同一层(只要是B树都满足这个条件)
●有两个子节点的节点叫二节点,二节点要么没有子节点,要么有两个子节点.
●有三个子节点的节点叫三节点,三节点要么没有子节点,要么有三个子节点.
●2-3树是由二节点和三节点构成的树。

posted @ 2020-08-05 22:13  旭东东  阅读(179)  评论(0编辑  收藏  举报