1、基本介绍

  • 数组存储方式分析
    • 优点:通过下标方式访问元素,速度快。对于有序数组,还可以使用二分查找提高检索速度
    • 缺点:如果要检索具体某个值,或者插入值(按一定顺序)会整体移动值,效率较低
  • 链式存储方式分析
    • 优点:在一定程度上对数组存储方式有优化(如:插入一个数值节点,只需要将插入节点,链接到链表中即可,删除效率也很好)
    • 缺点:在进行检索时,效率仍然较低(需从头到尾开始遍历)
  • 树存储方式分析
    • 能提高数据存储,读取的效率,比如利用二叉排序树(Binary Sort Tree),既可以保证数据的检索速度,同时也可以保证数据的插入,删除,修改的速度

image.png

  • 节点
  • 根节点
  • 父节点
  • 子节点
  • 叶子节点(没有子节点的节点)
  • 节点的权(节点值)
  • 路径(从root节点找到该节点的路线)
  • 子树
  • 树的高度(最大的层数)
  • 森林:多颗子树构成森林

2、二叉树

2.1、基本介绍

  • 每个节点最多只能有两个子节点
  • 二叉树子节点分为左节点和右节点
  • 如果该二叉树的所有叶子节点都在最后一层,并且节点总数为 2^n-1(n为层数),称为满二叉树
  • 如果该二叉树的所欲叶子节点都在最后一层或者倒数第二层,而且最后一层的叶子节点在左边连续,倒数第二层的叶子节点在右边连续,我们称为完全二叉树

image.png

2.2、二叉树遍历

  • 前序遍历:先输出父节点,再遍历左子树和右子树
  • 中序遍历:先遍历左子树,在输出父节点,再遍历右子树
  • 后序遍历:先遍历左子树,再遍历右子树,最后输出父节点
package tree;

//遍历二叉树
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, "林冲");

        //手动创建二叉树

        root.left = node2;
        root.right= node3;
        node3.right = node4;

        binaryTree.root = root;

        

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

//定义BinaryTree 二叉树
class BinaryTree{
    public HeroNode root;


    //前序遍历
    public void preOrder(){
        if (this.root != null){
            this.root.preOrder();
        }
    }

    //中序遍历
    public void infixOrder(){
        if (this.root != null){
            this.root.infixOrder();
        }
    }

    //后序遍历
    public void postOrder(){
        if (this.root != null){
            this.root.postOrder();
        }
    }
}

//创建节点
class HeroNode{
    public int no;
    public String name;
    public HeroNode left;
    public HeroNode right;

    public HeroNode(int no,String name){
        this.no = no;
        this.name = name;
    }

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

    //前序遍历
    public void preOrder(){
        System.out.println(this.toString()); // 先输出父节点
        //递归向左子树前序遍历
        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.toString());
        //递归向右子树中序遍历
        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.toString());
    }


}

2.3、二叉树查找

  • 前序查找
    • 先判断当前节点的no是否等于要查找的
    • 如果相等,则返回当前节点
    • 如果不等,则判断当前节点左子节点是否为空,如果不为空,则递归前序查找
    • 如果左递归查找,找到节点,则返回,如果没有找到,则判断当前节点的右子节点是否为空,如果不为空,则继续向右递归前序查找
    • 如果找到就返回,否则返回null
  • 中序查找
    • 判断当前节点的左子节点是否为空,如果不为空,则递归中序查找
    • 如果找到,则返回,如果没有找到,就和当前节点比较,如果是则返回,如果不是则进行右递归中序查找
    • 如果右递归中序查找找到则返回,否则返回null
  • 后序查找
    • 判断当前节点的左子节点是否为空,如果不为空,则递归后序查找
    • 如果找到,则返回,如果没有找到,则判断当前节点右子节点是否为空,如果不为空,则右递归后序查找
    • 如果找到,就返回,如果没有找到,就和当前节点比较,如果是就返回,否则返回null
package tree;

