树结构的实现与复习

本文深入探讨了二叉树的特性,包括前序、中序和后序遍历,以及如何查找特定节点。进一步,介绍了顺序存储二叉树的概念,以及在存储和遍历上的优势。接着,详细讲解了线索化二叉树的构建和遍历,以及如何删除节点。此外,还分析了赫夫曼树的构造过程和优化查询速度的原理。最后,讨论了平衡二叉树(AVL树)的概念,以及左旋、右旋和双旋的调整策略,以确保树的平衡。
摘要由CSDN通过智能技术生成

目录

一,树结构的基础介绍:

1.为什么需要树这种数据结构::

a)数组存储方式的分析

b)链式存储方式的分析:

c)数存储方式的分析:

2.数的示意图以及常用的术语:

 二,二叉树:

1.二叉树的概念

 2.二叉树的遍历:

 3.二叉树查找指定的节点

三,顺序存储二叉树:

1.顺序存储二叉树的基本概念:

2.顺序存储二叉树的基本遍历 

3.顺序存储二叉树的应用实例;

四,线索化二叉树:

 1.线索化二叉树的基本介绍:

2.线索化二叉树的应用实例:

五,赫夫曼树

1.赫夫曼树的基本介绍

2.赫夫曼树创建思路图解

 3.赫夫曼树的代码实现

六,二叉排序树

1.二叉排序树的介绍

七,平衡二叉树(AVL树)

1.平衡二叉树的基本介绍

​ 2.平衡二叉树的左旋转:

3.平衡二叉树的右旋转:

 4.平衡二叉树的双旋转:

八,多路查找树

1.二叉树与B树

a)二叉树的问题分析

b)多叉树

 2.B树的基本介绍:

3.B树,B+树和B*树:

a)B树的介绍:

b)B+树的基本介绍:

c)B*树的基本介绍


一,树结构的基础介绍:

1.为什么需要树这种数据结构::

a)数组存储方式的分析

优点:通过下标方式访问元素,速度快。对于有序数组,还可使用二分查找提高检索速度。

缺点:如果要检索具体某个值,或者插入值(按一定顺序)会整体移动,效率较低

示意图

b)链式存储方式的分析:

优点:在一定程度上对数组存储方式有优化(比如:插入一个数值节点,只需要将插入节点,链接到链表中即可, 删除效率也很好)。

缺点:在进行检索时,效率仍然较低,比如(检索某个值,需要从头节点开始遍历)

示意图

c)数存储方式的分析:

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

案例: [7, 3, 10, 1, 5, 9, 12]

2.数的示意图以及常用的术语:

数的常用术语:

1) 节点

2) 根节点

3) 父节点

4) 子节点

5) 叶子节点 (没有子节点的节点)

6) 节点的权(节点值)

7) 路径(从 root 节点找到该节点的路线)

8) 层

9) 子树

10) 树的高度(最大层数)

11) 森林 :多颗子树构成森林

 二,二叉树:

1.二叉树的概念

1) 树有很多种,每个节点最多只能有两个子节点的一种形式称为二叉树。

2) 二叉树的子节点分为左节点和右节点

3) 示意图

 4) 如果该二叉树的所有叶子节点都在最后一层,并且结点总数= 2^n -1 , n 为层数,则我们称为满二叉树。

5) 如果该二叉树的所有叶子节点都在最后一层或者倒数第二层,而且最后一层的叶子节点在左边连续,倒数第二 层的叶子节点在右边连续,我们称为完全二叉树 

 2.二叉树的遍历:

▲二叉数可使用前序,中序和后序进行遍历:

1) 前序遍历: 先输出父节点,再遍历左子树和右子树

2) 中序遍历: 先遍历左子树,再输出父节点,再遍历右子树

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

二叉树遍历的实现:

a)思路分析:

b)代码实现:

public class BinaryTreeDemo {
        public static void main(String[] args) {
            //先需要创建一颗二叉树
            BinaryTree binaryTree = new BinaryTree();
            //创建需要的结点
            HeroNode root = new HeroNode(1, "宋江");
            HeroNode node2 = new HeroNode(2, "吴用");
            HeroNode node3 = new HeroNode(3, "卢俊义");
            HeroNode node4 = new HeroNode(4, "林冲");
            HeroNode node5 = new HeroNode(5, "关胜");
            //说明,我们先手动创建该二叉树,后面我们学习递归的方式创建二叉树
            root.setLeft(node2);
            root.setRight(node3);
            node3.setRight(node4);
            node3.setLeft(node5);
            binaryTree.setRoot(root);
            //测试
            System.out.println("前序遍历"); // 1,2,3,5,4
            binaryTree.preOrder();
            //测试
            System.out.println("中序遍历");
            binaryTree.infixOrder(); // 2,1,5,3,4

            System.out.println("后序遍历");
            binaryTree.postOrder(); // 2,5,4,3,1
        }
    }
//定义 BinaryTree 二叉树
class BinaryTree {
    private HeroNode root;
    public void setRoot(HeroNode 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("二叉树为空,无法遍历");
        }
    }
}
    //先创建 HeroNode 结点
    class HeroNode {
        private int no;
        private String name;
        private HeroNode left; //默认 null
        private HeroNode right; //默认 null
        public HeroNode(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 HeroNode getLeft() {
            return left;
        }
        public void setLeft(HeroNode left) {
            this.left = left;
        }
        public HeroNode getRight() {
            return right;
        }
        public void setRight(HeroNode right) {
            this.right = right;
        }
        @Override
        public String toString() {
            return "HeroNode [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);
        }
    }

 3.二叉树查找指定的节点

要求

1) 请编写前序查找,中序查找和后序查找的方法。

2) 并分别使用三种查找方式,查找 heroNO = 5 的节点

3) 并分析各种查找方式,分别比较了多少次

a)思路分析图解

 b)代码实现:

