文章目录
1. 数据结构
1.1 二叉树
- 二叉树特点
二叉树中,任意一个节点的度都要小于等于2;
节点:在树结构中,每一个元素称之为节点(对象);
度:每一个节点的子节点数量称之为度;
普通二叉树:
1.2 二叉查找树
- 二叉查找树的特点
二叉查找树,又称二叉排序树或者二叉搜索树
每一个节点上最多有两个子节点
左子树上所有节点的值都小于根节点的值
右子树上所有节点的值都大于根节点的值 - 二叉查找树结构图
- 二叉查找树和二叉树做对比
- 二叉查找树添加节点的规则
小的存在左边
大的存在右边
如果按照以上规则,把 7,10,11,12,13按照规则插入,如果数据量大的话,添加和查找的效率就会非常低。
1.3 平衡二叉树
- 平衡二叉树特点
二叉树左右两个子树的高度差不超过1
任意节点的左右两个子树都是一颗平衡二叉树
往平衡二叉树中添加元素,是如果保证二叉树的平衡的呢?
这时我们就用到平衡二叉树的旋转规则了
- 平衡二叉树旋转
旋转触发时机:添加一个节点之后,该树不再是一颗平衡二叉树了
左旋:右子树的高度偏大(简单理解:当右边的节点比较多,就挪一个给左边)
将根节点的右侧往左拉,
原先的右子节点变成新的父节点,
并把多余的左子节点出让,给已经降级的根节点当右节点
加大难度:添加12 节点之后导致不平衡
右旋:添加了 1 节点之后导致不平衡
左子树的高度偏大(当左的节点比较多,就挪一个给右边)
加大难度(添加1节点后,树不平衡了):
- 平衡二叉树和二叉查找树对比结构图
- 平衡二叉树旋转的四种情况
左左:当根节点左子树的左子树节点插入,导致二叉树不平衡
如何旋转:直接对整体进行右旋
左右:当根节点左子树的右子树有节点插入时,导致二叉树不平衡
如和旋转:现在左子树对应的节点位置进行左旋,再对整体进行右旋
右右:当根节点右子树的右子树有节点插入时,导致二叉树不平衡
如何旋转:直接对整体进行左旋
右左:当根节点的右子树的左子树右节点插入时,导致二叉树不平衡
如何旋转: 先在右子树对应的节点位置进行右旋,再对整体进行左旋
1.4 红黑树
- 红黑树的特点
以前叫做平衡二叉B树
每一个节点可以是红或者黑
它是一种特殊的二叉查找树,红黑树的每一个节点上都有存储为表示节点的颜色
红黑树不是高度平衡的,他的平衡是通过"自己的红黑规则"进行实现的 - 红黑树的红黑规则
a.每一个节点可以使红或者黑
b.根节点必须是黑色
c.如果一个节点没有子节点或者父节点(根节点),则该节点相应的指针属性值为Nil,这些Nil视为叶节点,每个叶节点(Nil)是黑色的
如果某一个节点是红色的,那么它的子节点必须是黑色(不能出现两个红色节点相连的情况)
d.对每一个节点,从该节点到其所有后代叶节点的简单路径(不能回头只能往前)上,均包含相同数目的黑色节点 - 红黑树与平衡二叉树的区别
平衡二叉树是高度平衡的:
条件:当左右字数高度差超过1时->旋转
红黑树是一个二叉查找树,但不是高度平衡的。
- 红黑树添加节点默认颜色
添加节点是,默认为红色,效率高(添加三个元素,一共需要调整一次)
同理,添加节点时,默认为黑色的话,在满足红黑规则的前提下,需要调整两次(多动手练一下啦!)。 - 红黑树添加节点如何保持红黑规则?
父节点为黑色,不需要进行任何操作,默认红色即可。
父节点为红色时:
如果叔叔节点为红色,此时
将"父节点"设为黑色,将"叔叔节点"设为黑色
将"祖父节点"设为红色
如果"祖父节点"为根节点,则将根节点再次变成黑色。
如果叔叔节点为黑色,此时
将"父节点"设为黑色
将"祖父节点"设为红色
以"祖父节点"为支点进行旋转。
在上边的例子基础上继续添加节点:
1.5 TreeSet集合的应用
- 案例要求
用TreeSet集合存储多个学生信息(姓名,语文成绩,数学成绩,英语成绩),并遍历该集合 - 代码实现
学生类
public class Student implements Comparable<Student>{
private String name;
private int chinese;
private int math;
private int english;
public Student(String name, int chinese, int math, int english) {
this.name = name;
this.chinese = chinese;
this.math = math;
this.english = english;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getChinese() {
return chinese;
}
public void setChinese(int chinese) {
this.chinese = chinese;
}
public int getMath() {
return math;
}
public void setMath(int math) {
this.math = math;
}
public int getenglish() {
return english;
}
public void setenglish(int english) {
this.english = english;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", chinese=" + chinese +
", math=" + math +
", english=" + english +
'}' + ",总分=" + getSum();
}
public int getSum() {
return chinese + math + english;
}
@Override
public int compareTo(Student o) {
int result = this.getSum() - o.getSum();
// 次要条件
// 总分一样就比较语文成绩
result = result == 0 ? this.getChinese() - o.getChinese() : result;
// 语文成绩一样就比较数学成绩
result = result == 0 ? this.getMath() - o.getMath() : result;
// 数学成绩一样,就比较英语成绩(总分一样,语文数学都一样,英语自然就一样了,这一行可以不用写)
result = result == 0 ? this.getenglish() - o.getenglish() : result;
// 如果成绩都一样,就按照姓名进行排序
result = result == 0 ? this.getName().compareTo(o.getName()) : result;
return result;
}
}
测试类
public static void main(String[] args) {
TreeSet<Student> p = new TreeSet<>();
Student s1 = new Student("王五", 86, 80, 90);//256
Student s2 = new Student("张三", 90, 86, 90);//266
Student s3 = new Student("赵六", 100, 100, 100);//300
p.add(s1);
p.add(s2);
p.add(s3);
for (Student student : p) {
System.out.println(student);
}
}
输出结果:
Student{name='王五', chinese=86, math=80, english=90},总分=256
Student{name='张三', chinese=90, math=86, english=90},总分=266
Student{name='赵六', chinese=100, math=100, english=100},总分=300
- 思考:为什么添加数据顺序没有变,通过修改compareTo中的(this-o)或者(o-this)方法之后,取出的结果就会改变呢?
修改compareTo中的方法:
@Override
public int compareTo(Student o) {
int result = o.getSum() - this.getSum();
// 次要条件
// 总分一样就比较语文成绩
result = result == 0 ? o.getChinese() - this.getChinese() : result;
// 语文成绩一样就比较数学成绩
result = result == 0 ? o.getMath() - this.getMath() : result;
// 数学成绩一样,就比较英语成绩(总分一样,语文数学都一样,英语自然就一样了,这一行可以不用写)
result = result == 0 ? o.getenglish() - this.getenglish() : result;
// 如果成绩都一样,就按照姓名进行排序
result = result == 0 ? o.getName().compareTo(this.getName()) : result;
return result;
}
调用上边的main方法:输出结果集(从大到小排序)
Student{name='赵六', chinese=100, math=100, english=100},总分=300
Student{name='张三', chinese=90, math=86, english=90},总分=266
Student{name='王五', chinese=86, math=80, english=90},总分=256
使用两种不同的方式,最后生成的红黑树不同。但是在取出结果时,都是先获取左边,在获取中间,在获取右边。
所以最后取出的结果也会有差异!
2. HashSet集合
2.1 HashSet集合概述和特点
- 底层数据结构是哈希表
- 存取无序
- 不可以存储重复元素
- 没有索引,不能使用普通for循环遍历
2.2 HashSet集合的基本使用
- 代码演示(入门案例)
public static void main(String[] args) {
HashSet<String> hs = new HashSet();
hs.add("你好");
hs.add("世界!");
hs.add("java");
hs.add("java");
hs.add("java");
hs.add("Hello");
hs.add("Hello");
Iterator<String> iterator = hs.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
System.out.println("=================");
for (String h : hs) {
System.out.println(h);
}
}
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import warnings
warnings.filterwarnings('ignore')
import ssl
ssl._create_default_https_context = ssl._create_unverified_context
2.3 哈希值
- 哈希值简介
是JDK根据对象的地址或者字符串或者数字算出来的int类型的数据 - 如何获取哈希值
Object类中的public int hashCode(): 返回对象的哈希编码值 - 哈希值的特点
同一个对象多次调用hashCode()方法返回的哈希值是相同的
默认情况下,不同对象的哈希值是不同的。而重写hashCode()方法,可以实现让不同的对象哈希值相同
2.4 哈希表结构
- jdk1.8以前
底层结构:数组+链表
数组长度默认为16,加载因子为0.75
首先会获取元素的哈希值,计算出在数组中应存入的索引,判断该索引是否为null,如果是null则直接添加,如果不是null,则与链表中所有的元素通过equals方法比较属性值,只要有一个相同,就不存,如果都不一样,才会存入集合。
创建一个默认长度为16, 默认加载因子为0.75的数组,数组名为table
HashSet<String> hm = new HashSet<>();
- jdk1.8以后
假设某一个位置所挂载的链表长度太长的话,后续有元素插入到此位置,会进行很多次比较,影响效率,不利于添加,也不利于查询。
底层结构:哈希表。(数组、链表、红黑树的结合体)
如果节点个数少于等于8个 数组 + 链表
节点个数多余8个 数组 + 红黑树
2.5 HashSet集合练习
- 案例需求
创建一个存储Person对象的集合,存储多个Person对象,使用程序实现在控制台遍历该集合。
要求:学生对象的成员变量值相同,我们就认为是同一个对象 - 代码实现
Person类中重写的方法
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
if (Double.compare(person.age, age) != 0) return false;
return Objects.equals(name, person.name);
}
@Override
public int hashCode() {
int result;
long temp;
result = name != null ? name.hashCode() : 0;
temp = Double.doubleToLongBits(age);
result = 31 * result + (int) (temp ^ (temp >>> 32));
return result;
}
主函数
public static void main(String[] args) {
HashSet<Person> hs = new HashSet<>();
// 如果不重写equals与hashcode方法,因为每个对象的地址值不同,Object类中的哈希值的计算方法就是根据地址来的,所以这些的哈希值也不同,所以存储的位置各不相同,所以都会存入进去,不满足题目条件
// 重写hashCode方法,让他们计算的哈希值一样
// 重写equals方法,设置自己想要的比较规则
Person person1 = new Person("穆罕默德", 63);
Person person2 = new Person("牛顿", 85);
Person person3 = new Person("耶稣", 33);
Person person4 = new Person("释迦牟尼", 81);
Person person5 = new Person("释迦牟尼", 81);
hs.add(person1);
hs.add(person2);
hs.add(person3);
hs.add(person4);
hs.add(person5);
for (Person h : hs) {
System.out.println(h);
}
}
- 结论
如果HashSet集合要存储自定义对象,那么必须重写hashCode和equals方法
2.6 小结
感谢大家的阅读,如果文章中有不懂的地方,或者是有错误,就请评论或者私信我吧,感谢大家的支持!