//遍历二叉树
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, "林冲");

        //手动创建二叉树

        root.left = node2;
        root.right= node3;
        node3.right = node4;

        binaryTree.root = root;



        //测试

        //System.out.println("前序遍历查找");
        //HeroNode node  = binaryTree.preOrderSearch(3);

        //System.out.println("后序查找");
        //HeroNode node = binaryTree.postOrderSearch(3);

        System.out.println("中序查找");
        HeroNode node = binaryTree.infixOrderSearch(3);

        if (node != null){
            System.out.println(node.toString());
        }else{
            System.out.println("没有找到");
        }
    }
}

//定义BinaryTree 二叉树
class BinaryTree{
    public HeroNode root;

    //前序遍历查找
    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 root.postOrderSearch(no);
        }else {
            return  null;
        }
    }
}

//创建节点
class HeroNode{
    public int no;
    public String name;
    public HeroNode left;
    public HeroNode right;

    public HeroNode(int no,String name){
        this.no = no;
        this.name = name;
    }

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


    //前序遍历查找
    /***
     *
     * @param no 查找节点的id
     * @return 如果找到返回该节点,如果没有找到返回null
     */
    public HeroNode preOrderSearch(int no){
        //比较当前节点,如果是则返回
        if (this.no == no){
            return this;
        }

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

        //如果找到则返回,如果没有找到则返回null
        return resNode;
    }

    //中序遍历查找
    public HeroNode infixOrderSearch(int no){
        //判断当前节点的左子节点是否为空,如果不为空,则递归中序查找
        HeroNode 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);
        }

        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;
        }
        if (this.no == no){
            return this;
        }
        return null;
    }
}

2.4、删除二叉树节点

  • 规定:
    • 如果删除的节点是叶子节点,则删除该节点
    • 如果删除的节点是非叶子节点,则删除该子树
    //递归删除节点
    //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.left != null){
            this.right.delNode(no);
        }

2.5、顺序存储二叉树

  • 基本说明
    • 从数据存储来看,数组存储方式和树的存储方式可以相互转换的,即数组可以转换成树,树也可以转换为数组
  • 要求
    • 下图的二叉树的结点,要求以数组的方式来存放
    • 要求在遍历数组arr时,仍然可以以前序遍历,中序遍历和后序遍历的方式完成结点的遍历

image.png

  • 顺序存储二叉树的特点
    • 顺序二叉树通常只考虑完全二叉树
    • 第n个元素的左子结点为 2 * n + 1
    • 第n个元素的右子节点为 2 * n + 2
    • 第n个元素的父节点为 (n-1) / 2
//顺序存储二叉树
public class ArrayBinaryTreeDemo {
    public static void main(String[] args) {
        int arr[] = {1,2,3,4,5,6,7};

        ArrayBinaryTree arrayBinaryTree = new ArrayBinaryTree(arr);

        arrayBinaryTree.preOrder();
    }
}

//编写ArrayBinaryTree,实现顺序存储二叉树
class ArrayBinaryTree{
    public int[] arr;  //存储二叉树节点的数组

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

    //顺序二叉树的前序遍历

    //重载,默认从0开始
    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(index * 2 +1);
        }

        //向右递归遍历
        if (index * 2 + 2 < arr.length){
            preOrder(index * 2 + 2);
        }
    }
}

2.6、线索化二叉树

  • 将数列{1,3,6,8,10,14}构建成一颗二叉树

image.png

  • 问题分析
    • 当我们对上面的二叉树进行中序遍历时,数列为
    • 但是6,8,10,14这几个节点的左右指针,并没有完全利用上
    • 希望充分的利用各个节点的左右指针,让各个节点可以指向自己的前后节点

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

image.png

  • 说明:当线索化二叉树后,Node结点的属性left和right,有如下情况
    • left指向的是左子树,也可能是指向的前驱结点,比如①结点的left指向的左子树,而⑩结点的left指向的是前驱结点
    • right指向的是右子树,也可能是指向的后继结点,比如①结点right指向的是右子树,而⑩结点的right指向的是后继节点

  • 遍历线索化二叉树
    • 对前面的中序线索的二叉树,进行遍历
    • 因为线索化后,各个结点指向有变化,因此原来的遍历方法不能使用,这时需要使用新的方式遍历线索化二叉树,各个结点可以通过线性方式遍历,因此无需使用递归的方法,这样也提高了遍历效率。遍历的次序应当和中序遍历保持一致
