(7)Java基础类库之比较器

比较器问题引出

​ 比较器指的是进行大小关系的确定判断。

java.util.Arrays提供了绝大部分的数组的操作支持,同时这个类中提供有一种对象数组的排序支持:

public static void sort(Object[] a)

任意一个类默认情况下是无法使用系统内部的类实现数组排序或者比较需求的,因为没有明确的指出到底该如何比较的定义,那么这个时候Java中为了同一比价规则的定义,所以提供有比较器的接口:Comparble.

Compare比较器

​ 想要实现对象的比较需要通过比较器来制定比较规则,比较规则通过Comparable来实现。

在这里插入图片描述

public interface Companable<T> {
/**
*实现对象的比较处理操作
*@param o要比较的对象
*@return当前数据比传入的对象小返回负数,如果大于返回整数,如果等于返回0
*/
public int compareTo(To) ;
}

自定义对象数组排序操作

import java.util.Arrays;

class Person implements Comparable<Person>{
    private String name;
    private int age;
    public Person(String name,int age){
        this.name=name;
        this.age=age;
    }

    @Override
    public int compareTo(Person per) {
        return this.age-per.age;
    }

    @Override
    public String toString() {
        return "【Person对象】姓名:"+this.name+"、年龄"+this.age;
    }
}
public class CompareDemo {
    public static void main(String[] args) {
        Person [] data=new Person[]{new Person("A",100),new Person("B",50),new Person("C",60)};
        Arrays.sort(data);
        System.out.println(Arrays.toString(data));
    }
}

Comparator比较器

Comparator比较器属于一种挽救的比较器支持,其主要的目的是解决一些没有使用Comparable排序类的数组排序。

实例:项目已经开发完成了,但是由于先前的设计并没有考虑比较器功能。

class Person {
    private String name;
    private int age;
    public Person(String name,int age){
        this.name=name;
        this.age=age;
    }

    @Override
    public String toString() {
        return "【Person对象】姓名:"+this.name+"、年龄"+this.age;
    }
}

后来经过若干 的迭代更新后发现需要对Person类进行排序处理,但是又不允许去修改Person类(无法实现Comparable接口),所以这个时候就需要一种挽救的形式来实现比较。在Arrays类里面排序有另外一种实现:

public static <T> void sort(T[]a,Comparator<? super T> c)
import java.util.Arrays;
import java.util.Comparator;

class Person {
    private String name;
    private int age;
    public Person(String name,int age){
        this.name=name;
        this.age=age;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    @Override
    public String toString() {
        return "【Person对象】姓名:"+this.name+"、年龄"+this.age;
    }
}
class PersonCompartor implements Comparator<Person>{
    @Override
    public int compare(Person o1, Person o2) {
        return o1.getAge()-o2.getAge();
    }
}
public class CompareDemo {
    public static void main(String[] args) {
        Person [] data=new Person[]{new Person("A",100),new Person("B",50),new Person("C",60)};
        Arrays.sort(data,new PersonCompartor());
        System.out.println(Arrays.toString(data));
    }
}

【面试题】Comparable和Comparator的区别:

  • Comparable是在类定义的时候实现的父接口,主要用于定义排序规则,里面只有compareTo()方法
  • Comparator是挽救的比较器操作,需要设hi在单独的比较器规则实现排序

二叉树结构简介

​ 在进行链表结构开发的过程中会发现所有的数据按照首尾相连的状态进行保存,那么对某一个数据进行查询时,时间复杂度为O(n)。数据量小(不超过30个)的情况下,性能上是不会有太大差别的,一旦保存的数据量很大,这个时候时间复杂度就会严重损耗程序的运行性能,那么对于数据的存储结构就必须发生改变,应该以尽可能减少检索次数为出发点进行设计,对于现在的数据结构而言,最好的性能就是O(logn),所以想要实现它就可以利用二叉树的结构来完成。

二叉树的遍历:

