当前位置: 首页 > news >正文

【Java to Architect】HashSet TreeSet 集合 红黑树

概论

  • Set集合继承了AbstractCollection集合,它的核心特点是没有重复元素,无序。
  • TreeSet集合(红黑树),在没有重复元素的基础上,还能够将元素进行排序储存。

Set

我将通过典型的HashSet来了解Set的特性。HashSet实现Set的方式是通过Hash表确保元素的唯一性。而Hash表中,又将分别进行两个动作来判断将要储存的元素是否唯一。

  1. *hashCode()*方法
  2. equals() 方法

在往HashSet内添加值时,会分别进行以上两个步骤。HashSet允许储存:

  • 与现有元素集哈希值不同的元素
  • 与现有元素集哈希值相同,但是 equals() 返回false的元素。

除了以上两种元素,其他元素都会被拒之门外。
因此,在使用HashSet储存对象时,我们需要重写对象的这两个方法,以便满足我们对储存对象数据唯一性的要求。

HashSet例子:

在这个例子中,我写了一个Band类,在这个类中,有三个内部变量:namestylerate, 在判断HashCode上,我选择用namerate判断,在equals上,我选择namestyle判断。

class Band implements {
    private String name;
    private String style;
    private int rate;

    Band() {

    }

    public Band(String name, String style, int rate) {
        this.name = name;
        this.style = style;
        this.rate = rate;
    }

    public String getName() {
        return this.name;
    }

    public String getStyle() {
        return this.style;
    }

    public int getRate() {
        return this.rate;
    }

    public void setRate(int rate) {
        this.rate = rate;
    }

    @Override
    public int hashCode() {
        System.out.println(this.toString() + "'s hashCode: " + this.name.hashCode() + this.rate * 37);
        return this.name.hashCode() + this.rate * 37;
    }

    @Override
    public boolean equals(Object obj) {
        System.out.println(this + "---equals---" + obj);
        if (obj instanceof Band) {
            Band bd  = (Band)obj;
            return this.name.equals(bd.name) && this.style.equals(bd.style);
        } else {
            return false;
        }
    }

    @Override
    public String toString() {
        return "Band@name: " + this.name + " style: " + this.style + " rate: " + this.rate;
    }
}

在主方法中,我们来尝试一下HashSet判断一致性的过程:

public static void main(String[] args) {
        HashSet<Band> hs = new HashSet<Band>();
        hs.add(new Band("Smashing Pumpkins", "Rock", 99));
        hs.add(new Band("Smashing Pumpkins", "Rock", 98));
        hs.add(new Band("Smashing Pumpkins", "Rock", 98));
        hs.add(new Band("Smashing Pumpkins", "synth", 97));
        hs.add(new Band("Smashing Pumpkins", "synth", 97));
        hs.add(new Band("Smashing Pumpkins", "synth", 96));
        hs.add(new Band("Smashing Pumpkins", "alternative", 95));
        hs.add(new Band("Smashing Pumpkins", "alternative", 94));
        Iterator<Band> it = hs.iterator();
		while (it.hasNext()) {
			Object next = it.next();
			System.out.println(next);
        }
    }

终端输出:

Band@name: Smashing Pumpkins style: Rock rate: 99's hashCode: -2287338513663
Band@name: Smashing Pumpkins style: Rock rate: 98's hashCode: -2287338513626
Band@name: Smashing Pumpkins style: Rock rate: 98's hashCode: -2287338513626
Band@name: Smashing Pumpkins style: Rock rate: 98---equals---Band@name: Smashing Pumpkins style: Rock rate: 98  
Band@name: Smashing Pumpkins style: synth rate: 97's hashCode: -2287338513589
Band@name: Smashing Pumpkins style: synth rate: 97's hashCode: -2287338513589
Band@name: Smashing Pumpkins style: synth rate: 97---equals---Band@name: Smashing Pumpkins style: synth rate: 97
Band@name: Smashing Pumpkins style: synth rate: 96's hashCode: -2287338513552
Band@name: Smashing Pumpkins style: alternative rate: 95's hashCode: -2287338513515
Band@name: Smashing Pumpkins style: alternative rate: 94's hashCode: -2287338513478
------------------------------ HashSet Elements ----------------------------
Band@name: Smashing Pumpkins style: Rock rate: 98
Band@name: Smashing Pumpkins style: alternative rate: 94
Band@name: Smashing Pumpkins style: synth rate: 97
Band@name: Smashing Pumpkins style: synth rate: 96
Band@name: Smashing Pumpkins style: Rock rate: 99
Band@name: Smashing Pumpkins style: alternative rate: 95