//线索二叉树
public class ThreadedBinaryTreeDemo {
    public static void main(String[] args) {
        //测试,中序线索二叉树功能
        Node node01 = new Node(1, "name01");
        Node node02 = new Node(3, "name02");
        Node node03 = new Node(6, "name03");
        Node node04 = new Node(8, "name04");
        Node node05 = new Node(10, "name05");
        Node node06 = new Node(14, "name05");

        node01.left = node02;
        node01.right = node03;
        node02.left = node04;
        node02.right = node05;
        node03.left = node06;

        ThreadedBinaryTree threadedBinaryTree = new ThreadedBinaryTree();
        threadedBinaryTree.root = node01;

        //线索化
        threadedBinaryTree.threadedNodes(node01);

        //以 10 节点做测试
        System.out.println("10号节点的前驱节点:"+node05.left.no);
        System.out.println("10号节点的后驱节点:"+node05.right.no);

        //遍历中序线索化的二叉树
        threadedBinaryTree.threadedList();
    }
}

//实现线索化功能的二叉树
class ThreadedBinaryTree{
    public Node root;

    //为了实现线索化,需要创建指向当前节点的前驱的指针
    public Node pre = null;

    //编写对二叉树进行中序线索化的方法
    /***
     *
     * @param node 当前需要线索化的节点
     */
    public void threadedNodes(Node node){

        //如果node==null 无法线索化直接退出
        if (node == null){
            return;
        }

        //1.先线索化左子树
        threadedNodes(node.left);
        //2.线索化当前节点
        //处理当前节点的前驱节点
        if (node.left == null){
            //让当前节点的左指针,指向前驱节点
            node.left = pre;
            node.leftType = 1;
        }
        //处理当前节点的后驱节点
        if (pre != null && pre.right == null){
            //让前驱节点的右指针指向当前节点
            pre.right = node;
            pre.rightType = 1;
        }

        //每处理一个结点后,让当前节点是下一个节点的前驱节点
        pre = node;

        //3.线索化右子树
        threadedNodes(node.right);

    }

    //遍历中序线索化二叉树
    public void threadedList(){
        //定义一个变量,存储当前遍历的节点
        Node node = root;
        while (node != null){
            //循环找到leftType == 1 的节点,第一个找到的就是 8 号节点
            //后面随着遍历而变化,因为当leftType == 1 时,说明该结点按照线索化处理后的有效结点
            while (node.leftType == 0){
                node = node.left;
            }

            //打印当前节点
            System.out.println(node);

            //如果当前节点的右指针指向的是后继节点,就一直输出
            while (node.rightType == 1){
                //获取当前节点的后继节点
                node = node.right;
                System.out.println(node);
            }

            // 替换这个遍历的节点
            node = node.right;
        }

    }
}

class Node{
    public int no;
    public String name;
    public Node left;
    public Node right;

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

    public Node(int no,String name){
        this.no = no;
        this.name = name;
    }

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

3、赫夫曼树

  • 给定n个权值作为n个叶子节点,构造一棵二叉树,若该树的带权路径长度(wpl)达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree)
  • 赫夫曼树是带权路径长度最短的树,权值较大的结点离根较近

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

  • 赫夫曼树创建思路
    1. 从小打到进行排序,将每一个数据,每个数据都是一个结点,每个结点可以看成一棵最简单的二叉树
    2. 取出根结点权值最小的两棵二叉树
    3. 组成一棵新的二叉树,该新的二叉树的根结点的权值是前面两棵二叉树根结点权值的和
    4. 再将这颗新的二叉树,以根结点的权值大小再次排序,不断重复1-2-3-4的步骤,直到数列中,所有的数据都被处理,就得到一棵赫夫曼树