public static void main(String[] args) {
        //先需要创建一颗二叉树
        BinaryTree binaryTree = new BinaryTree();
        //创建需要的结点
        HeroNode root = new HeroNode(1, "宋江");
        HeroNode node2 = new HeroNode(2, "吴用");
        HeroNode node3 = new HeroNode(3, "卢俊义");
        HeroNode node4 = new HeroNode(4, "林冲");
        HeroNode node5 = new HeroNode(5, "关胜");
        //说明,我们先手动创建该二叉树,后面我们学习递归的方式创建二叉树
        root.setLeft(node2);
        root.setRight(node3);
        node3.setRight(node4);
        node3.setLeft(node5);
        binaryTree.setRoot(root);
        //测试
        System.out.println("前序遍历"); // 1,2,3,5,4
        binaryTree.preOrder();
        //测试
        System.out.println("中序遍历");
        binaryTree.infixOrder(); // 2,1,5,3,4
        //
        System.out.println("后序遍历");
        binaryTree.postOrder(); // 2,5,4,3,1
        //前序遍历
        //前序遍历的次数 :4
        // System.out.println("前序遍历方式~~~");
        // HeroNode resNode = binaryTree.preOrderSearch(5);
        // if (resNode != null) {
        // System.out.printf("找到了,信息为 no=%d name=%s", resNode.getNo(), resNode.getName());
        // } else {
        // System.out.printf("没有找到 no = %d 的英雄", 5);
        // }
        //中序遍历查找
        //中序遍历 3 次
        // System.out.println("中序遍历方式~~~");
        // HeroNode resNode = binaryTree.infixOrderSearch(5);
        // if (resNode != null) {
        // System.out.printf("找到了,信息为 no=%d name=%s", resNode.getNo(), resNode.getName());
        // } else {
        // System.out.printf("没有找到 no = %d 的英雄", 5);
        // }
                //后序遍历查找
        //后序遍历查找的次数 2 次
        System.out.println("后序遍历方式~~~");
        HeroNode resNode = binaryTree.postOrderSearch(5);
        if (resNode != null) {
            System.out.printf("找到了,信息为 no=%d name=%s", resNode.getNo(), resNode.getName());
        } else {
            System.out.printf("没有找到 no = %d 的英雄", 5);
        }
    }
}
    //定义 BinaryTree 二叉树
class BinaryTree {
    private HeroNode root;
    public void setRoot(HeroNode 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 HeroNode preOrderSearch(int no) {
        if(root != null) {
            return root.preOrderSearch(no);
        } else {
            return null;
        }
    }
    //中序遍历
    public HeroNode infixOrderSearch(int no) {
        if(root != null) {
            return root.infixOrderSearch(no);
        }else {
            return null;
        }
    }
    //后序遍历
    public HeroNode postOrderSearch(int no) {
        if(root != null) {
            return this.root.postOrderSearch(no);
        }else {
            return null;
        }
    }
//先创建 HeroNode 结点
class HeroNode {
    private int no;
    private String name;
    private HeroNode left; //默认 null
    private HeroNode right; //默认 null
    public HeroNode(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 HeroNode getLeft() {
        return left;
    }
    public void setLeft(HeroNode left) {
        this.left = left;
    }
    public HeroNode getRight() {
        return right;
    }
    public void setRight(HeroNode right) {
        this.right = right;
    }
    @Override
    public String toString() {
        return "HeroNode [no=" + no + ", name=" + name + "]";
    }
    //编写前序遍历的方法
    public void preOrder() {
        System.out.println(this); //先输出父结点
        //递归向左子树前序遍历
        if(this.left != null) {
            this.left.preOrder();
        }
        //递归向右子树前序遍历
        if(this.right != null) {
            this.right.preOrder();
        }
    }
    //中序遍历
    public void infixOrder() {
        //递归向左子树中序遍历
        if(this.left != null) {
            this.left.infixOrder();
        }
        //输出父结点
        System.out.println(this);
        //递归向右子树中序遍历
        if(this.right != null) {
            this.right.infixOrder();
        }
    }
    //后序遍历
    public void postOrder() {
        if(this.left != null) {
            this.left.postOrder();
        }
        if(this.right != null) {
            this.right.postOrder();
        }
        System.out.println(this);
    }
        //前序遍历查找
    /**
     *
     * @param no 查找 no
     * @return 如果找到就返回该 Node ,如果没有找到返回 null */
    public HeroNode preOrderSearch(int no){
        System.out.println("进入前序遍历");
        //比较当前结点是不是
        if(this.no == no) {
            return this;
        }
        //1.则判断当前结点的左子节点是否为空,如果不为空,则递归前序查找
        //2.如果左递归前序查找,找到结点,则返回
        HeroNode resNode = null;
        if(this.left != null) {
            resNode = this.left.preOrderSearch(no);
        }
        if(resNode != null) {//说明我们左子树找到
            return resNode;
        }
        //1.左递归前序查找,找到结点,则返回,否继续判断,
        //2.当前的结点的右子节点是否为空,如果不空,则继续向右递归前序查找
        if(this.right != null) {
            resNode = this.right.preOrderSearch(no);
        }
        return resNode;
    }
    //中序遍历查找
    public HeroNode infixOrderSearch(int no) {
        //判断当前结点的左子节点是否为空,如果不为空,则递归中序查找
        HeroNode resNode = null;
        if(this.left != null) {
            resNode = this.left.infixOrderSearch(no);
        }
        if(resNode != null) {
            return resNode;
        }
        System.out.println("进入中序查找");
        //如果找到,则返回,如果没有找到,就和当前结点比较,如果是则返回当前结点
        if(this.no == no) {
            return this;
        }
        //否则继续进行右递归的中序查找
        if(this.right != null) {
            resNode = this.right.infixOrderSearch(no);
        }
        return resNode;
    }
    //后序遍历查找
    public HeroNode postOrderSearch(int no) {
        //判断当前结点的左子节点是否为空,如果不为空,则递归后序查找
        HeroNode 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;
        }
        System.out.println("进入后序查找");
        //如果左右子树都没有找到,就比较当前结点是不是
        if(this.no == no) {
            return this;
        }
        return resNode;
    }
}

4.二叉树删除节点

 要求

1) 如果删除的节点是叶子节点,则删除该节点

2) 如果删除的节点是非叶子节点,则删除该子树