从输出上看到,当发现有一个元素的hashCode撞到元素集中的某一个元素后,HashSet会调用对象的equals方法来看二者是否一致,如果判断不一致,则拒绝添加。在输出HashSet所有元素时,我们可以看到它是无序的,与添加顺序、对象值并无关系。

红黑树(TreeSet)

如果需求中,既需要不重复,又需要排序,则TreeSet是合理的选择。在唯一性的基础上,往红黑树里添加元素时,会有一个比较的过程。而这个比较的过程会有两种方法实现:

  1. 将对象继承Comparable,并重写实现compareTo方法
  2. 在红黑树容器中,指定一个比较器(Comparator
红黑树例子:

对象自身比较方法 - 将Band对象实现Comparable接口并且重写compareTo方法

class Band implements Comparable<Band>{
    private String name;
    private String style;
    private int rate;

    Band() {

    }

    public Band(String name, String style, int rate) {
        this.name = name;
        this.style = style;
        this.rate = rate;
    }

    public String getName() {
        return this.name;
    }

    public String getStyle() {
        return this.style;
    }

    public int getRate() {
        return this.rate;
    }

    public void setRate(int rate) {
        this.rate = rate;
    }

    @Override
    public int hashCode() {
        System.out.println(this.toString() + "'s hashCode: " + this.name.hashCode() + this.rate * 37);
        return this.name.hashCode() + this.rate * 37;
    }

    @Override
    public boolean equals(Object obj) {
        System.out.println(this + "---equals---" + obj);
        if (obj instanceof Band) {
            Band bd  = (Band)obj;
            return this.name.equals(bd.name) && this.style.equals(bd.style);
        } else {
            return false;
        }
    }

    @Override
    public String toString() {
        return "Band@name: " + this.name + " style: " + this.style + " rate: " + this.rate;
    }

    @Override
    public int compareTo(Band obj) {
        Band bd = (Band)obj;
        System.out.println(this + " compare to: " + bd);
        if (this.rate > bd.rate) {
            return -1;
        }
        if (this.rate < bd.rate) {
            return 1;
        }
        return this.name.compareTo(bd.name);
    }
}

在这里,我用Band对象的rate值来比较两个band。在主函数中,我们看一下红黑树时怎么储存元素的:

public static void main(String[] args) {
	TreeSet<Band> ts = new TreeSet<Band>();
        ts.add(new Band("Smashing Pumpkins", "Alternative", 100));
        ts.add(new Band("Guns'n'Roses", "Rock", 95));
        ts.add(new Band("Radiohead", "Alternative", 98));
        ts.add(new Band("Oasis", "Rock", 75));
        ts.add(new Band("Queen", "Rock", 95));
        Iterator<Band> ib = ts.iterator();
        while (ib.hasNext()) {
            Object next = ib.next();
            System.out.println(next);
        }
 }

输出:

Band@name: Smashing Pumpkins style: Alternative rate: 100 compare to: Band@name: Smashing Pumpkins style: Alternative rate: 100
Band@name: Guns'n'Roses style: Rock rate: 95 compare to: Band@name: Smashing Pumpkins style: Alternative rate: 100
Band@name: Radiohead style: Alternative rate: 98 compare to: Band@name: Smashing Pumpkins style: Alternative rate: 100
Band@name: Radiohead style: Alternative rate: 98 compare to: Band@name: Guns'n'Roses style: Rock rate: 95       
Band@name: Oasis style: Rock rate: 75 compare to: Band@name: Radiohead style: Alternative rate: 98
Band@name: Oasis style: Rock rate: 75 compare to: Band@name: Guns'n'Roses style: Rock rate: 95
Band@name: Queen style: Rock rate: 95 compare to: Band@name: Radiohead style: Alternative rate: 98
Band@name: Queen style: Rock rate: 95 compare to: Band@name: Guns'n'Roses style: Rock rate: 95
Band@name: Queen style: Rock rate: 95 compare to: Band@name: Oasis style: Rock rate: 75
------------------------------ TreeSet Elements ----------------------------
Band@name: Smashing Pumpkins style: Alternative rate: 100
Band@name: Radiohead style: Alternative rate: 98
Band@name: Guns'n'Roses style: Rock rate: 95
Band@name: Queen style: Rock rate: 95
Band@name: Oasis style: Rock rate: 75

现在,红黑树中已经根据rate来排序了,从输出中,我们也看到在排到最终位置之前,元素会被与各种节点比较。

下面来看一下如何用比较器构造红黑树:

class BandComparator implements Comparator<Band> {
    public int compare(Band o1, Band o2) {
        Band b1 = (Band)o1;
        Band b2 = (Band)o2;
        System.out.println(b1 + " comparator " + b2);
        if (b1.getRate() > b2.getRate()) {
            return 1;
        }
        if (b1.getRate() < b2.getRate()) {
            return -1;
        }
        return b1.getName().compareTo(b2.getName());
    }
}

然后在主函数中,把构造器传入红黑树实例:

public static void main(String[] args) {
	TreeSet<Band> tswc = new TreeSet<Band>(new BandComparator());
        tswc.add(new Band("Smashing Pumpkins", "Alternative", 100));
        tswc.add(new Band("Guns'n'Roses", "Rock", 95));
        tswc.add(new Band("Radiohead", "Alternative", 98));
        tswc.add(new Band("Oasis", "Rock", 75));
        tswc.add(new Band("Queen", "Rock", 95));
        Iterator<Band> ic = tswc.iterator();
        while (ic.hasNext()) {
            Object next = ic.next();
            System.out.println(next);
        }
 }

输出:

Band@name: Smashing Pumpkins style: Alternative rate: 100 comparator Band@name: Smashing Pumpkins style: Alternative rate: 100
Band@name: Guns'n'Roses style: Rock rate: 95 comparator Band@name: Smashing Pumpkins style: Alternative rate: 100
Band@name: Radiohead style: Alternative rate: 98 comparator Band@name: Smashing Pumpkins style: Alternative rate: 100
Band@name: Radiohead style: Alternative rate: 98 comparator Band@name: Guns'n'Roses style: Rock rate: 95        
Band@name: Oasis style: Rock rate: 75 comparator Band@name: Radiohead style: Alternative rate: 98
Band@name: Oasis style: Rock rate: 75 comparator Band@name: Guns'n'Roses style: Rock rate: 95
Band@name: Queen style: Rock rate: 95 comparator Band@name: Radiohead style: Alternative rate: 98
Band@name: Queen style: Rock rate: 95 comparator Band@name: Guns'n'Roses style: Rock rate: 95
------------------------------ TreeSet Elements ----------------------------
Band@name: Oasis style: Rock rate: 75
Band@name: Guns'n'Roses style: Rock rate: 95
Band@name: Queen style: Rock rate: 95
Band@name: Radiohead style: Alternative rate: 98
Band@name: Smashing Pumpkins style: Alternative rate: 100

这时候,输出的Band已经是rate升序排序了。可知在红黑树中,构造时的比较器优先级是大于对象自身的比较方法的。

相关文章:

  • 【Salesforce】【LWC】响应式验证标准查找输入框
  • 最长递增子序列问题(LIS) 动态规划 JavaScript
  • 位屏蔽(Bitmasking)中屏蔽字赋值语句 mask | (1 << j) 的解释
  • 【Java to Architect】synchronized保证内存可见性 demo的另一种解法
  • 利用位屏蔽和动态规划解决最小代价任务分配问题 Bitmasking Dynamic Programming
  • 算法:回溯法(backtracking)解决寻找给定字符串的所有排序(permutations)问题
  • 算法: 动态规划 寻找2D矩阵中到达某一坐标的最小代价路径
  • 算法:动态规划 寻找2D矩阵中到达某一坐标的可能路径总数
  • 算法:动态规划 寻找2D矩阵中到达某一坐标的可能路径总数进阶版(添加路障)
  • 算法: 动态规划,二维矩阵代价最值进阶版 两条行进路径,一次相交,求解最大代价
  • Programming Languages And Lambda calculi 1.1 定义集合
  • 算法: 动态规划 编辑距离 Edit Distance / Levenshtein Distance
  • 【Salesforce】【Apex】Trigger中不通过soql查询记录类型的开发名称
  • 【Programming Languages And Lambda calculi】 1.2 ~ 1.3 关系、赋值与关系
  • 【Programming Languages And Lambda calculi】 1.4 有向赋值
  • Android Volley源码解析
  • Docker: 容器互访的三种方式
  • ERLANG 网工修炼笔记 ---- UDP
  • Javascript基础之Array数组API
  • mysql外键的使用
  • React 快速上手 - 07 前端路由 react-router
  • 观察者模式实现非直接耦合
  • 技术发展面试
  • 码农张的Bug人生 - 见面之礼
  • 面试题:给你个id,去拿到name,多叉树遍历
  • 前端攻城师
  • 前端面试之闭包
  • 深度学习在携程攻略社区的应用
  • 7行Python代码的人脸识别
  • ​​​​​​​Installing ROS on the Raspberry Pi
  • ​​​​​​​ubuntu16.04 fastreid训练过程
  • ​直流电和交流电有什么区别为什么这个时候又要变成直流电呢?交流转换到直流(整流器)直流变交流(逆变器)​
  • # 日期待t_最值得等的SUV奥迪Q9:空间比MPV还大,或搭4.0T,香
  • ###51单片机学习(2)-----如何通过C语言运用延时函数设计LED流水灯
  • #考研#计算机文化知识1(局域网及网络互联)
  • $HTTP_POST_VARS['']和$_POST['']的区别
  • (07)Hive——窗口函数详解
  • (2)STL算法之元素计数
  • (3)Dubbo启动时qos-server can not bind localhost22222错误解决
  • (39)STM32——FLASH闪存
  • (Mac上)使用Python进行matplotlib 画图时,中文显示不出来
  • (二)换源+apt-get基础配置+搜狗拼音
  • (附源码)ssm考生评分系统 毕业设计 071114
  • (六) ES6 新特性 —— 迭代器(iterator)
  • (区间dp) (经典例题) 石子合并
  • (三维重建学习)已有位姿放入colmap和3D Gaussian Splatting训练
  • (深入.Net平台的软件系统分层开发).第一章.上机练习.20170424
  • (一)搭建springboot+vue前后端分离项目--前端vue搭建
  • (终章)[图像识别]13.OpenCV案例 自定义训练集分类器物体检测
  • (转)C#开发微信门户及应用(1)--开始使用微信接口
  • (转)从零实现3D图像引擎:(8)参数化直线与3D平面函数库
  • (转)全文检索技术学习(三)——Lucene支持中文分词
  • .bat批处理(三):变量声明、设置、拼接、截取
  • .NET Core 中的路径问题
  • .NET 反射的使用