public class HuffmanTree {
    public static void main(String[] args) {
        int arr[] = {13,7,8,3,29,6,1};
        Node rootNode = createHuffmanTree(arr);

        //前序遍历赫夫曼树
        rootNode.preOrder();
        
    }

    //创建赫夫曼树的方法
    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);

            //取出权值最小的两个二叉树
            //1.取出权值最小的结点(二叉树)
            Node leftNode = nodes.get(0);
            //2.取出权值第二小的结点(二叉树)
            Node rightNode = nodes.get(1);

            //构建一棵新的二叉树
            Node parent = new Node(leftNode.value + rightNode.value);
            parent.left = leftNode;
            parent.right = rightNode;

            //从list重删除处理过的结点(二叉树)
            nodes.remove(leftNode);
            nodes.remove(rightNode);

            //将parent加入到list
            nodes.add(parent);
        }

        //返回赫夫曼树根结点
        return nodes.get(0);
    }
}


//创建节点
class Node implements Comparable<Node>{
    int value;   //权值
    Node left;   //左子节点
    Node right;  //右子节点

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

    //前序遍历
    public void preOrder(){
        System.out.println(this);
        if (this.left != null){
            this.left.preOrder();
        }
        if (this.right != null){
            this.right.preOrder();
        }
    }
    @Override
    public String toString(){
        return "Node [value = "+ value + "]";
    }

    public int compareTo(Node o) {
        //从小到大
        return this.value - o.value;
    }
}

4、二叉排序树

  • 需求
  • 给你一个数列{7,3,10,12,5,1,9},要求能够高效的完成对数据的查询和添加

  • 使用数组
    • 数组未排序,优点:直接在数组尾添加,速度快,缺点:查找速度慢
    • 数组排序,优点:可以使用二分查找,查找速度快,缺点:为了保护数组有序,在添加新数据时,找到插入位置后,后面的数据需整体移动,速度慢
  • 使用链式存储-链表
    • 不管链表是否有序,查找速度都慢,添加数据速度比数组快,不需要数据整体移动

  • 二叉排序树

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

  • 删除叶子节点

    1. 找到需要删除的结点 targetNode
    2. 找到targetNode的父节点
    3. 根据targetNode是parent的左子节点还是右子节点对应删除
      • 左子节点:parent.left = null
      • 右子节点:parent.right = null
  • 删除只有一棵子树的结点

    1. 找到需要删除的结点 targetNode
    2. 找到targetNode的父节点parent
    3. 判断targetNode有左子节点还是右子节点
      1. 左子节点
        1. targetNode是parent的左子节点:parent.left = targetNode.left
        2. targetNode是parent的右子节点:parent.right = targetNode.left
      2. 右子节点
        1. targetNode是parent的左子节点:parent.left = targetNode.right
        2. targetNode是parent的右子节点:parent.right = targeNode.right
  • 删除有两个子树的结点

    1. 找到需要删除的结点 targetNode
    2. 找到targetNode的父节点parent
    3. 从targetNode的右子树找到最小的结点
    4. 用一个临时变量将最小结点的值保存 temp
    5. 删除该最小结点
    6. targetNode.value = temp
package binarySortTree;

//二叉排序树
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]));
        }

        //中序遍历二叉排序树
        binarySortTree.infixOrder();

        //删除结点
        binarySortTree.delNode(7);
        binarySortTree.infixOrder();

    }
}

//二叉排序树
class BinarySortTree{
    private Node root;
    //添加节点
    public void add(Node node){
        if (root == null){
            root = node;
        }else {
            root.add(node);
        }
    }

