1 今日内容
1、泛型
2、TreeSet集合
3、红黑树
- 树
- 二叉树
- 二叉树平衡树
- 红黑树
2 泛型
2.1 泛型概述
泛型是jdk1.5以后所提供的一个新特性,一般情况在是在集合中进行使用,作用就是用来限定集合中可以存储的元素的数据类型。本质上是一种参数化类型,所谓的参数化类型就是在定义时
候,并不知道其具体的类型到底是什么, 只有在使用的时候才能知道它的类型。
ArrayList集合的定义:
public class ArrayList<E> {} // E表示的就是泛型
创建ArrayList集合对象:
ArrayList<String> al = new ArrayList<String>() ; // 创建集合对象的时候,使用了泛型
ArrayList<Integer> al = new ArrayList<Integer>() ;
泛型好处:
1、让运行期的错误提前到编译期
2、省略了强制类型转换
注意:如果在创建集合对象的时候没有指定泛型,默认情况下就是Object类型,那么此时这个集合中是可以存储任意类型的数据。
2.2 自定义泛型
泛型的定义格式: <数据类型1 , 数据类型2 , …>
泛型按照定义位置的不同,可以分为如下几种:
1、泛型类 : 把泛型定义在类上
2、泛型接口 : 把泛型定义在接口上
3、泛型方法 : 把泛型定义在方法上
2.2.1 自定义泛型类
泛型类:
//就是一个泛型类
public class Box<E> { // E仅仅表示一个占位符,定义了一种数据类型为E ,但是此时并不确定这个E到底是什么类型
private E element;
public E getElement() {
return element;
}
public void setElement(E element) {
this.element = element;
}
}
使用泛型类:
Box<String> box1 = new Box<>(); // 使用泛型
box1.setElement("给小丽的土味情话");
String element1 = box1.getElement();
System.out.println(element1);
Box<Integer> box2 = new Box<>(); // 使用泛型
box2.setElement(19);
Integer element2 = box2.getElement();
System.out.println(element2);
2.2.2 自定义泛型方法
格式:
修饰符 <类型> 返回值类型 方法名(类型 变量名) { }
示例代码:
public class GenericityMethod2 {
public static void main(String[] args) {
ArrayList<String> list1 = addElement(new ArrayList<String>(), "a", "b", "c", "d");
System.out.println(list1);
ArrayList<Integer> list2 = addElement(new ArrayList<Integer>(), 1, 2, 3, 4);
System.out.println(list2);
}
// 泛型方法
// 好处:大大提高了代码的灵活性
public static <T> ArrayList<T> addElement(ArrayList<T> list , T t1 ,T t2 ,T t3 ,T t4){
list.add(t1);
list.add(t2);
list.add(t3);
list.add(t4);
return list;
}
}
2.2.3 自定义泛型接口
格式:
修饰符 interface 接口名<类型> { }
示例代码:
interface Genericity<E>{
public abstract void method(E e);
}
子类:
// 子类不是泛型类
class GenericityImpl2 implements Genericity<Integer>{
@Override
public void method(Integer integer) {
System.out.println(integer);
}
}
// 子类是泛型类
class GenericityImpl1<E> implements Genericity<E>{ // 使用的比较多
@Override
public void method(E e) {
System.out.println(e);
}
}
2.3 泛型通配符
分类:
? : 表示任意的类型
?extends E : 向上限定,?表示的是E或者E的子类
?super E : 向下限定,?表示的是E或者E的父类
常见的使用场景:在方法定义上进行使用,方法的参数需要的是一个集合类型,此时就可以使用泛型通配符来指定这个集合中可以存储的元素的类型,目的:为了提高方法的扩展性
// 调用method1方法的时候需要给我传递一个Collection集合的子类对象,并且在这个集合中可以存储任意的元素
public static void method1(Collection<?> col) {}
// 调用method2方法的时候需要给我传递一个Collection集合的子类对象,在这个集合对象中可以存储的元素的数据类型是Animal或者Animal的子类
public static void method2(Collection<? extends Animal> col) {}
// 调用method3方法的时候需要给我传递一个Collection集合的子类对象,在这个集合对象中可以存储的元素的数据类型是Animal或者Animal的父类
public static void method3(Collection<? super Animal> col) {}
2.4 小结
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cWaOKwTf-1635151445754)(images/image-20210427103254901.png)]
3 Set集合
3.2 概述
Set集合是Collection集合体系中的另外一个分支,是一个接口。
特点:
1、元素无序(不保证存和取的顺序一致)
2、元素不可以重复(可以保证元素的唯一性)
3、元素无索引(不能使用普通for循环进行遍历)
Set集合的遍历方式:
1、迭代器
2、增强for
3、forEach方法
3.2 基本使用
使用思想:
1、创建集合对象
2、添加元素
3、遍历集合
代码实现:
public class MySet1 {
public static void main(String[] args) {
// 创建集合对象
Set<String> set = new TreeSet<>();
set.add("ccc");
set.add("aaa");
set.add("aaa");
set.add("bbb");
// 迭代器
Iterator<String> it = set.iterator();
while (it.hasNext()){
String s = it.next();
System.out.println(s);
}
System.out.println("-----------------------------------");
// 增强for
for (String s : set) {
System.out.println(s);
}
System.out.println("-----------------------------------");
// forEach方法
set.forEach( s -> System.out.println(s));
}
}
4 TreeSet集合(面试)
4.1 概述
特点:
1、元素无序(不保证存和取的顺序一致)
2、元素不可以重复(可以保证元素的唯一性)
3、元素无索引(不能使用普通for循环进行遍历)
4、可以对元素按照指定的规则进行排序
在使用TreeSet集合的时候,就必须去指定排序规则,如果没有指定排序规则,此时程序会报错。
4.2 排序规则
4.2.1 自然排序
自然排序的使用:
1、如果我们在创建TreeSet集合对象的时候,采用的是无参数的构造方法,那么此时其实就是使用自然排序对元素进行排序
2、但是要使用自然排序进行排序,元素必须去实现Comparable接口,如果没有实现就报错
排序规则:根据Comparable接口中的compareTo方法的返回值来决定元素在集合中的位置
1、如果返回的int类型的值是小于0的,此时认为当前所添加的元素比集合中的元素小,此时就把这个元素存储到集合的左侧
2、如果返回的int类型的值是等于0的,此时就认为当前要存储的元素在集合中已经存在了,就不会把这个元素再次添加到集合中
3、如果返回的int类型的值是大于0的,此时就认为当前所要添加的元素比集合中的元素大,此时就把这个元素存储到了集合的右侧
Student类:
public class Student implements Comparable<Student>{
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public int compareTo(Student o) {
return this.age - o.age;
}
}
测试类:
public class MyTreeSet2 {
public static void main(String[] args) {
TreeSet<Student> ts = new TreeSet<>();
Student s1 = new Student("zhangsan",28);
Student s2 = new Student("lisi",27);
Student s3 = new Student("wangwu",29);
Student s4 = new Student("zhaoliu",28);
Student s5 = new Student("qianqi",30);
ts.add(s1);
ts.add(s2);
ts.add(s3);
ts.add(s4);
ts.add(s5);
System.out.println(ts);
}
}
存储结构:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YmAyrHVm-1635151445758)(images/image-20210427114306805.png)]
4.2.2 练习题
需求:使用TreeSet集合存储学生对象,按照年龄的从小到大进行排序,如果年龄相同,则比较姓名
学生类:
public class Student implements Comparable<Student>{
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public int compareTo(Student o) {
// 比较年龄
int result = this.age - o.age;
// 如果年龄是相同的,此时就需要比较姓名
int result2 = (result == 0 ) ? this.name.compareTo(o.name) : result ;
return result2 ;
}
}
测试类:
public class MyTreeSet2 {
public static void main(String[] args) {
TreeSet<Student> ts = new TreeSet<>();
Student s1 = new Student("zhangsan",28);
Student s2 = new Student("lisi",27);
Student s3 = new Student("wangwu",29);
Student s4 = new Student("zhaoliu",28);
Student s5 = new Student("qianqi",30);
ts.add(s1);
ts.add(s2);
ts.add(s3);
ts.add(s4);
ts.add(s5);
System.out.println(ts);
}
}
4.2.3 比较器排序
如何使用比较器排序?
在创建TreeSet集合对象的时候选择有参构造方法即可。
public TreeSet(Comparator<? super E> comparator)
Comparator接口的定义
public interface Comparator<T> {
int compare(T o1, T o2);
}
比较器排序的规则:
排序规则:根据Comparator接口中的compare方法的返回值来决定元素在集合中的位置
1、如果返回的int类型的值是小于0的,此时认为当前所添加的元素比集合中的元素小,此时就把这个元素存储到集合的左侧
2、如果返回的int类型的值是等于0的,此时就认为当前要存储的元素在集合中已经存在了,就不会把这个元素再次添加到集合中
3、如果返回的int类型的值是大于0的,此时就认为当前所要添加的元素比集合中的元素大,此时就把这个元素存储到了集合的右侧
代码演示:
public class MyTreeSet4 {
public static void main(String[] args) {
// 匿名内部类的使用方式
TreeSet<Teacher> ts = new TreeSet<>(new Comparator<Teacher>() {
@Override
public int compare(Teacher o1, Teacher o2) {
//o1表示现在要存入的那个元素
//o2表示已经存入到集合中的元素
//主要条件
int result = o1.getAge() - o2.getAge();
//次要条件
result = result == 0 ? o1.getName().compareTo(o2.getName()) : result;
return result;
}
});
Teacher t1 = new Teacher("zhangsan",23);
Teacher t2 = new Teacher("lisi",22);
Teacher t3 = new Teacher("wangwu",24);
Teacher t4 = new Teacher("zhaoliu",24);
ts.add(t1);
ts.add(t2);
ts.add(t3);
ts.add(t4);
System.out.println(ts);
}
}
public class MyTreeSet4 {
public static void main(String[] args) {
// lambda表达式
TreeSet<Teacher> ts = new TreeSet<>(( o1, o2) ->{
//主要条件
int result = o1.getAge() - o2.getAge();
//次要条件
result = result == 0 ? o1.getName().compareTo(o2.getName()) : result;
return result;
});
Teacher t1 = new Teacher("zhangsan",23);
Teacher t2 = new Teacher("lisi",22);
Teacher t3 = new Teacher("wangwu",24);
Teacher t4 = new Teacher("zhaoliu",24);
ts.add(t1);
ts.add(t2);
ts.add(t3);
ts.add(t4);
System.out.println(ts);
}
}
4.3 小结
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-snVGpPEA-1635151445759)(images/image-20210427151417129.png)]
5 数据结构(树)
5.1 树
要想了解红黑树这种数据结构,那么我们首先要了解一下树这种数据结构。提到树,我们能想到的是我们现实生活中的树,每一个树都存在一个树根,现实生活中的树是从下往上长,而我们编程语言中的树是从上往下长。下图展示的就是一个树的结构:
关于树这种数据结构,我们首先需要了解一下一些相关的名词,如下表所示:
名词 | 含义 |
---|---|
节点 | 指树中的一个元素 |
节点的度 | 节点拥有的子树的个数,二叉树的度不大于2 |
叶子节点 | 度为0的节点,也称之为终端结点 |
父节点 | 若一个节点含有子节点,则这个节点称之为其子节点的父节点 |
子节点 | 子节点是父节点的下一层节点 |
兄弟节点 | 拥有共同父节点的节点互称为兄弟节点 |
高度 | 最底层的叶子结点的高度为1,叶子结点的父节点高度为2,以此类推,根节点的高度最高 |
层 | 根节点在第一层,以此类推 |
树还具有的特点:
- 每一个节点有零个或者多个子节点
- 没有父节点的节点称之为根节点
- 每一个非根节点有且只有一个父节点
为了满足不同的需求,后期基于基本结构的树又衍生出来很多种树;
5.2 二叉树
二叉查找树(Binary Search Tree),又称二叉排序树(Binary Sort Tree),亦称二叉搜索树。
二叉查找树的特点:
- 每一个节点最多有两个子节点
- 左子树上所有的节点的值均小于他的根节点的值
- 右子树上所有的节点值均大于或者等于他的根节点的值
研究数据结构的网站:https://www.cs.usfca.edu/~galles/visualization/Algorithms.html
使用二叉搜索树存储数据:20 , 18 , 23 , 22 , 17 , 24 , 19,19
最终形成的树的结构如下所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-K0NDgyL9-1635151445762)(images/image-20210427154151896.png)]
使用二叉搜索树存储数据的时候存在一定的弊端:会出现"瘸腿"的现象 ,出现该现象以后,树的高的就比较高,树的高度越高,搜索数据的效率越低。
5.3 平衡二叉树
5.3.1 概述
平衡二叉树保持平衡有两个原则:
-
它的左右两个子树的高度差的绝对值不超过1
-
并且左右两个子树都是一棵平衡二叉树
5.3.2 左旋
在构建一棵平衡二叉树的过程中,当有新的节点要插入进来以后,,我们需要校验新节点的插入是否破坏了树的这种平衡结构,如果是,则需要对树做调整,以改变树的结构让其
成为一个平衡二叉树;而调整就需要涉及到树的旋转;
左旋就是将节点的右支往左拉,右子节点变成父节点,并把晋升之后多余的左子节点出让给降级节点的右子节点;
下图的动画展示的就是一个树的左旋过程:
![1562644020804](images/121gif.gif)
5.3.3 右旋
将节点的左支往右拉,左子节点变成了父节点,并把晋升之后多余的右子节点出让给降级节点的左子节点
![1562644020804](images/122gif.gif)
5.4 平衡二叉树调整
5.4.1 左左
概述:左左即为在原来平衡的二叉树上,在节点的左子树的左子树下,有新节点插入。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5O8osSp5-1635151445764)(images/image-20210427163316859.png)]
左左调整其实比较简单,只需要对节点进行右旋即可,如下图,对节点"10"进行右旋:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-h2CrVrxO-1635151445765)(images/image-20210427163356095.png)]
5.4.2 左右
概述:左右即为在原来平衡的二叉树上,在节点的左子树的右子树下,有新节点插入
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8sCQYsQn-1635151445766)(images/image-20210427163650014.png)]
左右这种情况,进行一次旋转是不能满足我们的条件的,正确的调整方式是,将左右进行第一次旋转,将左右先调整成左左,然后再对左左进行调整,从而使得二叉树平衡。即先对上图的节点"7"进行左旋,使得二叉树变成了左左,之后再对"11"节点进行右旋,此时二叉树就调整完成,如下图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WH1wEluw-1635151445767)(images/image-20210427163802018.png)]
5.4.3 右左
概述:右左即为在原来平衡的二叉树上,在节点的右子树的左子树下,有新节点插入
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IuhzoF0g-1635151445769)(images/image-20210427164644855.png)]
调整策略:先进行右旋,然后在进行左旋
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TFOLeNCf-1635151445770)(images/image-20210427164817423.png)]
5.4.4 右右
概述:右右即为在原来平衡的二叉树上,在节点的右子树的右子树下,有新节点插入
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fHLBMLRk-1635151445771)(images/image-20210427164458662.png)]
调整策略:右右只需对节点进行一次左旋即可调整平衡,如下图,对"11"节点进行左旋。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ExJg6vJ9-1635151445772)(images/image-20210427164559173.png)]
5.5 红黑树
h2CrVrxO-1635151445765)]
5.5.2 左右
概述:左右即为在原来平衡的二叉树上,在节点的左子树的右子树下,有新节点插入
[外链图片转存中…(img-8sCQYsQn-1635151445766)]
左右这种情况,进行一次旋转是不能满足我们的条件的,正确的调整方式是,将左右进行第一次旋转,将左右先调整成左左,然后再对左左进行调整,从而使得二叉树平衡。即先对上图的节点"7"进行左旋,使得二叉树变成了左左,之后再对"11"节点进行右旋,此时二叉树就调整完成,如下图所示:
[外链图片转存中…(img-WH1wEluw-1635151445767)]
5.5.3 右左
概述:右左即为在原来平衡的二叉树上,在节点的右子树的左子树下,有新节点插入
[外链图片转存中…(img-IuhzoF0g-1635151445769)]
调整策略:先进行右旋,然后在进行左旋
[外链图片转存中…(img-TFOLeNCf-1635151445770)]
5.5.4 右右
概述:右右即为在原来平衡的二叉树上,在节点的右子树的右子树下,有新节点插入
[外链图片转存中…(img-fHLBMLRk-1635151445771)]
调整策略:右右只需对节点进行一次左旋即可调整平衡,如下图,对"11"节点进行左旋。
[外链图片转存中…(img-ExJg6vJ9-1635151445772)]