  • 前序遍历
  • 中序遍历
  • 后序遍历

二叉树基本实现

​ 在实现二叉树的处理中最为关键的问题在于数据的保存,而且数据由于牵扯到对象比较的问题,那么一定要有比较器的支持,而这个比较器首选的一定就是Comparable,所以本次将保存一个Person类数据。

​ 如果想要实现数据的保存,首先一定需要一个节点类,节点类由于牵扯到数据的保存问题,所以必须使用Comparable(可以区分大小)。

import java.util.Arrays;

class Person implements Comparable<Person>{
    private String name;
    private int age;
    public Person(String name,int age){
        this.name=name;
        this.age=age;
    }

    @Override
    public int compareTo(Person per) {
        return this.age-per.age;
    }

    @Override
    public String toString() {
        return "【Person对象】姓名:"+this.name+"、年龄"+this.age;
    }
}

/**
 *实现二叉树操作
 * @Param<T> 要进行二叉树的实现
 *
 */
class BinaryTree<T extends Comparable<T>>{
    private class Node{
        private Comparable<T> data;//存放Comparable可以比较大小
        private Node parent;//保存父节点
        private Node left;//保存左子树
        private Node right;//保存右子树
        public Node(Comparable<T> data){//构造方法负责进行数据的存储
            this.data=data;
        }

        /**
         * 实现节点数据适当位置的存储
         *
         * @param newNode 创建新的节点
         */
        public void addNode(Node newNode){
            if(newNode.data.compareTo((T)this.data)<=0){//比当前节点数据小
                if(this.left==null){//现在没有左子树
                    this.left=newNode;//保存左子树
                    newNode.parent=this;//保存父节点
                }else{
                    this.left.addNode(newNode);//继续向下判断
                }
            }else{//比根节点的数据更大
                if(this.right==null){
                    this.right=newNode;//没有右子树
                    newNode.parent=this;//保存父节点
                }else{
                    this.right.addNode(newNode);//继续向下判断
                }

            }
        }

        /**
         * 实现所有数据的获取处理,按照中序遍历完成
         */
        public void toArrayNode(){
            if(this.left!=null){//有左子树
                this.left.toArrayNode();//递归调用
            }
            BinaryTree.this.returnData[BinaryTree.this.foot++]=this.data;
            if(this.right!=null){
                this.right.toArrayNode();
            }
        }

    }
    //-------------以下为二叉树的功能实现-------------\
    private Node root;//保存的是根节点
    private int count;//保存数据的个数
    private Object []returnData;//返回的数据
    private int foot=0;//脚标控制
    /**
     *进行数据的保存
     * @param data 数据
     * @exception  NullPointerException 保存数据为空时抛出的异常
     */
    public void add(Comparable<T> data){
        if(data==null){
            throw new NullPointerException("保存的数据不允许为空");
        }
        //所有的数据本身不具有节点关系的匹配,那么一定要将其包装在Node类之中
        Node newNode=new Node(data);//保存节点
        if(this.root==null){//现在没有根节点 则第一个节点作为根节点
            this.root=newNode;
        }else{//要将其保存到一个合适的节点
            this.root.addNode(newNode);//交由Node类负责处理
        }
        this.count++;
    }

    /**
     * 以对象数据的形式返回全部数据,如果没有数据返回Null
     *
     * @return {@link Object[]} 全部数据
     */
    public Object[] toArray(){
        if(this.count<=0){
            return null;
        }
        this.returnData=new Object[this.count];
        this.foot=0;//脚标清零
        this.root.toArrayNode();//直接通过Node类负责
        return this.returnData;
    }

}
public class CompareDemo {
    public static void main(String[] args) {
       BinaryTree<Person> tree=new BinaryTree<Person>();
       tree.add(new Person("A",20));
       tree.add(new Person("B",30));
       tree.add(new Person("C",40));
       tree.add(new Person("D",50));
       System.out.println(Arrays.toString(tree.toArray()));
    }
}

在进行数据添加的时候只是实现了节点关系的保存,而这种关系保存后的结果就是数据都属于有序排列。

二叉树数据删除

​ 基本规则“