3) 测试,删除掉 5 号叶子节点 和 3 号子树.

a)完成删除思路分析

 b)代码实现:

HeroNode 类增加方法
递归删除结点
1.如果删除的节点是叶子节点,则删除该节点
2.如果删除的节点是非叶子节点,则删除该子树
public void delNode(int no) {
//思路
    /*
    * 1. 因为我们的二叉树是单向的,所以我们是判断当前结点的子结点是否需要删除结点,而不能去判断
    当前这个结点是不是需要删除结点
    2. 如果当前结点的左子结点不为空,并且左子结点 就是要删除结点,就将 this.left = null; 并且就返回
            (结束递归删除)
    3. 如果当前结点的右子结点不为空,并且右子结点 就是要删除结点,就将 this.right= null ;并且就返回
            (结束递归删除)
    4. 如果第 2 和第 3 步没有删除结点,那么我们就需要向左子树进行递归删除
    5. 如果第 4 步也没有删除结点,则应当向右子树进行递归删除. */
//2. 如果当前结点的左子结点不为空,并且左子结点 就是要删除结点,就将 this.left = null; 并且就返回(结束递归删除)
    if(this.left != null && this.left.no == no) {
        this.left = null;
        return;
    }
//3.如果当前结点的右子结点不为空,并且右子结点 就是要删除结点,就将 this.right= null ;并且就返回(结
    束递归删除)
    if(this.right != null && this.right.no == no) {
        this.right = null;
        return;
    }
//4.我们就需要向左子树进行递归删除
    if(this.left != null) {
        this.left.delNode(no);
    }
//5.则应当向右子树进行递归删除
    if(this.right != null) {
        this.right.delNode(no);
    }
}
在 BinaryTree 类增加方法
   删除结点
public void delNode(int no) {
    if(root != null) {
        //如果只有一个 root 结点, 这里立即判断 root 是不是就是要删除结点
        if(root.getNo() == no) {
            root = null;
        } else {
            //递归删除
            root.delNode(no);
        }
    }else{
        System.out.println("空树,不能删除~");
    }
}

在 BinaryTreeDemo 类增加测试代码:
  测试一把删除结点
System.out.println("删除前,前序遍历");
binaryTree.preOrder(); // 1,2,3,5,4
binaryTree.delNode(5);
//binaryTree.delNode(3);
System.out.println("删除后,前序遍历");
binaryTree.preOrder(); // 1,2,3,4

三,顺序存储二叉树:

1.顺序存储二叉树的基本概念:

        ▲基本说明

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

▲要求:

1) 右图的二叉树的结点,要求以数组的方式来存放 arr : [1, 2, 3, 4, 5, 6, 6]

2) 要求在遍历数组 arr 时,仍然可以以前序遍历,中序遍历和后序遍历的方式完成结点的遍历

顺序存储二叉树的特点:

1) 顺序二叉树通常只考虑完全二叉树

2) 第 n 个元素的左子节点为 2 * n + 1

3) 第 n 个元素的右子节点为 2 * n + 2

4) 第 n 个元素的父节点为 (n-1) / 2

5) n : 表示二叉树中的第几个元素(按 0 开始编号如图所示) 

2.顺序存储二叉树的基本遍历 

需求: 给你一个数组 {1,2,3,4,5,6,7},要求以二叉树前序遍历的方式进行遍历。 前序遍历的结果应当为 1,2,4,5,3,6,7

▲代码实现:

 public class ArrBinaryTreeDemo {
        public static void main(String[] args) {
            int[] arr = { 1, 2, 3, 4, 5, 6, 7 };
            //创建一个 ArrBinaryTree
            ArrBinaryTree arrBinaryTree = new ArrBinaryTree(arr);
            arrBinaryTree.preOrder(); // 1,2,4,5,3,6,7
        }
    }
    //编写一个 ArrayBinaryTree, 实现顺序存储二叉树遍历
    class ArrBinaryTree {
        private int[] arr;//存储数据结点的数组

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

        //重载 preOrder
        public void preOrder() {
            this.preOrder(0);
        }
        //编写一个方法,完成顺序存储二叉树的前序遍历

        /**
         * @param index 数组的下标
         */
        public void preOrder(int index) {
            //如果数组为空,或者 arr.length = 0
            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);
            }
        }
    }

3.顺序存储二叉树的应用实例;

★八大排序算法中的堆排序就需要使用到顺序存储二叉树

四,线索化二叉树:

▲将数列 {1, 3, 6, 8, 10, 14 } 构建成一颗二叉树. n+1= 

问题分析:

1) 当我们对上面的二叉树进行中序遍历时,数列为 {8, 3, 10, 1, 6, 14 }

2) 但是 6, 8, 10, 14 这几个节点的 左右指针,并没有完全的利用上.

3) 如果我们希望充分的利用 各个节点的左右指针, 让各个节点可以指向自己的前后节点,怎么办? 4) 解决方案-线索二叉树 

 1.线索化二叉树的基本介绍:

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

3) 一个结点的前一个结点,称为前驱结点

4) 一个结点的后一个结点,称为后继结点

2.线索化二叉树的应用实例:

应用案例说明:将下面的二叉树,进行中序线索二叉树。中序遍历的数列为 {8, 3,10,1,14,6}

思路分析: 中序遍历的结果:{8, 3, 10,1,14,6}

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

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

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