    //查找要删除的结点
    public Node searchNode(int value){
        if (root == null){
            return null;
        }

        return root.searchNode(value);
    }
    //查找要删除节点的父节点
    public Node searchParentNode(int value){
        if(root == null){
            return null;
        }
        return root.searchParentNode(value);
    }
    //删除节点
    public void delNode(int value){
        if (root == null){
            return;
        }else {
            //找到需要删除的结点
            Node targetNode = searchNode(value);
            //如果没有找到要删除的结点
            if (targetNode == null){
                return;
            }

            //如果当前这棵二叉排序树只有一个结点
            if (root.left == null && root.right == null){
                root = null;
                return;
            }
            //找到需要删除节点的父节点
            Node parentNode = searchParentNode(value);

            //删除的结点为叶子节点
            if (targetNode.left == null && targetNode.right == null){
                //判断targetNode是父节点的左子节点还是右子节点
                if (parentNode.left != null && parentNode.left.value == targetNode.value){
                    //如果targetNode为parentNode的左子节点
                    parentNode.left = null;
                }else if (parentNode.right != null && parentNode.right.value == targetNode.value){
                    //如果targetNode为parentNode的右子节点
                    parentNode.right = null;
                }
                //return;
            }else if (targetNode.left != null && targetNode.right != null){

            //删除的结点有两棵子树

                System.out.println("删除具有两棵子树的结点");
                //从targetNode的右子树找到最小的结点
                Node tempNode = targetNode.right;
                if (tempNode.left != null){
                    tempNode = tempNode.left;
                }
                delNode(tempNode.value);
                targetNode.value = tempNode.value;
                return;
            }else{
                //删除的的结点有一个子树
                //如果删除结点有一棵左子树
                if (targetNode.left != null){
                    if (parentNode != null){
                        //如果targetNode是parentNode的左子节点
                        if (parentNode.left.value == targetNode.value){
                            parentNode.left = targetNode.left;
                        }else {
                            //如果targetNode是parentNode的右子节点
                            parentNode.right = targetNode.left;
                        }
                    }else {
                        root = targetNode.left;
                    }
                }else {    //如果删除的结点有一棵右子树
                    if(parentNode != null) {
                        //如果 targetNode 是 parent 的左子结点
                        if(parentNode.left.value == value) {
                            parentNode.left = targetNode.right;
                        } else { //如果 targetNode 是 parent 的右子结点
                            parentNode.right = targetNode.right;
                        }
                    } else {
                        root = targetNode.right;
                    }
                }
            }
        }
    }



    //中序遍历排序二叉树
    public void infixOrder(){

        if (root != null){
            root.infixOrder();
        }else {
            System.out.println("二叉排序树为空,不能遍历");
        }
    }
}

//结点
class Node{
    int value;
    Node left;
    Node right;
    public Node(int value){
        this.value = value;
    }

    //添加节点
    public void add(Node 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);
            }
        }
    }

    //查找需要删除的结点
    public Node searchNode(int value){
        //找到需要删除的结点
        if (value == this.value){
            return this;
        }else if (value < this.value){
            //如果查找的值小于当前节点,向左子树递归查找
            if (this.left == null) {
                return null;
            }
            return this.left.searchNode(value);
        }else {
            //如果朝招的值大于当前节点,向右子树递归查找
            if (this.right == null){
                return null;
            }
            return this.right.searchNode(value);
        }
    }

    //查找要删除节点得父节点
    public Node searchParentNode(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.searchParentNode(value);
            }else if (value > this.value && this.right != null){
                //如果查找的值大于当前节点的值,并当前节点的右子树不为空
                //向右递归查找
                return this.right.searchParentNode(value);
            }else {
                return null;  //没有找到父节点
            }
        }
    }

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

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

5、平衡二叉树

  • 案例(说明二叉排序树可能出现的问题)
    • 数列 {1,2,3,4,5,6}构建排序二叉树时