  1. 如果待删除节点没有子节点直接删除即可。
  2. 如果待删除节点只有一个子节点,那么直接删除掉,并用其子节点去顶替
  3. 如果待删除节点有两个子节点,这种情况比较复杂。首先找出它的后继节点,然后处理”后继节点“和“被删除节点”之间的关系,最后处理她们之间的关系。

在Node类中增加新的处理功能:

  /**
         * 获取要删除的节点对象
         *
         * @param data 数据
         * @return {@link Node}
         */
        public Node getRemoveNode(Comparable<T>data){
            if(data.compareTo((T)this.data)==0){
                return this;
            }
            else if(data.compareTo((T)this.data)<0){
                if(this.left!=null){
                    return this.left.getRemoveNode(data);
                }
                else{
                    return null;
                }
            }else{
                if(this.right!=null){
                    return this.left.getRemoveNode(data);
                }
                else{
                    return null;
                }
            }
        }

在BinaryTree类里面进行节点的处理:

/**
     * 删除
     *
     * @param data 要删除的数据
     */
    public void remove(Comparable<T> data){
        
        Node removeNode = this.root.getRemoveNode(data);//找到要删除的节点
        if(this.returnData!=null){
            //第一种情况没有任何的子节点
            if(removeNode.left==null&&removeNode.right==null){
                removeNode.parent=null;//父节点断开链接
            }else if(removeNode.left!=null&&removeNode.right==null){//第二种情况  1左边不为空
                removeNode.left.parent=removeNode.parent;
            }else if(removeNode.left==null&&removeNode.right!=null){//第二种情况  2右边不为空
                removeNode.right.parent=removeNode.parent;
            }else{
                //第三种情况 两边都有节点 则将右边节点中最左边的节点找到 改变其指向
                Node moveNode=removeNode.right;//移动节点
                while(moveNode.left!=null){
                    moveNode=moveNode.left;
                }
                moveNode.parent.left=null;//断开原本的链接
                moveNode.parent=removeNode.parent;
                moveNode.right=removeNode.right;//改变右节点的指向
            }
        }


    }

红黑树原理简介

​ 红黑树的本质是一颗二叉查找树,但它在二叉查找树的基础上添加了一个标记(颜色),同时具有一定的规则,这些规则是的红黑树保持了一种平衡,插入、删除、查找的最坏时间复杂度都为O(logn)。红黑树是在 1972 年由 Rudolf Bayer 发明的,当时被称为平衡二叉 B 树( symmetric binary B-trees )。后来,在1978年被 Leo J.Guibas 和 Robert Sedgewick 修改为如今的“红黑树”。红黑树的本质就是在节点上追加了一个表示颜色的操作信息。

enum Color{
    RED,BLACK;
}
class BinaryTree<T>{
    private T data;
    private Node parent;
    private Node left;
    private NOde right;
    private Color color;
}

对于 Node 节点中的颜色标记也可以使用 true 或 false 来实现,不一定非要使用枚举类。

标准的红黑树结构:

在这里插入图片描述

红黑树的特点:

  • 规则一:每个节点或者是红色,或者是黑色;

  • 规则二:根节点必须是黑色;

  • 规则三:每个叶子节点是黑色;

    java实现的红黑树用null来代表空节点,因此遍历红黑树的时候将看不到黑色的叶子节点,反而看到每个叶子节点都是红色的

  • 规则四:如果一个节点是红色的,那么它的子节点必须是黑色的;

    从每个根到节点的路径上不会有两个连续的红色节点,但是黑色节点是可以连续的。若给定黑色节点的个数为N,最短路径情况是连续的N个黑色,树的高度为N-1;最长路径的情况为红黑相间,树的高度为2(N-1)

  • 规则五:一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点数量;

    成为红黑树最主要的条件,后序的插入、删除操作都是为了遵守这个规定\

红色节点之后绝对不可能是红色节点,但是没有说黑色节点之后不允许是黑色节点,允许黑-黑连接,主要是利用这个红色节点与黑色节点实现均衡的控制。简单点理解红黑树的结构就是为了可以进行右旋的控制,以保证树的平衡性.

数据插入平衡原则

  • 第一次插入,由于原树为空,所以只会违反红-黑树的规则所以只要把根节点涂黑即可;