代码实现:

 public class ThreadedBinaryTreeDemo {
        public static void main(String[] args) {
            //测试一把中序线索二叉树的功能
            HeroNode root = new HeroNode(1, "tom");
            HeroNode node2 = new HeroNode(3, "jack");
            HeroNode node3 = new HeroNode(6, "smith");
            HeroNode node4 = new HeroNode(8, "mary");
            HeroNode node5 = new HeroNode(10, "king");
            HeroNode node6 = new HeroNode(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 号节点测试
            HeroNode leftNode = node5.getLeft();
            HeroNode rightNode = node5.getRight();
            System.out.println("10 号结点的前驱结点是 =" + leftNode); //3
            System.out.println("10 号结点的后继结点是=" + rightNode); //1
            //当线索化二叉树后,能在使用原来的遍历方法
            //threadedBinaryTree.infixOrder();
            System.out.println("使用线索化的方式遍历 线索化二叉树");
            threadedBinaryTree.threadedList(); // 8, 3,10,1,14,6
        }
    }
    //定义 ThreadedBinaryTree 实现了线索化功能的二叉树
    class ThreadedBinaryTree {
        private HeroNode root;
        //为了实现线索化,需要创建要给指向当前结点的前驱结点的指针
        //在递归进行线索化时,pre 总是保留前一个结点
        private HeroNode pre = null;
        public void setRoot(HeroNode root) {
            this.root = root;
        }
        //重载一把 threadedNodes 方法
        public void threadedNodes() {
            this.threadedNodes(root);
        }
        //遍历线索化二叉树的方法
        public void threadedList() {
            //定义一个变量,存储当前遍历的结点,从 root 开始
            HeroNode node = root;
            while(node != null) {
                //循环的找到 leftType == 1 的结点,第一个找到就是 8 结点
                //后面随着遍历而变化,因为当 leftType==1 时,说明该结点是按照线索化
                //处理后的有效结点
                while(node.getLeftType() == 0) {
                    node = node.getLeft();
                }
                //打印当前这个结点
                System.out.println(node);
                //如果当前结点的右指针指向的是后继结点,就一直输出
                while(node.getRightType() == 1) {
                    //获取到当前结点的后继结点
                    node = node.getRight();
                    System.out.println(node);
                }
                //替换这个遍历的结点
                node = node.getRight();
            }
        }
//编写对二叉树进行中序线索化的方法
/**
 *
 * @param node 就是当前需要线索化的结点
 */
public void threadedNodes(HeroNode node) {
//如果 node==null, 不能线索化
    if(node == null) {
        return;
    }
    //(一)先线索化左子树
    threadedNodes(node.getLeft());
    //(二)线索化当前结点[有难度]
    //处理当前结点的前驱结点
    //以 8 结点来理解
    //8 结点的.left = null , 8 结点的.leftType = 1
    if(node.getLeft() == null) {
        //让当前结点的左指针指向前驱结点
        node.setLeft(pre);
        //修改当前结点的左指针的类型,指向前驱结点
        node.setLeftType(1);
    }
    //处理后继结点
    if (pre != null && pre.getRight() == null){
        //让前驱结点的右指针指向当前结点
        pre.setRight(node);
        //修改前驱结点的右指针类型
        pre.setRightType(1);
    }
    //!!! 每处理一个结点后,让当前结点是下一个结点的前驱结点
    pre = node;
    //(三)在线索化右子树
    threadedNodes(node.getRight());
}
        //删除结点
        public void delNode(int no) {
            if(root != null) {
        //如果只有一个 root 结点, 这里立即判断 root 是不是就是要删除结点
                if(root.getNo() == no) {
                    root = null;
                } else {
                    //递归删除
                    root.delNode(no);
                }
            }else{
                System.out.println("空树,不能删除~");
            }
        }
        //前序遍历
        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 HeroNode preOrderSearch(int no) {
            if(root != null) {
                return root.preOrderSearch(no);
            } else {
                return null;
            }
        }
        //中序遍历
        public HeroNode infixOrderSearch(int no) {
            if(root != null) {
                return root.infixOrderSearch(no);
            }else {
                return null;
            }
        }
        //后序遍历
        public HeroNode postOrderSearch(int no) {
            if(root != null) {
                return this.root.postOrderSearch(no);
            }else {
                return null;
            }
        }
    }
    //先创建 HeroNode 结点
    class HeroNode {
        private int no;
        private String name;
        private HeroNode left; //默认 null
        private HeroNode right; //默认 null
        //说明
        //1. 如果 leftType == 0 表示指向的是左子树, 如果 1 则表示指向前驱结点
        //2. 如果 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 HeroNode(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 HeroNode getLeft() {
            return left;
        }
        public void setLeft(HeroNode left) {
            this.left = left;
        }
        public HeroNode getRight() {
            return right;
        }
        public void setRight(HeroNode right) {
            this.right = right;
        }
        @Override
        public String toString() {
            return "HeroNode [no=" + no + ", name=" + name + "]";
        }
        //递归删除结点
        //1.如果删除的节点是叶子节点,则删除该节点
        //2.如果删除的节点是非叶子节点,则删除该子树
        public void delNode(int no) {
    //思路
    /*
    * 1. 因为我们的二叉树是单向的,所以我们是判断当前结点的子结点是否需要删除结点,而不能去判断
    当前这个结点是不是需要删除结点. 2. 如果当前结点的左子结点不为空,并且左子结点 就是要删除结点,就将 this.left = null; 并且就返回
    (结束递归删除)
    3. 如果当前结点的右子结点不为空,并且右子结点 就是要删除结点,就将 this.right= null ;并且就返回
    (结束递归删除)
    4. 如果第 2 和第 3 步没有删除结点,那么我们就需要向左子树进行递归删除
    5. 如果第 4 步也没有删除结点,则应当向右子树进行递归删除. */
    //2. 如果当前结点的左子结点不为空,并且左子结点 就是要删除结点,就将 this.left = null; 并且就返回(结束递归删除)
            if(this.left != null && this.left.no == no) {
                this.left = null;
                return;
            }
            //3.如果当前结点的右子结点不为空,并且右子结点 就是要删除结点,就将 this.right= null ;并且就返回(结束递归删除)
            if(this.right != null && this.right.no == no) {
                this.right = null;
                return;
            }
            //4.我们就需要向左子树进行递归删除
            if(this.left != null) {
                this.left.delNode(no);
            }
            //5.则应当向右子树进行递归删除
            if(this.right != null) {
                this.right.delNode(no);
            }
        }
        //编写前序遍历的方法
        public void preOrder() {
            System.out.println(this); //先输出父结点
            //递归向左子树前序遍历
            if(this.left != null) {
                this.left.preOrder();
            }
            //递归向右子树前序遍历
            if(this.right != null) {
                this.right.preOrder();
            }
        }
        //中序遍历
        public void infixOrder() {
            //递归向左子树中序遍历
            if(this.left != null) {
                this.left.infixOrder();
            }
            //输出父结点
            System.out.println(this);
            //递归向右子树中序遍历
            if(this.right != null) {
                this.right.infixOrder();
            }
        }
        //后序遍历
        public void postOrder() {
            if(this.left != null) {
                this.left.postOrder();
            }
            if(this.right != null) {
                this.right.postOrder();
            }
            System.out.println(this);
        }
        //前序遍历查找
        /**
         *
         * @param no 查找 no
         * @return 如果找到就返回该 Node ,如果没有找到返回 null */
        public HeroNode preOrderSearch(int no) {
            System.out.println("进入前序遍历");
            //比较当前结点是不是
            if(this.no == no) {
                return this;
            }
            //1.则判断当前结点的左子节点是否为空,如果不为空,则递归前序查找
            //2.如果左递归前序查找,找到结点,则返回
            HeroNode resNode = null;
            if(this.left != null) {
                resNode = this.left.preOrderSearch(no);
            }
            if(resNode != null) {//说明我们左子树找到
                return resNode;
            }
            //1.左递归前序查找,找到结点,则返回,否继续判断,
            //2.当前的结点的右子节点是否为空,如果不空,则继续向右递归前序查找
            if(this.right != null) {
                resNode = this.right.preOrderSearch(no);
            }
            return resNode;
        }
        //中序遍历查找
        public HeroNode infixOrderSearch(int no) {
            //判断当前结点的左子节点是否为空,如果不为空,则递归中序查找
            HeroNode resNode = null;
            if(this.left != null) {
                resNode = this.left.infixOrderSearch(no);
            }
            if(resNode != null) {
                return resNode;
            }
            System.out.println("进入中序查找");
            //如果找到,则返回,如果没有找到,就和当前结点比较,如果是则返回当前结点
            if(this.no == no) {
                return this;
            }
            //否则继续进行右递归的中序查找
            if(this.right != null) {
                resNode = this.right.infixOrderSearch(no);
            }
            return resNode;
        }
        //后序遍历查找
        public HeroNode postOrderSearch(int no) {
            //判断当前结点的左子节点是否为空,如果不为空,则递归后序查找
            HeroNode 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;
            }
            System.out.println("进入后序查找");
            //如果左右子树都没有找到,就比较当前结点是不是
            if(this.no == no) {
                return this;
            }
            return resNode;
        }
    }

