数据结构之树以及TreeSet,HashSet底层原理讲解


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

compareTo方法解释
使用两种不同的方式,最后生成的红黑树不同。但是在取出结果时,都是先获取左边,在获取中间,在获取右边。
所以最后取出的结果也会有差异!

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.7

  • jdk1.8以后
    假设某一个位置所挂载的链表长度太长的话,后续有元素插入到此位置,会进行很多次比较,影响效率,不利于添加,也不利于查询。
    底层结构:哈希表。(数组、链表、红黑树的结合体)
    如果节点个数少于等于8个 数组 + 链表
    节点个数多余8个 数组 + 红黑树
    存入数据的流程
    哈希表结构1.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 小结

Set集合总结
感谢大家的阅读,如果文章中有不懂的地方,或者是有错误,就请评论或者私信我吧,感谢大家的支持!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值