  • 如果插入节点的父节点是黑色的,那不会违背红-黑树的规则,什么也不需要做;但是如果遇到如下三种情况时就要开始变色和旋转了。

    1. 插入节点的父节点和其叔叔节点(祖父节点的另一个子节点)均为红色的;
    2. 插入节点的父节点是红色,叔叔节点是黑色,且插入节点是其父节点的左子节点;
    3. 插入节点的父节点是红色,叔叔节点是黑色,且插入节点是其父节点的右子节点;

在进行红黑树处理的时候为了方便操作都会将新的节点使用红色来进行描述,于是当设置根数据插入平衡处理规则节点的时候就会违反规则二,那么这个时候只需要将节点的颜色涂黑即可.

数据插入平衡处理规则1

插入节点的父节点和其叔叔节点(祖父节点的另一个子节点)均为红色的;将当前节点【10节点】父节点【30节点】与叔叔节点【70节点】涂黑

在这里插入图片描述

数据插入平衡处理规则2

插入节点的父节点和其叔叔节点(祖父节点的另一个子节点)均为红色的;将当前节点【10节点】父节点【30节点】与祖父节点【50节点】进行颜互换,随后采用右旋处理

在这里插入图片描述

数据插入平衡处理规则3

在这里插入图片描述

插入操作分析

在这里插入图片描述

在这里插入图片描述

在红黑树进行修复处理之中,它需要根据当前节点以及当前节点的父节点和叔叔节点之间的颜色来判断树是否需要进行修复处理

数据删除平衡原则

二叉搜索树的数据删除

  • 如果待删除节点没有子节点,那么直接删掉即可
  • 如果待删除节点只有一个子节点,那么直接删除,并用其子节点去顶替它
  • 如果待删除节点有两个子节点,这种情况比较复杂。首先找出它的后继节点,然后处理”后继节点“和”被删除节点之间的关系“,最后处理”后继节点的子节点“和”被删除节点的子节点“之间的关系

红黑树的数据删除处理

  • 删除操作后,如果当前节点是黑色的根节点,那么不用任何操作,因为并没有破坏树的平衡性,即没有破坏红黑树的规则
  • 如果当前节点是红色的,说明刚刚移走的后继节点是黑色的,那么不管后继节点的父节点是啥颜色,只要将当前节点涂黑就可以了,红黑树的平衡就看可以恢复
  • 但是如果遇到以下四种情况,就需要通过变色或旋转来恢复红-黑树的平衡了:
    1. 当前节点是黑色的,且兄弟节点是红色的(那么父节点和兄弟节点的子节点肯定是黑色的)
    2. 当前节点是黑色的,且兄弟节点是黑色的,且兄弟节点的两个子节点均为黑色
    3. 当前节点是黑色的,且兄弟节点是黑色的,且兄弟节点的左子节点是红色,右子节点是黑色
    4. 当前节点是黑色的,且兄弟节点是黑色的,且兄弟节点的右子节点是红色,左子节点是任意颜色

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

如果当前节点是红色的,说明刚刚移走的后继节点是黑色的,那么不管后继节点的父节点是啥颜色,只要将当前节点涂黑就可以了,红黑树的平衡就看可以恢复

  • 但是如果遇到以下四种情况,就需要通过变色或旋转来恢复红-黑树的平衡了:
    1. 当前节点是黑色的,且兄弟节点是红色的(那么父节点和兄弟节点的子节点肯定是黑色的)
    2. 当前节点是黑色的,且兄弟节点是黑色的,且兄弟节点的两个子节点均为黑色
    3. 当前节点是黑色的,且兄弟节点是黑色的,且兄弟节点的左子节点是红色,右子节点是黑色
    4. 当前节点是黑色的,且兄弟节点是黑色的,且兄弟节点的右子节点是红色,左子节点是任意颜色

在红黑树之中修复的目的是为了保证树结构中的黑色节点的数量平衡,黑色节点的数量平衡了,那么才可能达到 O(logn) 的执行性能,但是修复的过程一方面是红黑的处理,另一方面就是黑色节点的保存层次.

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值