3.线索化二叉树的遍历:

1) 说明:对前面的中序线索化的二叉树, 进行遍历

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

代码实现

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

五,赫夫曼树

1.赫夫曼树的基本介绍

1) 给定 n 个权值作为 n 个叶子结点,构造一棵二叉树,若该树的带权路径长度(wpl)达到最小,称这样的二叉树为 最优二叉树,也称为哈夫曼树(Huffman Tree), 还有的书翻译为霍夫曼树。

2) 赫夫曼树是带权路径长度最短的树,权值较大的结点离根较近

▲赫夫曼树的几个概念和举例说明

1) 路径和路径长度:在一棵树中,从一个结点往下可以达到的孩子或孙子结点之间的通路,称为路径通路中分支的数目称为路径长度。若规定根结点的层数为 1,则从根结点到第 L 层结点的路径长度为 L-1

2) 结点的权及带权路径长度:若将树中结点赋给一个有着某种含义的数值,则这个数值称为该结点的权。结 点的带权路径长度为:从根结点到该结点之间的路径长度与该结点的权的乘积

3) 树的带权路径长度:树的带权路径长度规定为所有叶子结点的带权路径长度之和,记为 WPL(weighted path length) ,权值越大的结点离根结点越近的二叉树才是最优二叉树。

4) WPL 最小的就是赫夫曼树。

2.赫夫曼树创建思路图解

给你一个数列 {13, 7, 8, 3, 29, 6, 1},要求转成一颗赫夫曼树

▲构成赫夫曼树的步骤:

1) 从小到大进行排序, 将每一个数据,每个数据都是一个节点 , 每个节点可以看成是一颗最简单的二叉树

2) 取出根节点权值最小的两颗二叉树

3) 组成一颗新的二叉树, 该新的二叉树的根节点的权值是前面两颗二叉树根节点权值的和

4) 再将这颗新的二叉树,以根节点的权值大小 再次排序, 不断重复 1-2-3-4 的步骤,直到数列中,所有的数 据都被处理,就得到一颗赫夫曼树

‘5) 图解: 

 3.赫夫曼树的代码实现

    public class HuffmanTree {
        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) {
            // 第一步为了操作方便
            // 1. 遍历 arr 数组
            // 2. 将 arr 的每个元素构成成一个 Node
            // 3. 将 Node 放入到 ArrayList 中
            List<Node> nodes = new ArrayList<Node>();
            for (int value : arr) {
                nodes.add(new Node(value));
            }
            //我们处理的过程是一个循环的过程
            while (nodes.size() > 1) {
                //排序 从小到大
                Collections.sort(nodes);
                System.out.println("nodes =" + 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 集合排序
    // 让 Node 实现 Comparable 接口
    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) {
        // TODO Auto-generated method stub
        // 表示从小到大排序
            return this.value - o.value;
        }
    }

六,二叉排序树

1.二叉排序树的介绍

二叉排序树:BST: (Binary Sort(Search) Tree), 对于二叉排序树的任何一个非叶子节点,要求左子节点的值比当 前节点的值小,右子节点的值比当前节点的值大。

特别说明:如果有相同的值,可以将该节点放在左子节点或右子节点

比如针对前面的数据 (7, 3, 10, 12, 5, 1, 9) ,对应的二叉排序树为:

2.二叉排序树的创建与遍历

        一个数组创建成对应的二叉排序树,并使用中序遍历二叉排序树,比如: 数组为 Array(7, 3, 10, 12, 5, 1, 9) , 创 建成对应的二叉排序树为 : 

 3.二叉排序树的删除:

二叉排序树的删除情况比较复杂,有下面三种情况需要考虑

1) 删除叶子节点 (比如:2, 5, 9, 12)

