Day18
红黑树
- 平衡二叉B树
- 每一个节点可以是红或者黑
- 红黑树不是高度平衡的,它的平衡是通过“自己的红黑规则”实现的
红黑规则
- 每一个节点或是红色的,或是黑色的
- 根节点必须是黑色
- 如果一个节点没有子节点或者父节点,则该节点相应的指针属性为Nil,这些Nil视为叶节点,每个叶节点(Nil)是黑色的
- 如果某一个节点是红色的,那么它的子节点必须是黑色的(不能出现两个红色节点相连的情况)
- 对每一个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点;
案例:成绩排序
创建3个学生对象,属性为(姓名,语文成绩,数学成绩,英语成绩),按照总分从低到高输出到控制台;
import java.text.Collator;
import java.util.Locale;
public class Student implements Comparable<Student>{
private String name;
private int chinese;
private int math;
private int english;
public Student() {
}
public Student(String name, int chinese, int math, int english) {
this.name = name;
this.chinese = chinese;
this.math = math;
this.english = english;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", chinese=" + chinese +
", math=" + math +
", english=" + english +
'}'+"总成绩:"+getSum();
}
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;
}
public int getSum(){
int sum = chinese + math + english;
return sum;
}
@Override
public int compareTo(Student o) {
//按总成绩排序
int result = this.getSum() - o.getSum();
//总成绩相同,按语文成绩排序
result = result == 0 ? this.chinese - o.chinese : result;
//语文成绩也相同,按数学成绩排序
result = result == 0 ? this.math - o.math : result;
//语数成绩都相同,则英语成绩也相同,按姓名排序
Collator col = Collator.getInstance(Locale.CHINESE);
result = result == 0 ? col.compare(this.name,o.name) : result;
return result;
}
}
import java.util.TreeSet;
public class StudentTest {
public static void main(String[] args) {
TreeSet<Student> ts = new TreeSet<>();
Student st1 = new Student("肖恩",90,90,80);
Student st2 = new Student("阿衰",90,90,80);
Student st3 = new Student("吉吉",100,100,100);
ts.add(st1);
ts.add(st2);
ts.add(st3);
System.out.println("姓名\t语文\t数学\t英语\t总分");
for (Student t : ts) {
System.out.println(t.getName()+"\t"+t.getChinese()+"\t\t"+t.getMath()+"\t\t"+t.getEnglish()+"\t\t"+t.getSum());
}
}
}
运行结果:
HashSet集合特点
- 底层数据结构是哈希表
- 不能保证存储和取出的数据完全一致
- 没有带索引的方法,所以不能使用普通for循环遍历
- 由于是Set集合,所以元素唯一
示例:
import java.util.HashSet;
import java.util.Iterator;
public class HashTest {
public static void main(String[] args) {
HashSet<String> hs = new HashSet<>();
hs.add("Hello");
hs.add("world");
hs.add("java");
hs.add("java");
hs.add("java");
Iterator<String> it = hs.iterator();
while (it.hasNext()){
System.out.println(it.next());
}
System.out.println("----------");
for (String h : hs) {
System.out.println(h);
}
}
}
运行结果:
哈希值
哈希值:是JDK根据对象的地址或者属性值,算出来的int类型的整数
Object类中有一个方法可以获取对象的哈希值
- public int hashCode():根据对象的地址计算出来的哈希值
对象的哈希值特点
- 如果没有重写hashCode方法,那么是根据对象的地址值计算出来的哈希值;同一个对象多次调用hashCode()方法返回的哈希值是相同的,不同的对象的哈希值是不一样的。
- 如果重写了hashCode方法,一般都是通过对象的属性值计算出哈希值;如果不同的对象属性值是一样的,那么计算出来的哈希值也是一样的。
案例:HashSet集合存储学生对象并遍历
创建一个HashSet集合,存储多个学生对象,并进行遍历;要求:学生对象的成员变量值相同,我们就认为是同一个对象;
public class Student {
private String name;
private int age;
public Student() {
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
if (age != student.age) return false;
return name != null ? name.equals(student.name) : student.name == null;
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + age;
return result;
}
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 +
'}';
}
}
import java.util.HashSet;
public class StudentTest {
public static void main(String[] args) {
HashSet<Student> hs = new HashSet<>();
Student st1 = new Student("图图",8);
Student st2 = new Student("图图",8);
Student st3 = new Student("小美",7);
hs.add(st1);
hs.add(st2);
hs.add(st3);
System.out.println(hs);
}
}
运行结果:
总结:如果HashSet集合要存储自定义对象,则要重写、hashCode和equals方法;
练习1
假如你有3个室友,请使用HashSet集合保存3个室友的信息;信息如:小颖,18;小冰,20;小幂,19;
室友以对象形式存在,包含姓名和年龄两个属性;使用代码保证集合中同名同年龄的对象只有一份;(相同姓名和年龄的对象认为是同一个对象)
要保证集合中同名同年龄的对象只有一份,要重写equals和hashCode方法
public class ShiYou {
private String name;
private int age;
public ShiYou() {
}
public ShiYou(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ShiYou shiYou = (ShiYou) o;
if (age != shiYou.age) return false;
return name != null ? name.equals(shiYou.name) : shiYou.name == null;
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + age;
return result;
}
@Override
public String toString() {
return "ShiYou{" +
"name='" + name + '\'' +
", 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;
}
}
测试类
import java.util.HashSet;
public class ShiYouTest {
public static void main(String[] args) {
HashSet<ShiYou> hs = new HashSet<>();
ShiYou s1 = new ShiYou("小颖",18);
ShiYou s2 = new ShiYou("小冰",20);
ShiYou s3 = new ShiYou("小幂",19);
hs.add(s1);
hs.add(s2);
hs.add(s3);
System.out.println(hs);
}
}
运行结果:
Map
Map集合概述
- Interface Map<K,V> K:键的数据类型;V:值的数据类型
- 键不能重复,值可以重复
- 键和值是一一对应的,每一个键只能找到自己对应的值
- (键+值)这个整体我们称之为”键值对“或者”键值对对象“,在Java中叫做”Entry对象“
创建Map集合的对象
- 多态的方式
- 具体的实现类HashMap
示例:
import java.util.HashMap;
import java.util.Map;
public class MapTest {
public static void main(String[] args) {
Map<String,String> map = new HashMap<>();
map.put("2020001","花花");
map.put("2020002","朵朵");
map.put("2020003","图图");
System.out.println(map); //{2020003=图图, 2020002=朵朵, 2020001=花花}
}
}
Map集合的基本功能
方法名 | 说明 |
---|---|
V put(K key,V value) | 添加元素 |
V remove(Object key) | 根据键值删除键值对元素 |
void clear() | 移除所有的键值对元素 |
boolean containsKey(Object key) | 判断集合是否包含指定的键 |
boolean containsValue(Object value) | 判断集合是否包含指定的值 |
boolean isEmpty() | 判断集合是否为空 |
int size() | 集合的长度,也就是集合中键值对的个数 |
import java.util.HashMap;
import java.util.Map;
public class MapTest {
public static void main(String[] args) {
Map<String,String> map = new HashMap<>();
map.put("2020001","花花");
map.put("2020002","朵朵");
map.put("2020003","图图");
map.put("2020004","洛洛");
map.put("2020005","香香");
//如果要添加的键不存在,那么会把键值对都添加到集合中
//如果要添加的键是存在的,那么会覆盖原先的值,把原先的值返回
String s = map.put("2020005", "丁丁");
System.out.println(s); //香香
System.out.println(map); //{2020003=图图, 2020002=朵朵, 2020001=花花, 2020005=丁丁, 2020004=洛洛}
String s1 = map.remove("2020005");
System.out.println(s1); //丁丁
System.out.println(map); //{2020003=图图, 2020002=朵朵, 2020001=花花, 2020004=洛洛}
boolean b1 = map.containsKey("2020001");
boolean b2 = map.containsKey("2020005");
System.out.println(b1); //true
System.out.println(b2); //false
boolean b3 = map.containsValue("图图");
boolean b4 = map.containsValue("香香");
System.out.println(b3); //true
System.out.println(b4); //false
boolean empty1 = map.isEmpty();
System.out.println(empty1); //false
int size = map.size();
System.out.println(size); //4
map.clear();
System.out.println(map); //{}
boolean empty2 = map.isEmpty();
System.out.println(empty2); //true
}
}
Map集合的获取功能
方法名 | 说明 |
---|---|
Set keySet() | 获取所有键的集合 |
V get(Object key) | 根据键获取值 |
Set<Map.Entry<K,V>> entrySet() | 获取所有键值对对象的集合 |
K getKey() | 获得键 |
V getValue() | 获得值 |
Map集合的遍历方式一
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class MapTest {
public static void main(String[] args) {
Map<String,String> map = new HashMap<>();
map.put("2020001","花花");
map.put("2020002","朵朵");
map.put("2020003","图图");
map.put("2020004","洛洛");
map.put("2020005","香香");
//获取到所有的键
Set<String> keys = map.keySet();
for (String key : keys) {
//通过每一个键获取对应的值
String value = map.get(key);
System.out.println(key+"------"+value);
}
}
}
运行结果:
Map集合的遍历方式二
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class MapTest {
public static void main(String[] args) {
Map<String,String> map = new HashMap<>();
map.put("2020001","花花");
map.put("2020002","朵朵");
map.put("2020003","图图");
map.put("2020004","洛洛");
map.put("2020005","香香");
//首先要获取到所有的键值对对象
//Set集合中装的是键值对对象(Entry)对象
//而Entry里面装的是键和值
Set<Map.Entry<String,String>> entries = map.entrySet();
for (Map.Entry<String, String> entry : entries) {
//获取每一个键值对对象
String key = entry.getKey();
String value = entry.getValue();
System.out.println(key+"---------"+value);
}
}
}
运行结果:
HashMap
- HashMap底层是哈希表结构的
- 依赖hashCode方法和equals方法保证键的唯一
- 如果键要存储的是自定义对象,需要重写hashCode和equals方法
案例:HashMap集合存储自定义对象并遍历
创建一个HashMap集合,键是学生对象(Student),值是籍贯(String)。存储三个键值对元素,并遍历;
public class Student {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public Student() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public void setAge(int age) {
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
if (age != student.age) return false;
return name != null ? name.equals(student.name) : student.name == null;
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + age;
return result;
}
}
测试类
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class StudentTest {
public static void main(String[] args) {
HashMap<Student,String> hm = new HashMap<>();
Student st1 = new Student("小明",17);
Student st2 = new Student("小樱",16);
Student st3 = new Student("芝士",17);
hm.put(st1,"上海");
hm.put(st2,"北京");
hm.put(st3,"南京");
//先获取集合中所有的键,再根据键去获取对应的值
Set<Student> key = hm.keySet();
for (Student student : key) {
String value = hm.get(student);
System.out.println(student+"--------"+value);
}
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
//先获取集合中所有的键值对,在获取键和对应的值
Set<Map.Entry<Student, String>> entries = hm.entrySet();
for (Map.Entry<Student, String> entry : entries) {
System.out.println(entry.getKey()+"--------"+entry.getValue());
}
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
hm.forEach((Student key1,String value)->{
System.out.println(key1+"--------"+value);
});
}
}
运行结果:
TreeMap
- TreeMap底层是红黑树结构的
- 依赖自然排序或比较器排序,对键进行排序
- 如果键存储的是自定义对象,需要实现Comparable接口或者在创建TreeMap对象时候给出比较器排序规则
案例
创建一个TreeMap集合,键是学生对象(Student),值是籍贯(String);学生属性姓名和年龄,按照年龄进行排序并遍历。
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 boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
if (age != student.age) return false;
return name != null ? name.equals(student.name) : student.name == null;
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + age;
return result;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public int compareTo(Student o) {
//按年龄排序
int result = this.age - o.age;
//若年龄相同按姓名排序
result = result ==0 ? this.name.compareTo(o.name) : result;
return result;
}
}
import java.util.Comparator;
import java.util.TreeMap;
public class StudentTest {
public static void main(String[] args) {
//自然排序
TreeMap<Student,String> tm = new TreeMap<>();
Student st1 = new Student("小明",17);
Student st2 = new Student("小樱",16);
Student st3 = new Student("芝士",17);
tm.put(st1,"上海");
tm.put(st2,"北京");
tm.put(st3,"南京");
tm.forEach((Student key, String value) -> {
System.out.println(key+"--------"+value);
});
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
//比较器排序
TreeMap<Student,String> tm1 = new TreeMap<>(new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
//按年龄大小排序
int result = o1.getAge() - o2.getAge();
//年龄相同,按姓名排序
result = result ==0 ? o1.getName().compareTo(o2.getName()) : result;
return result;
}
});
tm1.put(st1,"上海");
tm1.put(st2,"北京");
tm1.put(st3,"南京");
tm1.forEach((Student key,String value) -> {
System.out.println(key+"------"+value);
});
}
}
运行结果:
练习1
假如你有3个室友,请使用HashSet集合保存3个室友的信息;信息如下:路飞,18;漩涡鸣人,17;工藤新一,19;
要求:室友以对象形式存在,包含姓名和年龄两个属性;使用代码保证集合中同名同年龄的对象只有一份;(相同姓名和年龄的对象认为是同一个对象)
public class Shiyou {
private String name;
private int age;
public Shiyou(String name, int age) {
this.name = name;
this.age = age;
}
public Shiyou() {
}
@Override
public String toString() {
return "Shiyou{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Shiyou shiyou = (Shiyou) o;
if (age != shiyou.age) return false;
return name != null ? name.equals(shiyou.name) : shiyou.name == null;
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + age;
return result;
}
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;
}
}
测试类:
import java.util.HashSet;
public class ShiyouTest {
public static void main(String[] args) {
HashSet<Shiyou> hs = new HashSet<>();
Shiyou s1 = new Shiyou("蒙奇D-路飞",18);
Shiyou s2 = new Shiyou("旋涡鸣人",17);
Shiyou s3 = new Shiyou("工藤新一",19);
Shiyou s4 = new Shiyou("旋涡鸣人",17);
hs.add(s1);
hs.add(s2);
hs.add(s3);
hs.add(s4);
//虽然s2和s4地址值不一样,但他们的内容一样,因为已经重写了equals和hashCode方法,所以不会重复添加
System.out.println("我的室友是"+hs);
}
}
运行结果:
练习2
请使用HashMap集合保存街道两旁的店铺名称;使用门牌号作为键,店铺名作为值,然后使用三种方式遍历输出;
信息如下:
2020001,小万面馆
2020002,小图粥馆
2020003,小蕊米馆
2020004,小福茶馆
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class Lian2 {
public static void main(String[] args) {
HashMap<Integer,String> hm = new HashMap<>();
hm.put(2020001,"小万面馆");
hm.put(2020002,"小图粥馆");
hm.put(2020003,"小蕊米馆");
hm.put(2020004,"小福茶馆");
//遍历方式一,先获取所有的键,再根据键找值
Set<Integer> set = hm.keySet();
for (Integer key : set) {
System.out.println(key+"--"+hm.get(key));
}
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>");
//遍历方式二,先获取所有的键值对对象在获取键值对中的键和值
Set<Map.Entry<Integer, String>> entries = hm.entrySet();
for (Map.Entry<Integer, String> entry : entries) {
System.out.println(entry.getKey()+"--"+entry.getValue());
}
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>");
//遍历方式三,接口中默认的forEach方法配合Lambda表达式
hm.forEach((key,value)->{
System.out.println(key+"--"+value);
});
}
}
运行结果:
练习3
请使用TreeMap集合保存学生信息,要求以学生对象为键,家庭住址为值,并按照学生的年龄从大到小排序后输出;
信息如下:
18岁的张三,北京
20岁的李四,上海
19岁的王五,天津
21岁的赵六,北京
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
public class StudentTest {
public static void main(String[] args) {
//注意如果没有让Student类实现自然排序接口,那么就需要创建TreeMap对象的时候,写自定义排序规则
//且TreeMap集合并不依赖对象的hashCode方法和equals方法,因此学生类中hashCode方法和equals方法写不写都行;
//但是如果是HashMap集合,就必须写;
TreeMap<Student,String> tm = new TreeMap<>((s1,s2)->{
//降序排列
int result = s2.getAge() - s1.getAge(); //主要条件
return result==0 ? s1.getName().compareTo(s2.getName()) : result; //次要条件
});
//创建对象添加信息
Student s1 = new Student("张三",18);
Student s2 = new Student("李四",20);
Student s3 = new Student("王五",19);
Student s4 = new Student("赵六",21);
//添加入集合
tm.put(s1,"北京");
tm.put(s2,"上海");
tm.put(s3,"天津");
tm.put(s4,"北京");
//遍历方式一
Set<Student> set = tm.keySet();
for (Student key : set) {
因为重写了toString方法,所以可以直接打印 key
System.out.println(key+"--"+tm.get(key));
}
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>");
//遍历方式二
Set<Map.Entry<Student, String>> entries = tm.entrySet();
for (Map.Entry<Student, String> entry : entries) {
System.out.println(entry.getKey()+"--"+entry.getValue());
}
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>");
//遍历方式三
tm.forEach((key,value)->{
System.out.println(key+"--"+value);
});
}
}
运行结果: