Set集合
HashSet与TreeSet的区别
HashSet:底层是哈希表,线程不安全的
TreeSet:底层是⼆叉树,线程不安全的
哈希表
哈希表简介
Set集合的两个实现类HashSet与LinkedHashSet,底层实现都是哈希表。
Hash,⼀般翻译做“散列”,也有直接⾳译为“哈希”的,它是基于快速存取的⻆度设计的,也是⼀种典型的“空间换时间”的做法。顾名思义,该数据结构可以理解为⼀个线性表,但是其中的元素不是紧密排列的,⽽是可能存在空隙。
散列表(Hash table,也叫哈希表),是根据键值码值(Key value)⽽直接进⾏访问的数据结构。也就是说,它通过把键值码值映射到表中⼀个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数。
Hash表的组成是”数组+链表”这些元素是按照什么样的规则存储到数组中呢。⼀般情况是通过hash(key)%len获得,也就是元素的key的哈希值对数组⻓度取模得到。
hash表扩容的理解
Java默认的散列单元⼤⼩全部都是2的幂,初始值为16(2的4次幂)。假如16条链表中的75%链接有数据的时候,则认为加载因⼦达到默认的0.75。HahSet开始重新散列,也就是将原来的散列结构全部抛弃,重新开辟⼀个散列单元⼤⼩为32(2的5次幂)的散列结果,并重新计算各个数据的存储位置。
排重机制的实现
假如我们有⼀个数据(散列码76268),⽽此时的HashSet有128个散列单元,那么这个数据将有可能插⼊到数组的第108个链表中(76268%128=108)。但这只是有可能,如果在第108号链表中发现有⼀个⽼数据与新数据equals()=true的话,这个新数据将被视为已经加⼊,⽽不再重复丢⼊链表。
优点
哈希表的插⼊和查找是很优秀的.
对于查找:直接根据数据的散列码和散列表的数组⼤⼩计算除余后,就得到了所在数组的位置,然后再查找链表中是否有这个数据即可。因为数组本身查找速度快,所以
查找的效率⾼低体现在链表中,但是真实情况下在⼀条链表中的数据⼜很少,有的甚⾄没有,所以⼏乎没有什么迭代的代价。所以散列表的查找效率建⽴在散列单元所指向的链表中数据的多少上.
对于插⼊:数组的插⼊速度慢,⽽链表的插⼊速度快.当我们使⽤哈希表时,不需要更改数组的结构,只需要在找到对应的数组下标后,进⼊对应的链表,操作链表即可.所以hash表的整体插⼊速度也很快.
缺点
哈希表唯一的缺点就在于,当链表中的75%链接有数据的时候,会进行扩容而进行重新散列,需要重新计算每一个元素所在的位置,造成时间上的浪费。
二叉树
常规概念
树
树是由根结点和若⼲颗⼦树构成的。树是由⼀个集合以及在该集合上定义的⼀种关系构成的。集合中的元素称为树的结点,所定义的关系称为⽗⼦关系。⽗⼦关系在树的结点之间建⽴了⼀个层次结构。在这种层次结构中有⼀个结点具有特殊的地位,这个结点称为该树的根结点,或称为树根。注意:普通树中每个节点的⼦节点个数不⼀定是2个.
注意:单个结点是⼀棵树,树根就是该结点本身。
空集合也是树,称为空树。空树中没有结点;
森林
森林:由m(m⼤于等于0)棵互不相交的树的集合称为森林。
孩⼦结点或⼦结点:⼀个结点含有的⼦树称为该结点的⼦结点;
结点的度:⼀个结点含有的⼦结点的个数称为该结点的度;
叶结点或终端结点:度为0的结点称为叶结点;
⾮终端结点或分⽀结点:度不为0的结点;
双亲结点或⽗结点:若⼀个结点含有⼦结点,则这个结点称为其⼦结点的⽗结点;
兄弟结点:具有相同⽗结点的结点互称为兄弟结点;
树的度:⼀棵树中,最⼤的结点的度称为树的度;
结点的层次:从根开始定义起,根为第1层,根的⼦结点为第2层,以此类推;
树的⾼度或深度:树中结点的最⼤层次;
堂兄弟结点:双亲在同⼀层的结点互为堂兄弟;
结点的祖先:从根到该结点所经分⽀上的所有结点;
⼦孙:以某结点为根的⼦树中任⼀结点都称为该结点的⼦孙;
哈夫曼树(最优⼆叉树):带权路径最短的⼆叉树称为哈夫曼树或最优⼆叉树。
⼆叉树遍历
⼆叉树是⼀种⾮常重要的数据结构,它同时具有数组和链表各⾃的特点:它可以像数组⼀样快速查找,也可以像链表⼀样快速添加。但是他也有⾃⼰的缺点:删除操作复杂。
⼆叉树:是每个结点最多有两个⼦树的有序树,在使⽤⼆叉树的时候,数据并不是随便插⼊到节点中的,⼀个节点的左⼦节点的关键值必须⼩于此节点,右⼦节点的关键值必须⼤于或者是等于此节点,所以⼜称⼆叉查找树、⼆叉排序树、⼆叉搜索树。
⼆叉树遍历分为三种
先序遍历
⾸先访问根,再先序遍历左⼦树,最后先序遍历右⼦树(根左右)
图中顺序:10-3-2-4-9-8-18-13-21
中序遍历
⾸先中序遍历左⼦树,再访问根,最后中序遍历右⼦树(左根右)
图中顺序:2-3-4-8-9-10-13-18-21
后序遍历
⾸先后序遍历左⼦树,再后序遍历右⼦树,最后访问根(左右根)
图中顺序:2-8-9-4-3-13-21-18-10
树的分类
⽆序树:树中任意节点的⼦结点之间没有顺序关系,这种树称为⽆序树,也称为⾃由树;
有序数:树中任意节点的⼦结点之间有顺序关系,这种树称为有序树;
⼆叉树:每个节点最多含有两个⼦树的树称为⼆叉树;
满⼆叉树:叶节点除外的所有节点均含有两个⼦树的树被称为满⼆叉树;
完全⼆叉树:有2^k-1个节点的满⼆叉树称为完全⼆叉树;
HashSet & LinkedHashSet
去重原理
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uOOebqkG-1628307086889)(Set集合.assets/image-20210802172236133.png)]
解释:是通过调⽤元素的hashCode和equals⽅法实现去重,⾸先调⽤hashCode⽅法,拿到当前对象的哈希码值,去让两个对象的哈希码值进⾏⽐较,如果不同,直接认为是两个对象,不再去调⽤equals,如果相同,再继续调⽤equals⽅法,返回true认为是⼀个对象,返回false认为是两个对象
TreeSet
TreeSet的简介
TreeSet是⼀个Set接⼝的实现类,底层实现是⼆叉树。这样的集合,会对添加进集合的元素进⾏去重的处理。 同时, 这个集合会对添加进⼊的元素进⾏⾃动的升序排序。
Comparable接⼝(默认排序)
如果某⼀个类实现这个接⼝, 表示⾃⼰实现了⼀个可以和⾃⼰的对象进⾏⼤⼩⽐较的规则。 此时, 这个类的对象就可以直接存储进TreeSet集合中了。 因为此时TreeSet集合已经知道了怎么对两个这个类的对象进⾏⼤⼩⽐较。
Comparator接⼝(⼈⼯排序)
**定义:**使⽤实现了Comparator接⼝的compare()⽅法的⽐较器对象进⾏⽐较
**分析1:**有了Comparable,为什么还要有comparator?
原因:
对于⾃定义的类,代码是我们⾃⼰编写的,所有在排序时不管是通过Comparator还是Comparable,排序规则我们都可以⾃⼰制定,所以最终使⽤那种⽅法没有太⼤的区别
对于系统类,影响⾮常⼤.系统类中的代码我们只能⽤,不能改.这也就意味着系统类内部通过Comparable实现的⽐较规则已经确定了.这时我们想使⽤其他的规则对当前的系统类对象进⾏⽐较,只能使⽤Comparator⾃⼰重新制定⽐较规则.
**分析2:**⼈⼯排序和默认排序那个优先级⾼?
答:⼈⼯排序的优先级⾼于默认排序.
我们可以让TreeSet同时获取到Comparator和Comparable的⽐较⽅法,此时对于系统类来说默认排序是系统⾃带的,通过Comparator实现的⼈⼯排序规则是我们想要的,所以系统必须让⼈⼯排序优先于默认排序,才能正常的使⽤后加的排序规则.
Comparable与Comparator的使⽤场景
如果这个对象, 在项⽬中⼤多数的情况下, 都采⽤相同的⼤⼩⽐较的⽅式。 ⽐如: ⼀个Person类, 在⼤多数情况下, 都是按照年龄进⾏⼤⼩⽐较的。 此时就可以让Person类实现Comparable接⼝。
如果某⼀个类的对象, 在临时进⾏⼤⼩⽐较的时候, 使⽤的与默认的⽐较不⼀样的规则。 ⽐如: ⼀个Person类, ⼤多数情况下, 都是使⽤的年龄进⾏⼤⼩⽐较的, 但是临时需要使⽤身⾼进⾏⼀次⽐较, 此时就可以使⽤ Comparator临时完成了。 ⽽且, Comparator的优先级要⾼于Comparable。
系统类想实现新的⽐较规则,使⽤Comparator
TreeSet的去重
⾏⼤⼩⽐较的, 但是临时需要使⽤身⾼进⾏⼀次⽐较, 此时就可以使⽤ Comparator临时完成了。 ⽽且, Comparator的优先级要⾼于Comparable。
系统类想实现新的⽐较规则,使⽤Comparator
TreeSet的去重
⽆论使⽤Comparator还是Comparable,如果两个对象进⾏⼤⼩⽐较的结果是0,此时代表这两个对象是相同的对象。 在TreeSet中会完成排重的处理。