2) 删除只有一颗子树的节点 (比如:1)

3) 删除有两颗子树的节点. (比如:7, 3,10 )

▲思路分析

//对删除结点的各种情况的思路分析:

第一种情况: 删除叶子节点 (比如:2, 5, 9, 12)

思路

(1) 需求先去找到要删除的结点 targetNode

(2) 找到 targetNode 的 父结点 parent

(3) 确定 targetNode 是 parent 的左子结点 还是右子结点

(4) 根据前面的情况来对应删除 左子结点 parent.left = null 右子结点 parent.right = null;

第二种情况: 删除只有一颗子树的节点 比如 1

思路

(1) 需求先去找到要删除的结点 targetNode

(2) 找到 targetNode 的 父结点 parent

(3) 确定 targetNode 的子结点是左子结点还是右子结点

(4) targetNode 是 parent 的左子结点还是右子结点

(5) 如果 targetNode 有左子结点

5. 1 如果 targetNode 是 parent 

parent.left = targetNode.left;

5.2 如果 targetNode 是 parent 的右子结点 parent.right = targetNode.left;

(6) 如果 targetNode 有右子结点

6.1 如果 targetNode 是 parent 的左子结点 parent.left = targetNode.right;

6.2 如果 targetNode 是 parent 的右子结点 parent.right = targetNode.right

情况三 : 删除有两颗子树的节点. (比如:7, 3,10 )

思路

(1) 需求先去找到要删除的结点 targetNode

(2) 找到 targetNode 的 父结点 parent

(3) 从 targetNode 的右子树找到最小的结点

(4) 用一个临时变量,将 最小结点的值保存 temp = 11

(5) 删除该最小结点

(6) targetNode.value = temp

具体代码实现: 

    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 Node(arr[i]));
            }
            //中序遍历二叉排序树
            System.out.println("中序遍历二叉排序树~");
            binarySortTree.infixOrder(); // 1, 3, 5, 7, 9, 10, 12
            //测试一下删除叶子结点
            binarySortTree.delNode(12);
            binarySortTree.delNode(5);
            binarySortTree.delNode(10);
            binarySortTree.delNode(2);
            binarySortTree.delNode(3);
            binarySortTree.delNode(9);
            binarySortTree.delNode(1);
            binarySortTree.delNode(7);
            System.out.println("root=" + binarySortTree.getRoot());
            System.out.println("删除结点后");
            binarySortTree.infixOrder();
        }
    }
    //创建二叉排序树
    class BinarySortTree {
        private Node root;
        public Node getRoot() {
            return root;
        }
        //查找要删除的结点
        public Node search(int value){
            if(root == null) {
                return null;
            } else {
                return root.search(value);
            }
        }
        //查找父结点
        public Node searchParent(int value) {
            if(root == null) {
                return null;
            } else {
                return root.searchParent(value);
            }
        }
        //编写方法:
        //1. 返回的 以 node 为根结点的二叉排序树的最小结点的值
        //2. 删除 node 为根结点的二叉排序树的最小结点
        /**
         *
         * @param node 传入的结点(当做二叉排序树的根结点) * @return 返回的 以 node 为根结点的二叉排序树的最小结点的值
         */
        public int delRightTreeMin(Node node) {
            Node 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 {
                //1.需求先去找到要删除的结点 targetNode
                Node targetNode = search(value);
                //如果没有找到要删除的结点
                if(targetNode == null) {
                    return;
                }
                //如果我们发现当前这颗二叉排序树只有一个结点
                if(root.left == null && root.right == null) {
                    root = null;
                    return;
                }
                //去找到 targetNode 的父结点
                Node 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) {//是由子结点
                        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(Node node) {
            if(root == null) {
                root = node;//如果 root 为空则直接让 root指向node
            } else {
                root.add(node);
            }
        }
        //中序遍历
        public void infixOrder() {
            if(root != null) {
                root.infixOrder();
            } else {
                System.out.println("二叉排序树为空,不能遍历");
            }
        }
    }
    //创建 Node 结点
    class Node {
        int value;
        Node left;
        Node right;
        public Node(int value) {
            this.value = value;
        }
        //查找要删除的结点
        /**
         *
         * @param value 希望删除的结点的值
         * @return 如果找到返回该结点,否则返回 null */
        public Node 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);
            }
        }
//查找要删除结点的父结点
/**
 *
 * @param value 要找到的结点的值
* @return 返回的是要删除的结点的父结点,如果没有就返回 null
*/
        public Node 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; // 没有找到父结点
                }
            }
        }
        @Override
        public String toString() {
            return "Node [value=" + value + "]";
        }
        //添加结点的方法
        //递归的形式添加结点,注意需要满足二叉排序树的要求
        public void add(Node node) {
            if(node == null) {
                return;
            }
            //判断传入的结点的值,和当前子树的根结点的值关系
            if(node.value < this.value) {
            //如果当前结点左子结点为 null
                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);
                }
            }
        }
        //中序遍历
        public void infixOrder() {
            if(this.left != null) {
                this.left.infixOrder();
            }
            System.out.println(this);
            if(this.right != null) {
                this.right.infixOrder();
            }
        }
    }

七,平衡二叉树(AVL树)