image.png

  • 存在的问题
    • 左子树全部为空,从形式上看更像一个单链表
    • 插入速度没有影响
    • 查询速度明显降低(因为需要一次比较),不能发挥排序二叉树的优势,因为每次比较还需要比较左子树,其查询速度比单链表还要慢
    • 解决方案:平衡二叉树

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

  • 左旋转(右子树高度 - 左子树高度 > 1)
    • 创建一个新的结点,以当前节点的值创建
    • 把新结点的左子结点设置为当前节点的左子结点
    • 把新结点的右子结点设置为当前节点的右子结点的左子结点
    • 把当前节点的值换为右子节点的值
    • 把当前节点的右子结点设置成当前节点右子结点的右子结点
    • 把当前节点的左子结点设置为新结点
  • 右旋转(左子树高度 - 右子树高度 > 1)
    • 创建新的结点,以当前结点的值创建
    • 把新结点的右子节点设置为当前节点的右子节点
    • 把新结点的左子节点设置为当前节点的左子节点的右子节点
    • 把当当前节点的值设置为当前节点左子节点的值
    • 把当前节点的左子节点设置为当前节点的左子节点的左子节点
    • 把当前节点的右子节点设置为新结点
  • 双旋转
    • 当右子树的高度 - 左子树的高度 > 1
      • 如果当前节点的右子树的左子树的高度大于它的右子树的高度,以当前右子节点为根结点的树先右旋转,以当前结点根结点的数再左旋转
      • 否则直接将以当前节点为根结点的数左旋转
    • 当左子树的高度 - 右子树的高度 > 1
      • 如果当前节点的左子树的右子树的高度大于左子树的高度,当前节点的左子节点为根结点的树先左旋转,当前节点为根结点的树右旋转
      • 否则直接将以当前节点为根结点的树右旋转
package avl;

//平衡二叉树
public class AVLTreeDemo {
    public static void main(String[] args) {
        int arr[] = {10,12,8,9,7,6};
        //创建一个平衡二叉树
        AVLTree avlTree = new AVLTree();
        //添加结点
        for (int i =0;i<arr.length;i++){
            avlTree.add(new Node(arr[i]));
        }

        System.out.println("树的高度:"+avlTree.root.height());
        System.out.println("左子树的高度:"+avlTree.root.left.height());
        System.out.println("右子树的高度:"+avlTree.root.right.height());

    }
}

//创建AVL Tree
class AVLTree{
    public Node root;
    //添加节点
    public void add(Node node){
        if (root == null){
            root = node;
        }else {
            root.add(node);
        }
    }
}
//结点
class Node{
    int value;
    Node left;
   Node right;
    public Node(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(){
        //创建新的结点,以当前结点的值创建
        Node node = new Node(value);
        //把新的结点的左子树设置成当前节点的左子树
        node.left = left;
        //把新的结点的右子树设置成当前节点右子树的左子树
        node.right = this.right.left;
        //把当前节点的值替换成右子节点的值
        value = this.right.value;
        //把当前节点的右子树设置成当前节点右子树的右子树
        this.right = this.right.right;
        //把当前节点的左子节点设置成新的结点
        this.left = node;
    }

    //右旋转
    private void rightRotate(){
        //创建新的结点,以当前结点的值创建
        Node node = new Node(this.value);
        //把新结点的右子节点设置为当前节点的右子节点
        node.right = this.right;
        //把新结点的左子节点设置为当前节点的左子节点的右子节点
        node.left = this.left.right;
        //把当当前节点的值设置为当前节点左子节点的值
        this.value = this.left.value;
        //把当前节点的左子节点设置为当前节点的左子节点的左子节点
        this.left = this.left.left;
        //把当前节点的右子节点设置为新结点
        this.right = node;
    }


    //添加节点
    public void add(Node 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 (this.rightHeight() - this.leftHeight() > 1){

            //如果当前节点的右子树的左子树的高度大于它的右子树的高度
            if (this.right != null && this.right.leftHeight() > this.rightHeight()){
                //当前右子树先右旋转
                this.right.rightRotate();
                //当前节点左旋转
                this.leftRotate();
            }else {
                //左旋转
                leftRotate();
            }

            return;
        }

        //当添加一个结点后,如果左子树的高度 - 右子树的高度 > 1
        if (this.leftHeight() - this.rightHeight() > 1){
            //如果它的左子树的右子树高度大于它的左子树的高度
            if (this.left != null && this.rightHeight() > this.leftHeight()){
                //当前节点的左子树先左旋转
                this.left.leftRotate();
                //当前节点为根结点的树右旋转
                this.rightRotate();
            }else {
                //右旋转
                rightRotate();
            }
            return;
        }
    }
}

posted @ 2022-11-06 22:38  youmo~  阅读(62)  评论(0编辑  收藏  举报