集合精通
Set集合:
HashSet集合和TreeSet集合的区别:
HashSet:底层是hash表,线程不安全
TreeSet:底层是二叉树,线程不安全
哈希表:
简介:Set集合的两个实现类HashSet和LinkedHashSet,底层实现都是哈希表
1.Hash,一般译为散列,也有直接译为哈希的,它是一个基于快速存取的角度设计的,也是一种典型的空间换时间的做法,顾名思义,该数据结构可以理解为一个线性表,但是其中的元素不是紧密排列的,而是可能存在空隙
2.散列表(Hash Table):是根据键值码值(key value)而直接进行访问的数据结构,也就是说,它通过把键值码值映射到表中的一个位置来访问记录,以加快查找的速度,这个映射函数称为散列函数(Hash Table)
3.Hash表的组成是:数组+链表这些元素是按照什么样的规则存储到数组中的呢,一般情况下是通过hash(key)%length获取,也就是元素的key的哈希值对数组长度取模得到
hash表扩容的理解:
可是当哈希表接近装满的时候,因为数组的扩容问题,性能较低(转移到更大的哈希表中)
Java默认的散列单元大小全部都是2的幂,初始值为16(2^4),假如16条链表中的75%有数据的时候,则认为加载因子达到默认的0.75。hashSet开始宠幸散列,也就是将原来的散列结构全部抛弃,并重新计算各个数据的存储位置,以此类推下去
负载(加载)因子:0.75——>>hash表提供的空间是16,也就是当到达12的时候就扩容
排重机制的实现:
假如我们有一个数据(散列码76268),而此时的HashSet有128个散列单元,那么这个数据将有可能插入到数组的第108和链表中(76268 % 128 = 108)。但这只是有可能,如果在第108号链表中发现了有一个老数据与新数据equals()= true的话,这个新数据将被视为已经加入,而不再重复丢入链表
优点:
hash表的插入和查找都是很优秀的
对于查找:直接根据数据的散列码和散列表的数组大小计算除余后,就得到了所在数组的位置,然后在查找链表中是否有这条数据即可,因为数组本身的查找速度快,所以查找的效率高低体现在链表中,但是真实情况下,一条链表中的数据又很少,有的甚至没有,所以就几乎没有什么迭代的代价,所以散列表的查找效率建立在散列单元所指向的链表中的数据的多少上
对于插入:数组的插入速度慢,而链表的插入速度快,当我们不再使用hash表的时候不需要更改数组的结构,只需要在找到对应的数组下标后,进入对应的链表,操作链表即可,所以hash表的整体插入顺序也很快
二叉树:
概念:树是由根节点和若干棵子树构成的。树是由一个集合以及在该集合上定义的一种关系构成的。集合中的元素称为树的节点,所定义的关系称为父子关系。父子关系在树的结点之间建立了一个层次结构。在这种层次结构中有一个结点具有特殊的地位、这个结点称为该树的根节点,或称为树根。
注意:普通树的每个结点的子节点个数不一定是2个
注意:单个节点也是一棵树,树根就是该结点本身
注意:空集合也是树,称为空树,空树中没有节点
森林:
相关概念:
由m(m大于等于0)棵互不相交的树的集合称为森林
孩子节点或者子节点:一个节点中含有的子树的称为该结点的子节点
结点的度:一个节点含有子结点的个数称为该结点的度
叶节点或者终端节点:度为0的结点称为叶结点
非终端节点或者分支节点:度不为0的结点
双亲结点或者父节点:若一个结点含有子节点,则这个节点称为其子节点的父节点
兄弟节点:具有相同父节点的结点称为兄弟节点
树的度:一棵树中,最大的节点的度称为树的度
结点的层次:从根开始定义,根为第一层,跟的子节点为第二层,以此类推
树的高度和深度:树中节点的最大层次
堂兄弟节点:双亲在同一层的结点互为堂兄弟
结点的祖先:从跟到该结点所经分支上的所有结点
子孙:以某节点为根的子树中任一节点都称为该结点的子孙
树的分类:
无序树:树中任意节点的子节点之间没有顺序关系,这种树称为无序树,也称自由树
有序树:树中任意节点的子节点之间有顺序关系,这种树称为有序树
二叉树:每个结点最多含有两个子树的树称为二叉树
满二叉树:叶节点除外的所有节点均含有两个子树的树称为满二叉树
完全二叉树:有2^k-1个节点的满二叉树称为完全二叉树
哈夫曼树(最优二叉树):带权路劲最短的二叉树称为哈夫曼树或最优二叉树
二叉树的遍历
1.二叉树是一种非常重要的数据结构,他同时具有数组和链表各自的特点:它可以像数组一样快速的查找,也可以像链表一样快速添加。但他也有自己缺点:删除操作复杂
2.二叉树:是每个结点最多有两个子树的有序树,在使用二叉树的时候,数据并不是随便插入到节点中的,一个节点的左子节点的关键值,必须小于此节点,右子节点的关键值必须大于或是等于此节点,所以又称二叉查找树,二叉排序树,二叉搜索树
二叉树的遍历方式:
先序遍历:
首先访问根,再先序遍历左子树,最后先序遍历右子树(根左右)
中序遍历:
首先中序遍历左子树,再访问根,最后中序遍历右子树(左跟右)
后序遍历:
首先后序遍历左子树,再后序遍历右子树,最后访问根
去重原理:
HashSet和LinkedHashSet
是通过调用元素的hashCode和equals方法实现去重,首先调用hashCode方法,拿到当前对象的哈希码值,去让两个对象的哈希码值进行比较,如果不同,直接认为是两个对象,不再去调用equals,如果相同,再继续调用equals方法,返回true认为是一个对象,返回false认为是两个对象
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bz4y5oJZ-1622422018276)(C:\Users\16326\Desktop\dayHomework\数据结构\QQ截图20210530151636.png)]
package bk.javase;
import java.util.HashSet;
import java.util.StringJoiner;
public class p530 {
public static void main(String[] args) {
HashSet<String> set = new HashSet<>();
set.add("java");
set.add("php");
set.add("bigData");
set.add("java");
System.out.println(set); //[bigData, java, php]
//说明Set本身的add方法内部实现了去重功能,默认调用的是元素的hashCode方法和equals方法
//String方法已经默认重写了hashCode和equals方法
//在创建一个HashSet
HashSet<Person> set2 = new HashSet<>();
set2.add(new Person("zhangsan", 20));
set2.add(new Person("lisi", 30));
set2.add(new Person("wangwu", 40));
set2.add(new Person("zhangsan", 20));
System.out.println(set2);
//[Person[name='wangwu', age=40], Person[name='lisi', age=30], Person[name='zhangsan', age=20], Person[name='zhangsan', age=20]]
//new出来的四个对象的地址肯定不同,那么放到HashSet中的第一步就直接判断而这不相同了。
//解决方式:
//自己制定比较的规则:并按照年龄和性别比较,相同则认为是同一个人
//第一步:重写hashcode方法
//第二步:重写equals方法
//[Person[name='lisi', age=30], Person[name='zhangsan', age=20], Person[name='wangwu', age=40]]
}
}
class Person{
String name;
int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return new StringJoiner(", ", Person.class.getSimpleName() + "[", "]")
.add("name='" + name + "'")
.add("age=" + age)
.toString