1.平衡二叉树的基本介绍

案例说明:

给你一个数列{1,2,3,4,5,6},要求创建一颗二叉排序树(BST), 并分析问题所在.

▲ 左边 BST 存在的问题分析:

1) 左子树全部为空,从形式上看,更像一个单链表.

2) 插入速度没有影响

3) 查询速度明显降低(因为需要依次比较), 不能发挥 BST的优势,因为每次还需要比较左子树,其查询速度比 单链表还慢

4) 解决方案-平衡二叉树(AVL)

基本介绍:

1) 平衡二叉树也叫平衡二叉搜索树(Self-balancing binary search tree)又被称为 AVL 树, 可以保证查询效率较高。

2) 具有以下特点:它是一 棵空树或它的左右两个子树的高度差的绝对值不超过 1,并且左右两个子树都是一棵 平衡二叉树。平衡二叉树的常用实现方法有红黑树、AVL、替罪羊树、Treap、伸展树等。

3) 举例说明, 看看下面哪些 AVL 树, 为什么?

 2.平衡二叉树的左旋转:

1) 要求: 给你一个数列,创建出对应的平衡二叉树.数列 {4,3,6,5,7,8}

2) 思路分析(示意图)

具体代码实现:

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

3.平衡二叉树的右旋转:

1) 要求: 给你一个数列,创建出对应的平衡二叉树.数列 {10,12, 8, 9, 7, 6}

2) 思路分析(示意图)

 具体代码实现:

//右旋转
private void rightRotate() {
    Node newNode = new Node(value);
    newNode.right = right;
    newNode.left = left.right;
    value = left.value;
    left = left.left;
    right = newNode;
}

 4.平衡二叉树的双旋转:

前面的两个数列,进行单旋转(即一次旋转)就可以将非平衡二叉树转成平衡二叉树,但是在某些情况下,单旋转 不能完成平衡二叉树的转换。

比如数列

int[] arr = { 10, 11, 7, 6, 8, 9 }; 运行原来的代码可以看到,并没有转成 AVL 树.

int[] arr = {2,1,6,5,7,3}; // 运行原来的代码可以看到,并没有转成 AVL 树

1) 问题分析

2) 解决思路分析

1. 当符号右旋转的条件时

2. 如果它的左子树的右子树高度大于它的左子树的高度 

3. 先对当前这个结点的左节点进行左旋转

4. 在对当前结点进行右旋转的操作即可

3) 代码实现[AVL 树的汇总代码(完整代码)

    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 avlTree = new AVLTree();
            //添加结点
            for(int i=0; i < arr.length; i++) {
                avlTree.add(new Node(arr[i]));
            }
            //遍历
            System.out.println("中序遍历");
            avlTree.infixOrder();
            System.out.println("在平衡处理~~");
            System.out.println("树的高度=" + avlTree.getRoot().height()); //3
            System.out.println("树的左子树高度=" + avlTree.getRoot().leftHeight()); // 2
            System.out.println("树的右子树高度=" + avlTree.getRoot().rightHeight()); // 2
            System.out.println("当前的根结点=" + avlTree.getRoot());//8
        }
    }
    // 创建 AVLTree
    class AVLTree {
        private Node root;
        public Node getRoot() {
            return root;
        }
        // 查找要删除的结点
        public Node search(int value) {
            if (root == null) {
                return null;
            } else {
                return root.search(value);
            }
        }
        // 查找父结点
        public Node searchParent(int value) {
            if (root == null) {
                return null;
            } else {
                return root.searchParent(value);
            }
        }
        // 编写方法:
        // 1. 返回的 以 node 为根结点的二叉排序树的最小结点的值
        // 2. 删除 node 为根结点的二叉排序树的最小结点
        /**
         *
         * @param node * 传入的结点(当做二叉排序树的根结点) * @return 返回的 以 node 为根结点的二叉排序树的最小结点的值
         */
        public int delRightTreeMin(Node node) {
            Node 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 {
                // 1.需求先去找到要删除的结点 targetNode
                Node targetNode = search(value);
                // 如果没有找到要删除的结点
                if (targetNode == null) {
                    return;
                }
                // 如果我们发现当前这颗二叉排序树只有一个结点
                if (root.left == null && root.right == null) {
                    root = null;
                    return;
                }
                // 去找到 targetNode 的父结点
                Node 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){//是右子结点
                        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(Node node) {
            if (root == null) {
                root = node;// 如果 root 为空则直接让 root 指向 node
            } else {
                root.add(node);
            }
        }
        // 中序遍历
        public void infixOrder() {
            if (root != null) {
                root.infixOrder();
            } else {
                System.out.println("二叉排序树为空,不能遍历");
            }
        }
    }
    // 创建 Node 结点
    class Node {
        int value;
        Node left;
        Node right;
        public Node(int value) {
            this.value = value;
        }
        // 返回左子树的高度
        public int leftHeight() {
            if (left == null) {
                return 0;
            }
            return left.height();
        }
        // 返回右子树的高度
        public int rightHeight() {
            if(right == null){
                return 0;
            }
            return right.height();
        }
        // 返回 以该结点为根结点的树的高度
        public int height() {
            return Math.max(left == null ? 0 : left.height(), right == null ? 0 : right.height()) + 1;
        }
        //左旋转方法
        private void leftRotate() {
            //创建新的结点,以当前根结点的值
            Node newNode = new Node(value);
            //把新的结点的左子树设置成当前结点的左子树
            newNode.left = left;
            //把新的结点的右子树设置成带你过去结点的右子树的左子树
            newNode.right = right.left;
            //把当前结点的值替换成右子结点的值
            value = right.value;
            //把当前结点的右子树设置成当前结点右子树的右子树
            right = right.right;
            //把当前结点的左子树(左子结点)设置成新的结点
            left = newNode;
        }
        //右旋转
        private void rightRotate() {
            Node newNode = new Node(value);
            newNode.right = right;
            newNode.left = left.right;
            value = left.value;
            left = left.left;
            right = newNode;
        }
        // 查找要删除的结点
        /**
         *
         * @param value * 希望删除的结点的值
         * @return 如果找到返回该结点,否则返回 null */
        public Node 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);
            }
        }
        // 查找要删除结点的父结点
        /**
         *
         * @param value * 要找到的结点的值
         * @return 返回的是要删除的结点的父结点,如果没有就返回 null */
        public Node 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; // 没有找到父结点
                }
            }
        }
        @Override
        public String toString() {
            return "Node [value=" + value + "]";
        }
        // 添加结点的方法
        // 递归的形式添加结点,注意需要满足二叉排序树的要求
        public void add(Node node) {
            if (node == null) {
                return;
            }
            // 判断传入的结点的值,和当前子树的根结点的值关系
            if (node.value < this.value) {
            // 如果当前结点左子结点为 null
                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();
                }
            }
        }
        // 中序遍历
        public void infixOrder() {
            if (this.left != null) {
                this.left.infixOrder();
            }
            System.out.println(this);
            if (this.right != null) {
                this.right.infixOrder();
            }
        }
    }

八,多路查找树

1.二叉树与B树

a)二叉树的问题分析

二叉树的操作效率较高,但是也存在问题, 请看下面的二叉树

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

2) 问题 1:在构建二叉树时,需要多次进行 i/o 操作(海量数据存在数据库或文件中),节点海量,构建二叉树时, 速度有影响

3) 问题 2:节点海量,也会造成二叉树的高度很大,会降低操作速度.

b)多叉树

1) 在二叉树中,每个节点有数据项,最多有两个子节点。如果允许每个节点可以有更多的数据项和更多的子节点, 就是多叉树(multiway tree)

2) 后面我们讲解的 2-3 树,2-3-4 树就是多叉树,多叉树通过重新组织节点,减少树的高度,能对二叉树进行优化。

3) 举例说明(下面 2-3 树就是一颗多叉树)

 2.B树的基本介绍:

B 树通过重新组织节点,降低树的高度,并且减少 i/o 读写次数来提升效率。

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

2) 文件系统及数据库系统的设计者利用了磁盘预读原理,将一个节点的大小设为等于一个页(页得大小通常为 4k), 这样每个节点只需要一次 I/O 就可以完全载入

3) 将树的度 M 设置为 1024,在 600 亿个元素中最多只需要 4 次 I/O 操作就可以读取到想要的元素, B 树(B+)广泛 应用于文件存储系统以及数据库系统中

 2-3树举例:

2-3树是最简单的B树,具有以下的特点:

1) 2-3 树的所有叶子节点都在同一层.(只要是 B 树都满足这个条件)

2) 有两个子节点的节点叫二节点,二节点要么没有子节点,要么有两个子节点.

3) 有三个子节点的节点叫三节点,三节点要么没有子节点,要么有三个子节点.

4) 2-3 树是由二节点和三节点构成的树。

2-3树的应用举例:

将数列{16, 24, 12, 32, 14, 26, 34, 10, 8, 28, 38, 20} 构建成 2-3 树,并保证数据插入的大小顺序。(演示一下构建 2-3 树的过程.)

 插入规则:

1) 2-3 树的所有叶子节点都在同一层.(只要是 B 树都满足这个条件)

2) 有两个子节点的节点叫二节点,二节点要么没有子节点,要么有两个子节点.

3) 有三个子节点的节点叫三节点,三节点要么没有子节点,要么有三个子节点

4) 当按照规则插入一个数到某个节点时,不能满足上面三个要求,就需要拆,先向上拆,如果上层满,则拆本层, 拆后仍然需要满足上面 3 个条件。

5) 对于三节点的子树的值大小仍然遵守(BST 二叉排序树)的规则

3.B树,B+树和B*树:

a)B树的介绍:

B-tree 树即 B 树,B 即 Balanced,平衡的意思。有人把 B-tree 翻译成 B-树,容易让人产生误解。会以为 B-树 是一种树,而B树又是另外一种树。实际上B-tree指的就是B树、

▲前面已经介绍了 2-3 树和 2-3-4 树,他们就是 B 树(英语:B-tree 也写成 B-树),这里我们再做一个说明,我们在学 习 Mysql 时,经常听到说某种类型的索引是基于 B 树或者 B+树的,如图

对上图的说明:

1) B 树的阶:节点的最多子节点个数。比如 2-3 树的阶是 3,2-3-4 树的阶是4

2) B-树的搜索,从根结点开始,对结点内的关键字(有序)序列进行二分查找,如果命中则结束,否则进入查询 关键字所属范围的儿子结点;重复,直到所对应的儿子指针为空,或已经是叶子结点

3) 关键字集合分布在整颗树中, 即叶子节点和非叶子节点都存放数据.

4) 搜索有可能在非叶子结点结束

5) 其搜索性能等价于在关键字全集内做一次二分查找

b)B+树的基本介绍:

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

对上图的说明:

1) B+树的搜索与 B 树也基本相同,区别是 B+树只有达到叶子结点才命中(B 树可以在非叶子结点命中),其性 能也等价于在关键字全集做一次二分查找

2) 所有关键字都出现在叶子结点的链表中(即数据只能在叶子节点【也叫稠密索引】),且链表中的关键字(数据) 恰好是有序的。

3) 不可能在非叶子结点命中

4) 非叶子结点相当于是叶子结点的索引(稀疏索引),叶子结点相当于是存储(关键字)数据的数据层 

5) 更适合文件索引系统

6) B 树和 B+树各有自己的应用场景,不能说 B+树完全比 B 树好,反之亦然.

c)B*树的基本介绍

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

▲B*树的说明:

1) B*树定义了非叶子结点关键字个数至少为(2/3)*M,即块的最低使用率为 2/3,而 B+树的块的最低使用率为的 1/2。

2) 从第 1 个特点我们可以看出,B*树分配新结点的概率比 B+树要低,空间使用率更高 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值