05-Collection、List、泛型、数据结构
-
Collection集合------------------>必须掌握
-
单列集合继承体系
-
Collection常用方法
-
-
迭代器------------------>必须掌握
-
迭代器的使用
-
迭代器的原理
-
增强for循环
-
-
泛型------------------>掌握使用泛型-----相对难点
-
定义和使用含有泛型的类
-
定义和使用含有泛型的方法
-
定义和使用含有泛型的接口
-
泛型通配符
-
-
数据结构--------->了解
-
常见数据结构的特点
-
-
List集合------------------>必须掌握
-
List常用方法
-
List实现类的使用
-
第一章 Collection集合
1.1 集合概述
-
概述: 集合是java中提供的一种容器,可以用来存储多个引用数据类型的数据
-
分类:
-
单列集合: 以单个单个元素进行存储
-
双列集合: 以键值对的形式进行存储
-
-
集合与数组的区别:
-
长度:
-
数组长度是固定的
-
集合长度是不固定的
-
-
存储范围:
-
数组可以存储基本类型+引用类型 eg; int[],String[]
-
集合只能存储引用类型,如果要存储基本类型,需要存储基本类型对应的包装类类型 eg; ArrayList
,ArrayList
-
-
1.2 单列集合常用类的继承体系[重点]
-
单列集合: 以单个单个元素进行存储
-
单列集合继承体系:
-
Collection接口: 是所有单列集合的顶层父接口,所以Collection中定义了所有单列集合共有的方法
-
List接口: 继承Collection接口,特
-
特点: 元素有索引,元素可重复,元素存取有序
-
ArrayList类: 底层采用的是数组结构,查询快,增删慢
-
LinkedList类: 底层采用的是链表结构,查询慢,增删快
-
...
-
-
Set接口: 继承Collection接口,特点: 元素无索引,元素唯一(不可重复)
-
HashSet类: 底层采用的是哈希表结构,由哈希表结构保证元素唯一,元素存取无序
-
LinkedHashSet类:底层采用的是哈希表+链表结构,由哈希表结构保证元素唯一,由链表保证元素存取有序
-
TreeSet类:底层采用的是红黑树结构,可以对元素进行排序
-
....
-

-
-
1.3 Collection 常用功能
-
Collection是接口,只能通过其子类创建对象
-
Collection是所有单列集合的顶层父接口,所以所有单列集合都拥有Collection中的方法
-
常用方法:
-
public boolean add(E e): 把给定的对象添加到当前集合中 。 -
public void clear():清空集合中所有的元素。 -
public boolean remove(E e): 把给定的对象在当前集合中删除。 -
public boolean contains(Object obj): 判断当前集合中是否包含给定的对象。 -
public boolean isEmpty(): 判断当前集合是否为空。 -
public int size(): 返回集合中元素的个数。 -
public Object[] toArray(): 把集合中的元素,存储到数组中
/**
* Created by PengZhiLin on 2021/8/3 9:34
*/
public class Test {
public static void main(String[] args) {
// 创建Collection集合对象,限制集合元素的类型为String类型
Collection<String> col = new ArrayList<>();
?
//- `public boolean add(E e)`: 把给定的对象添加到当前集合中 。
col.add("谢霆锋");
col.add("王宝强");
col.add("贾乃亮");
col.add("陈羽凡");
// col:[谢霆锋, 王宝强, 贾乃亮, 陈羽凡]
System.out.println("col:" + col);
?
//- `public void clear()` :清空集合中所有的元素。
//col.clear();
//System.out.println("col:" + col);// col:[]
?
//- `public boolean remove(E e)`: 把给定的对象在当前集合中删除。
// 删除王宝强
boolean res1 = col.remove("王宝强");
System.out.println("res1:"+res1);// res1:true
System.out.println("col:" + col);// col:[谢霆锋, 贾乃亮, 陈羽凡]
?
// 删除潘粤明
boolean res2 = col.remove("潘粤明");
System.out.println("res2:"+res2);// res2:false
System.out.println("col:" + col);// col:[谢霆锋, 贾乃亮, 陈羽凡]
?
//- `public boolean contains(Object obj)`: 判断当前集合中是否包含给定的对象。
// 判断是否包含王宝强这个元素
boolean res3 = col.contains("王宝强");
System.out.println("res3:"+res3);// res3: false
?
// 判断是否包含贾乃亮这个元素
boolean res4 = col.contains("贾乃亮");
System.out.println("res4:"+res4);// res4: true
?
//- `public boolean isEmpty()`: 判断当前集合是否为空。
boolean res5 = col.isEmpty();
System.out.println("col是否为空:"+res5);// false
?
//- `public int size()`: 返回集合中元素的个数。
int size = col.size();
System.out.println("元素个数:"+size);// 3
?
//- `public Object[] toArray()`: 把集合中的元素,存储到数组中
Object[] arr = col.toArray();
System.out.println("arr:"+ Arrays.toString(arr));// arr:[谢霆锋, 贾乃亮, 陈羽凡]
}
}
? -
第二章 Iterator迭代器
2.1 Iterator接口
迭代的概念
-
概述:迭代即Collection集合元素的通用获取方式。在取元素之前先要判断集合中有没有元素,如果有,就把这个元素取出来,继续再判断,如果还有就再取出来。一直把集合中的所有元素全部取出。这种取出方式专业术语称为迭代。
-
迭代的步骤:
-
获取迭代器对象
-
使用迭代器对象判断集合中是否有元素可以取出
-
如果有元素可以取出,就直接取出来该元素,如果没有元素可以取出,就结束迭代
-
获取迭代器对象
Collection集合提供了一个获取迭代器的方法:
-
public Iterator iterator(): 获取集合对应的迭代器,用来遍历集合中的元素的。
Iterator迭代器对象的常用方法
-
public boolean hasNext():如果仍有元素可以迭代,则返回 true。 -
public E next():返回迭代的下一个元素。 -
void remove()删除当前迭代出来的元素 -
案例:
?
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
?
/**
* Created by PengZhiLin on 2021/8/3 9:51
*/
public class Test {
public static void main(String[] args) {
// 创建Collection集合对象,限制集合元素的类型为String类型
Collection<String> col = new ArrayList<>();
?
// 往集合中添加元素
col.add("谢霆锋");
col.add("王宝强");
col.add("贾乃亮");
col.add("陈羽凡");
?
// 获取迭代器
Iterator<String> it = col.iterator();
?
// 循环判断是否有元素可以取出来
while (it.hasNext()) {
// 说明有元素可以取出来,取出来
String e = it.next();
System.out.println("取出来的元素:" + e);
?
// 删除当前迭代出来的元素
it.remove();
}
?
System.out.println("col:"+col);// col:[]
?
}
}
?
2.2 迭代器的实现原理
-
迭代器的实现原理
我们在之前案例已经完成了Iterator遍历集合的整个过程。当遍历集合时,首先通过调用集合的iterator()方法获得迭代器对象,然后使用hashNext()方法判断集合中是否存在下一个元素,如果存在,则调用next()方法将元素取出,否则说明已到达了集合末尾,停止遍历元素。
Iterator迭代器对象在遍历集合时,内部采用指针的方式来跟踪集合中的元素。在调用Iterator的next方法之前,迭代器的索引位于第一个元素之前,不指向任何元素,当第一次调用迭代器的next方法后,迭代器的索引会向后移动一位,指向第一个元素并将该元素返回,当再次调用next方法时,迭代器的索引会指向第二个元素并将该元素返回,依此类推,直到hasNext方法返回false,表示到达了集合的末尾,终止对元素的遍历。

2.3 迭代器的常见问题
常见问题一
-
在进行集合元素获取时,如果集合中已经没有元素可以迭代了,还继续使用迭代器的next方法,将会抛出java.util.NoSuchElementException没有集合元素异常。
?
/**
* Created by PengZhiLin on 2021/8/3 10:02
*/
public class Test1 {
public static void main(String[] args) {
?
// 创建Collection集合对象,限制集合元素的类型为String类型
Collection<String> col = new ArrayList<>();
?
// 往集合中添加元素
col.add("谢霆锋");
col.add("王宝强");
col.add("贾乃亮");
col.add("陈羽凡");
?
// 获取迭代器
Iterator<String> it = col.iterator();
?
// 循环判断是否有元素可以取出来
while (it.hasNext()) {
// 说明有元素可以取出来,取出来
String e = it.next();
System.out.println("取出来的元素:" + e);
}
System.out.println("col:" + col);
?
// 在进行集合元素获取时,如果集合中已经没有元素可以迭代了,
// 还继续使用迭代器的next方法,将会抛出java.util.NoSuchElementException没有集合元素异常。
//String e = it.next();// 报NoSuchElementException没有集合元素异常。
?
?
// 解决办法: 重新获取迭代器
// 获取迭代器
Iterator<String> it1 = col.iterator();
?
// 循环判断是否有元素可以取出来
while (it1.hasNext()) {
// 说明有元素可以取出来,取出来
String e = it1.next();
System.out.println("取出来的元素:" + e);
}
System.out.println("col:" + col);
?
}
}
? -
解决方式: 使用集合从新获取一个新的迭代器对象来使用
常见问题二
-
在进行集合元素迭代时,如果添加或移除集合中的元素 , 将无法继续迭代 , 将会抛出ConcurrentModificationException并发修改异常.
/**
* Created by PengZhiLin on 2021/8/3 10:02
*/
public class Test2 {
public static void main(String[] args) {
?
// 创建Collection集合对象,限制集合元素的类型为String类型
Collection<String> col = new ArrayList<>();
//Collectioncol = new CopyOnWriteArrayList<>();
?
// 往集合中添加元素
col.add("谢霆锋");
col.add("王宝强");
col.add("贾乃亮");
col.add("陈羽凡");
?
// 获取迭代器
Iterator<String> it = col.iterator();
?
// 循环判断是否有元素可以取出来
while (it.hasNext()) {
// 说明有元素可以取出来,取出来
String e = it.next();
System.out.println("取出来的元素:" + e);
?
// 迭代的同时,往集合中,添加或者删除元素---->不可以的,会报ConcurrentModificationException并发修改异常
col.add("潘粤明");// 解决方式:使用CopyOnWriteArrayList集合
//col.remove(e);// --->解决方式:使用迭代器的删除方法
?
// 迭代器自己删除----可以的
//it.remove();
}
System.out.println("col:" + col);
?
?
}
}
? -
解决办法:
-
使用CopyOnWriteArrayList集合,就可以迭代的时候,往集合中添加或删除元素
-
使用迭代器的remove方法实现一边迭代,一边删除
-
2.4
-
概述: 增强for循环(foreach循环),是JDK1.5以后出来的一个高级for循环,专门用来遍历数组和Collection集合
-
原理: 内部基于Iterator迭代器实现,所以在遍历的过程中,不能对集合中的元素进行增删操作,否则抛出ConcurrentModificationException并发修改异常
-
格式:
for(数据类型 变量名 : 数组名\集合名){
} -
案例:
/**
* Created by PengZhiLin on 2021/8/3 10:16
*/
public class Test {
public static void main(String[] args) {
// 创建Collection集合对象,限制集合元素的类型为String类型
Collection<String> col = new ArrayList<>();
?
// 往集合中添加元素
col.add("谢霆锋");
col.add("王宝强");
col.add("贾乃亮");
col.add("陈羽凡");
?
// 增强for循环
for (String name : col) {
System.out.println("name:" + name);
//col.add("潘粤明");// 报并发修改异常
//col.remove(name);// 报并发修改异常
}
?
System.out.println("--------------------");
?
// 创建String类型的数组,并存储元素
String[] arr = {"陈冠希", "罗志祥", "吴签", "林俊杰"};
// 增强for循环
for (String name : arr) {
System.out.println("name:" + name);
}
}
}
?
第三章 泛型
3.1 泛型的概述
-
概述: JDK5之后,新增了泛型(Generic)语法,可以在类、接口或方法中预支地使用未知的类型
-
简而言之: 泛型其实就是表示一种未知的数据类型,在使用的时候确定其具体数据类型
-
表示方式: <泛型变量> 泛型变量任意字母
-
泛型的好处:
-
将运行时期的ClassCastException,转移到了编译时期变成了编译失败
-
避免了类型转换的麻烦
-
-
案例:
-
集合不使用泛型
-
可能会发生类型转换异常
-
避免类型转换异常,就需要先做类型判断,再转型--->比较麻烦
/**
* Created by PengZhiLin on 2021/8/3 10:44
*/
public class Test {
public static void main(String[] args) {
// 创建Collection集合,不使用泛型
Collection col = new ArrayList();
?
// 往集合中添加元素
col.add("张三");
col.add(18);
col.add("中国北京");
col.add(3.14);
System.out.println("col:" + col);
?
// 循环遍历
for (Object obj : col) {
// 获取字符串元素的长度,打印输出
//String str = (String)obj;
//System.out.println("字符串元素的长度:"+str.length());
?
// 避免类型转换异常
if (obj instanceof String){
String str = (String)obj;
System.out.println("字符串元素的长度:"+str.length());
}
}
}
}
?
-
-
集合使用泛型
-
概述:指定泛型的具体数据类型----->(只能是引用数据类型)
/**
* Created by PengZhiLin on 2021/8/3 10:44
*/
public class Test {
public static void main(String[] args) {
// 创建Collection集合,使用泛型
Collection<String> col = new ArrayList();
?
// 往集合中添加元素
col.add("张三");
//col.add(18);// 编译报错
col.add("中国北京");
//col.add(3.14);// 编译报错
System.out.println("col:" + col);
?
// 循环遍历
/* for (Object obj : col) {
// 获取字符串元素的长度,打印输出
//String str = (String)obj;
//System.out.println("字符串元素的长度:"+str.length());
?
// 避免类型转换异常
if (obj instanceof String){
String str = (String)obj;
System.out.println("字符串元素的长度:"+str.length());
}
}*/
?
for (String str : col) {
System.out.println("字符串元素的长度:"+str.length());
}
}
}
?
-
-
3.2 定义和使用含有泛型的类
-
定义含有泛型的类
-
格式:
public class 类名<泛型变量>{
}
泛型变量: 可以是任意字母,eg: A,B,E,...a,b,....;一般写E -
案例
/**
* Created by PengZhiLin on 2021/8/3 10:54
*/
public class MyGenericClass{
// 成员变量
E e;// 未知类型的成员变量
// 成员方法
public E method(E e){
return e;
}
}
-
-
使用含有泛型的类
-
创建含有泛型的类的对象的时候,指定泛型的具体数据类型(只能是引用数据类型)
-
案例
/**
* Created by PengZhiLin on 2021/8/3 11:02
*/
public class Test {
public static void main(String[] args) {
// 创建含有泛型的类的对象的时候,指定泛型的具体数据类型(只能是引用数据类型)
// 创建MyGenericClass对象,指定泛型的具体数据类型为String
MyGenericClassmc1 = new MyGenericClass<>();
// 访问MyGenericClass类的成员变量
mc1.e = "itheima";
System.out.println("e:" + mc1.e);
// 访问MyGenericClass类的成员方法
String res1 = mc1.method("itcast");
System.out.println("res1:"+res1);
System.out.println("---------------");
// 创建MyGenericClass对象,指定泛型的具体数据类型为Integer
MyGenericClassmc2 = new MyGenericClass<>();
// 访问MyGenericClass类的成员变量
mc2.e = 100;
System.out.println("e:" + mc2.e);
// 访问MyGenericClass类的成员方法
Integer res2 = mc2.method(200);
System.out.println("res2:"+res2);
}
}
-
3.3 定义和使用含有泛型的接口
-
定义含有泛型的接口
-
格式:
public interface 接口名<泛型变量>{
}
// 泛型变量:可以是任意字母,一般写E -
案例:
/**
* Created by PengZhiLin on 2021/8/3 11:08
*/
public interface MyGenericInterface{
E method1(E e);
}
-
-
使用含有泛型的接口
-
方式一: 实现类实现接口的时候,确定接口泛型的具体数据类型
-
格式:
public class 类名 implements 接口名<具体的引用数据类型>{} -
案例:
/**
* Created by PengZhiLin on 2021/8/3 11:11
*/
public class MyImp1 implements MyGenericInterface{
@Override
public String method1(String s) {
return "String字符串";
}
}
-
-
方式二:实现类实现接口的时候,不确定接口泛型的具体数据类型,而是创建实现类对象的时候确定泛型的具体数据类型
-
格式:
public class 类名<泛型变量> implements 接口名<泛型变量>{} -
案例:
/**
* Created by PengZhiLin on 2021/8/3 11:13
*/
// 实现类实现接口的时候不指定接口泛型的具体数据类型
public class MyImp2implements MyGenericInterface {
@Override
public E method1(E e) {
return e;
}
}public class Test {
public static void main(String[] args) {
// 创建MyImp1对象
MyImp1 imp1 = new MyImp1();
String res1 = imp1.method1("itheima");
System.out.println("res1:"+res1);
// 创建MyImp2对象,指定泛型的具体数据类型为String
MyImp2imp2 = new MyImp2<>();
String res2 = imp2.method1("itcast");
System.out.println("res2:"+res2);
// 创建MyImp2对象,指定泛型的具体数据类型为Integer
MyImp2imp3 = new MyImp2<>();
Integer res3 = imp3.method1(100);
System.out.println("res3:"+res3);
}
} -
-
3.4 定义和使用含有泛型的方法
-
定义含有泛型的方法
-
格式:
修饰符 <泛型变量> 返回值类型 方法名(形参列名){
方法体
}
// 泛型变量:可以写任意字母,一般写T -
案例:
/**
* Created by PengZhiLin on 2021/8/3 11:22
*/
public class MyGenericMethod {
publicT method(T t){
return t;
}
}
-
-
使用含有泛型的方法
-
调用含有泛型方法的时候,确定泛型的具体数据类型
-
案例:
/**
* Created by PengZhiLin on 2021/8/3 11:23
*/
public class Test {
public static void main(String[] args) {
// 调用含有泛型方法的时候,确定泛型的具体数据类型
Integer res1 = MyGenericMethod.method(100);
System.out.println("res1:"+res1);
String res2 = MyGenericMethod.method("itheima");
System.out.println("res2:"+res2);
}
}
-
3.5 泛型通配符
-
概述: 泛型通配符用问号表示(?)
-
为什么需要泛型通配符:
-
泛型本身不存在继承关系,不可以给已指定泛型的变量接收有其他泛型类型的对象
-
翻译: 左边变量泛型指定什么类型,右边对象的泛型就是什么类型,如果不一致就会报错(无论是否有父子关系,都报错)
-
eg: Collection
col = new ArrayList ();// 正确的 -
eg: Collection
col = new ArrayList ();// 错误的 -
...
-
-
如果想要使变量在未来接收有泛型定义的对象,又不确定泛型要定义的类型可以使用泛型通配符
-
Collection<?> col3 = new ArrayList
();// 正确的 -
Collection<?> col4 = new ArrayList
();//正确的 -
Collection<?> col5 = new ArrayList
-
-
-
通配符基本使用
-
格式:
数据类型<?> 变量 -
案例:
Collection<?> col3 = new ArrayList
();// 正确的
Collection<?> col4 = new ArrayList();//正确的
Collection<?> col5 = new ArrayList -
注意:
-
如果使用了泛型通配符,那么该集合变量元素类型默认是Object类型
-
如果使用了泛型通配符,那么该集合变量只能取元素,无法增删元素
-
-
案例:
/**
* Created by PengZhiLin on 2021/8/3 11:46
*/
public class Test {
public static void main(String[] args) {
// 泛型本身不存在继承关系,不可以给已指定泛型的变量接收有其他泛型类型的对象
//Collection
-
-
通配符高级使用----受限泛型
-
上限:
-
格式:
<? extends 类名> -
表示: 只接受泛型是该类类型或者该类的子类类型
-
-
下限:
-
格式:
<? super 类名> -
表示: 只接受泛型是该类类型或者该类的父类类型
-
案例:
-
-
案例:
/**
* Created by PengZhiLin on 2021/8/3 12:02
*/
public class Test {
public static void main(String[] args) {
// 创建ArrayList集合,限制集合元素的类型为Object
ArrayListlist1 = new ArrayList<>();
// 创建ArrayList集合,限制集合元素的类型为String
ArrayListlist2 = new ArrayList<>();
// 创建ArrayList集合,限制集合元素的类型为Number
ArrayListlist3 = new ArrayList<>();
// 创建ArrayList集合,限制集合元素的类型为Integer
ArrayListlist4 = new ArrayList<>();
//method1(list1);// 编译报错
//method1(list2);// 编译报错
method1(list3);
method1(list4);
System.out.println("---------------");
method2(list1);
//method2(list2);// 编译报错
method2(list3);
//method2(list4);// 编译报错
}
// 定义一个方法,接收泛型为Number类型或者其子类类型的ArrayList集合
public static void method1(ArrayList<? extends Number> list){
}
// 定义一个方法,接收泛型为Number类型或者其父类类型的ArrayList集合
public static void method2(ArrayList<? super Number> list){
}
}
-
第四章 数据结构
4.1 数据结构介绍
-
数据结构 :其实就是存储数据和表示数据的方式
-
常见的数据结构:栈、队列、数组、链表和树
4.2 常见数据结构
栈
-
栈:stack,又称堆栈,它是运算受限的线性表,其限制是仅允许在表的一端进行插入和删除操作,不允许在其他任何位置进行添加、查找、删除等操作。
简单的说:采用该结构的集合,对元素的存取有如下的特点
-
先进后出(即,存进去的元素,要在后它后面的元素依次取出后,才能取出该元素)。例如,子弹压进弹夹,先压进去的子弹在下面,后压进去的子弹在上面,当开枪时,先弹出上面的子弹,然后才能弹出下面的子弹。
-
栈的入口、出口的都是栈的顶端位置。

这里两个名词需要注意:
-
压栈:就是存元素。即,把元素存储到栈的顶端位置,栈中已有元素依次向栈底方向移动一个位置。
-
弹栈:就是取元素。即,把栈的顶端位置元素取出,栈中已有元素依次向栈顶方向移动一个位置。
队列
-
队列:queue,简称队,它同堆栈一样,也是一种运算受限的线性表,其限制是仅允许在表的一端进行插入,而在表的另一端进行取出并删除。
简单的说,采用该结构的集合,对元素的存取有如下的特点:
-
先进先出(即,存进去的元素,要在后它前面的元素依次取出后,才能取出该元素)。例如,小火车过山洞,车头先进去,车尾后进去;车头先出来,车尾后出来。
-
队列的入口和出口在两侧。例如,下图中的左侧为入口,右侧为出口。
-

数组
-
数组:Array,是有序的元素序列,数组是在内存中开辟一段连续的空间,并在此空间存放元素。就像是一排出租屋,有100个房间,从001到100每个房间都有固定编号,通过编号就可以快速找到租房子的人。
简单的说,采用该结构的集合,对元素的存取有如下的特点:
-
查找元素快:通过索引,可以快速访问指定位置的元素

-
增删元素慢
-
指定索引位置增加元素:需要创建一个新数组,将指定新元素存储在指定索引位置,再把原数组元素根据索引,复制到新数组对应索引的位置。如下图

-
指定索引位置删除元素:需要创建一个新数组,把原数组元素根据索引,复制到新数组对应索引的位置,原数组中指定索引位置元素不复制到新数组中。如下图

链表
-
链表:linked list,由一系列结点node(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。我们常说的链表结构有单向链表与双向链表,那么这里给大家介绍的是单向链表\双向链表。

简单的说,采用该结构的集合,对元素的存取有如下的特点:
-
多个结点之间,通过地址进行连接。例如,多个人手拉手,每个人使用自己的右手拉住下个人的左手,依次类推,这样多个人就连在一起了。
-
查找元素慢:想查找某个元素,需要通过连接的节点,依次向后查找指定元素。
-
增删元素快:只需要修改链接下一个元素的地址值即可

-
4.3 树基本结构介绍
树具有的特点
-
每一个节点有零个或者多个子节点
-
没有父节点的节点称之为根节点,一个树最多有一个根节点。
-
每一个非根节点有且只有一个父节点

| 名词 | 含义 |
|---|---|
| 节点 | 指树中的一个元素 |
| 节点的度 | 节点拥有的子树的个数,二叉树的度不大于2 |
| 叶子节点 | 度为0的节点,也称之为终端结点 |
| 高度 | 叶子结点的高度为1,叶子结点的父节点高度为2,以此类推,根节点的高度最高 |
| 层 | 根节点在第一层,以此类推 |
| 父节点 | 若一个节点含有子节点,则这个节点称之为其子节点的父节点 |
| 子节点 | 子节点是父节点的下一层节点 |
| 兄弟节点 | 拥有共同父节点的节点互称为兄弟节点 |
二叉树
如果树中的每个节点的子节点的个数不超过2,那么该树就是一个二叉树。

二叉查找树
二叉查找树的特点:
-
左子树上所有的节点的值均小于等于他的根节点的值
-
右子树上所有的节点值均大于或者等于他的根节点的值
-
每一个子节点最多有两个子树
案例演示(20,18,23,22,17,24,19)数据的存储过程;

遍历获取元素的时候可以按照"左中右"的顺序进行遍历: 17 , 18, 19,20,22,23,24
注意:二叉查找树存在的问题:会出现"瘸子"的现象,影响查询效率

平衡二叉树
概述
为了避免出现"瘸子"的现象,减少树的高度,提高我们的搜素效率,又存在一种树的结构:"平衡二叉树"
规则:它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树
如下图所示:

如下图所示,左图是一棵平衡二叉树,根节点10,左右两子树的高度差是1,而右图,虽然根节点左右两子树高度差是0,但是右子树15的左右子树高度差为2,不符合定义,
所以右图不是一棵平衡二叉树。
旋转
在构建一棵平衡二叉树的过程中,当有新的节点要插入时,检查是否因插入后而破坏了树的平衡,如果是,则需要做旋转去改变树的结构。
左旋:
左旋就是将节点的右支往左拉,右子节点变成父节点,并把晋升之后多余的左子节点出让给降级节点的右子节点;

右旋:
将节点的左支往右拉,左子节点变成了父节点,并把晋升之后多余的右子节点出让给降级节点的左子节点

举个例子,像上图是否平衡二叉树的图里面,左图在没插入前"19"节点前,该树还是平衡二叉树,但是在插入"19"后,导致了"15"的左右子树失去了"平衡",
所以此时可以将"15"节点进行左旋,让"15"自身把节点出让给"17"作为"17"的左树,使得"17"节点左右子树平衡,而"15"节点没有子树,左右也平衡了。如下图,

由于在构建平衡二叉树的时候,当有新节点插入时,都会判断插入后时候平衡,这说明了插入新节点前,都是平衡的,也即高度差绝对值不会超过1。当新节点插入后,
有可能会有导致树不平衡,这时候就需要进行调整,而可能出现的情况就有4种,分别称作左左,左右,右左,右右。
左左:只需要做一次右旋就变成了平衡二叉树。
右右:只需要做一次左旋就变成了平衡二叉树。
左右:先做一次分支的左旋,再做一次树的右旋,才能变成平衡二叉树。
右左:先做一次分支的右旋,再做一次数的左旋,才能变成平衡二叉树。
课上只讲解“左左”的情况
左左
左左:只需要做一次右旋就变成了平衡二叉树。
左左即为在原来平衡的二叉树上,在节点的左子树的左子树下,有新节点插入,导致节点的左右子树的高度差为2,如下即为"10"节点的左子树"7",的左子树"4",插入了节点"5"或"3"导致失衡。

左左调整其实比较简单,只需要对节点进行右旋即可,如下图,对节点"10"进行右旋,


左右
左右:先做一次分支的左旋,再做一次树的右旋,才能变成平衡二叉树。
左右即为在原来平衡的二叉树上,在节点的左子树的右子树下,有新节点插入,导致节点的左右子树的高度差为2,如上即为"11"节点的左子树"7",的右子树"9",
插入了节点"10"或"8"导致失衡。

左右的调整就不能像左左一样,进行一次旋转就完成调整。我们不妨先试着让左右像左左一样对"11"节点进行右旋,结果图如下,右图的二叉树依然不平衡,而右图就是接下来要
讲的右左,即左右跟右左互为镜像,左左跟右右也互为镜像。

左右这种情况,进行一次旋转是不能满足我们的条件的,正确的调整方式是,将左右进行第一次旋转,将左右先调整成左左,然后再对左左进行调整,从而使得二叉树平衡。
即先对上图的节点"7"进行左旋,使得二叉树变成了左左,之后再对"11"节点进行右旋,此时二叉树就调整完成,如下图:

右左
右左:先做一次分支的右旋,再做一次树的左旋,才能变成平衡二叉树。
右左即为在原来平衡的二叉树上,在节点的右子树的左子树下,有新节点插入,导致节点的左右子树的高度差为2,如上即为"11"节点的右子树"15",的左子树"13",
插入了节点"12"或"14"导致失衡。

前面也说了,右左跟左右其实互为镜像,所以调整过程就反过来,先对节点"15"进行右旋,使得二叉树变成右右,之后再对"11"节点进行左旋,此时二叉树就调整完成,如下图:

右右
右右:只需要做一次左旋就变成了平衡二叉树。
右右即为在原来平衡的二叉树上,在节点的右子树的右子树下,有新节点插入,导致节点的左右子树的高度差为2,如下即为"11"节点的右子树"13",的左子树"15",插入了节点
"14"或"19"导致失衡。

右右只需对节点进行一次左旋即可调整平衡,如下图,对"11"节点进行左旋。

红黑树
红黑树是一种自平衡的二叉查找树,是计算机科学中用到的一种数据结构,它是在1972年由Rudolf Bayer发明的,当时被称之为平衡二叉B树,后来,在1978年被
Leoj.Guibas和Robert Sedgewick修改为如今的"红黑树"。它是一种特殊的二叉查找树,红黑树的每一个节点上都有存储位表示节点的颜色,可以是红或者黑;
红黑树不是高度平衡的,它的平衡是通过"红黑树的特性"进行实现的;
红黑树的特性:
-
每一个节点或是红色的,或者是黑色的。
-
根节点必须是黑色
-
每个叶节点(Nil)是黑色的;(如果一个节点没有子节点或者父节点,则该节点相应的指针属性值为Nil,这些Nil视为叶节点)
-
如果某一个节点是红色,那么它的子节点必须是黑色(不能出现两个红色节点相连的情况)
-
对每一个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点;
如下图所示就是一个

在进行元素插入的时候,和之前一样; 每一次插入完毕以后,使用黑色规则进行校验,如果不满足红黑规则,就需要通过变色,左旋和右旋来调整树,使其满足红黑规则;
4.4 小结
-
栈结构: 先进后出
-
队列结构:先进先出
-
数组: 查询快,增删慢
-
链表; 查询慢,增删快
-
二叉查找树: 提高搜索效率
第五章 List接口
5.1 List接口介绍
-
List接口的概述
-
java.util.List接口继承自Collection接口,是单列集合的一个重要分支
-
-
List接口的特点
-
它是一个元素存取有序的集合
-
它是一个带有索引的集合,通过索引就可以精确的操作集合中的元素
-
集合中可以有重复的元素
-
5.2 List接口中常用方法
List接口新增常用方法
List作为Collection集合的子接口,不但继承了Collection接口中的全部方法,而且还增加了一些根据元素索引来操作集合的特有方法,如下:
-
public void add(int index, E element): 将指定的元素,添加到该集合中的指定位置上。 -
public E get(int index):返回集合中指定位置的元素。 -
public E remove(int index): 移除列表中指定位置的元素, 返回的是被移除的元素。 -
public E set(int index, E element):用指定元素替换集合中指定位置的元素,返回值的更新前的元素。
List集合特有的方法都是跟索引相关,我们在基础班都学习过。
List接口新增常用方法的使用
/**
* Created by PengZhiLin on 2021/8/3 15:41
*/
public class Test {
public static void main(String[] args) {
// 创建List集合,限制集合元素的类型为String类型
Listlist = new ArrayList<>();
// 往集合中添加元素
list.add("林青霞");
list.add("张曼玉");
list.add("王祖贤");
list.add("张敏");
//list:[林青霞, 张曼玉, 王祖贤, 张敏]
System.out.println("list:" + list);
// - `public void add(int index, E element)`: 将指定的元素,添加到该集合中的指定位置上。
// 往索引为1的位置添加一个元素:温碧霞
list.add(1, "温碧霞");
//list:[林青霞, 温碧霞, 张曼玉, 王祖贤, 张敏]
System.out.println("list:" + list);
//- `public E get(int index)`:返回集合中指定位置的元素。
// 获取索引为3的元素
System.out.println("索引为3的元素:"+list.get(3));// 王祖贤
//- `public E remove(int index)`: 移除列表中指定位置的元素, 返回的是被移除的元素。
// 删除索引为1的元素
String removeE = list.remove(1);
System.out.println("被删除的元素:"+removeE);// 温碧霞
// list:[林青霞, 张曼玉, 王祖贤, 张敏]
System.out.println("list:" + list);
//- `public E set(int index, E element)`:用指定元素替换集合中指定位置的元素,返回值的更新前的元素。
// 把张曼玉换成邱淑贞
String setE = list.set(1, "邱淑贞");
System.out.println("被替换的元素:"+setE);// 张曼玉
// list:[林青霞, 邱淑贞, 王祖贤, 张敏]
System.out.println("list:" + list);
}
}
-
注意: 如果集合元素为Integer类型,那么删除的时候优先根据索引删除
/**
* Created by PengZhiLin on 2021/8/3 15:48
*/
public class Test2 {
public static void main(String[] args) {
// 创建List集合,限制集合中元素的类型为Integer类型
Listlist = new ArrayList<>();
// 添加元素
list.add(1);
list.add(2);
list.add(3);
list.add(4);
//删除之前:[1, 2, 3, 4]
System.out.println("删除之前:" + list);
// 删除索引为2的元素----如果集合元素为Integer类型,那么删除的时候优先根据索引删除
list.remove(2);
//删除之后:[1, 2, 4]
System.out.println("删除之后:" + list);
// 删除2这个元素
//list.remove(1);// 根据索引删除 remove(int index)
Integer i = 2;
list.remove(i);// 2 remove(Object obj)
//删除之后:[1, 4]
System.out.println("删除之后:" + list);
}
}
5.3 List的子类
-
ArrayList集合: 底层采用的是数组结构,查询快,增删慢
-
方法: 来自Collection,List
-
-
LinkedList集合; 底层采用的是链表结构,查询慢,增删快
-
方法: 来自Collection,List,LinkedList特有的方法
-
特有的方法:
public void addFirst(E e):将指定元素插入此列表的开头
public void addLast(E e):将指定元素添加到此列表的结尾
public E getFirst():返回此列表的第一个元素
public E getLast():返回此列表的最后一个元素
public E removeFirst():移除并返回此列表的第一个元素
public E removeLast():移除并返回此列表的最后一个元素
public E pop():从此列表所表示的堆栈处弹出一个元素
public void push(E e):将元素推入此列表所表示的堆栈 -
案例:
/**
* Created by PengZhiLin on 2021/8/3 15:56
*/
public class Test {
public static void main(String[] args) {
// 创建LinkedList集合,限制集合元素的类型为String
LinkedListlist = new LinkedList<>();
// 往集合中添加元素
list.add("林青霞");
list.add("张曼玉");
list.add("王祖贤");
list.add("张敏");
System.out.println("list:" + list);
//public void addFirst(E e):将指定元素插入此列表的开头
//public void addLast(E e):将指定元素添加到此列表的结尾
list.addFirst("温碧霞");
list.addLast("邱淑贞");
System.out.println("list:" + list);
//public E getFirst():返回此列表的第一个元素
//public E getLast():返回此列表的最后一个元素
String firstE = list.getFirst();
String lastE = list.getLast();
System.out.println("第一个元素:"+firstE);// 温碧霞
System.out.println("最后一个元素:"+lastE);// 邱淑贞
//public E removeFirst():移除并返回此列表的第一个元素
//public E removeLast():移除并返回此列表的最后一个元素
String removeFirst = list.removeFirst();
String removeLast = list.removeLast();
System.out.println("被删除的第一个元素:"+removeFirst);// 温碧霞
System.out.println("被删除的最后一个元素:"+removeLast);// 邱淑贞
System.out.println("list:" + list);// [林青霞, 张曼玉, 王祖贤, 张敏]
//public E pop():从此列表所表示的堆栈处弹出一个元素====>removeFirst()
//public void push(E e):将元素推入此列表所表示的堆栈==>addFirst()
String popE = list.pop();
System.out.println("弹出的第一个元素:" + popE);// 林青霞
list.push("李嘉欣");
System.out.println("list:" + list);// [李嘉欣, 张曼玉, 王祖贤, 张敏]
}
}
-
5.4 集合综合案例
需求:
-
按照斗地主的规则,完成造牌洗牌发牌的动作。 具体规则:
使用54张牌打乱顺序,三个玩家参与游戏,三人交替摸牌,每人17张牌,最后三张留作底牌。
分析:
//1.造牌
//1.1 创建扑克盒单列集合,用来存储54张扑克牌
//1.2 创建花色单列集合,用来存储4个花色
//1.3 创建牌面值单列集合,用来存储13个牌面值
//1.4 往扑克盒单列集合中添加大小王
//1.5 花色和牌面值单列集合循环嵌套,拼接52张牌,添加到扑克盒集合中
//2.洗牌
//使用Collections的静态方法shuffle(List<?> list)
//3.发牌
//3.1 创建玩家1,玩家2,玩家3,底牌的集合,用来存储各自的牌
//3.2 循环遍历扑克盒中的牌
//3.3 在循环中,根据索引获取遍历出来的牌
//3.4 在循环中,判断遍历出来的牌的索引:
//3.5 如果索引>=51,牌就给底牌
//3.5 如果索引%3==0,牌就给玩家1
//3.5 如果索引%3==1,牌就给玩家2
//3.5 如果索引%3==2,牌就给玩家3
//3.6 展示牌
实现:
/**
* Created by PengZhiLin on 2021/8/3 16:14
*/
public class Test {
public static void main(String[] args) {
//1.造牌
//1.1 创建扑克盒单列集合,用来存储54张扑克牌
ArrayListpokerBox = new ArrayList<>();
//1.2 创建花色单列集合,用来存储4个花色
ArrayListcolors = new ArrayList<>();
colors.add("?");
colors.add("?");
colors.add("?");
colors.add("?");
//1.3 创建牌面值单列集合,用来存储13个牌面值
ArrayListnumbers = new ArrayList<>();
for (int i = 2; i <= 10; i++) {
numbers.add(i + "");
}
numbers.add("J");
numbers.add("Q");
numbers.add("K");
numbers.add("A");
//1.4 往扑克盒单列集合中添加大小王
pokerBox.add("大王");
pokerBox.add("小王");
//1.5 花色和牌面值单列集合循环嵌套,拼接52张牌,添加到扑克盒集合中
for (String number : numbers) {
for (String color : colors) {
String pai = color + number;
// 添加到集合中
pokerBox.add(pai);
}
}
System.out.println("牌:" + pokerBox);
System.out.println("牌:" + pokerBox.size());
//2.洗牌
//使用Collections的静态方法shuffle(List<?> list)
Collections.shuffle(pokerBox);
System.out.println("打乱顺序后的牌:" + pokerBox);
System.out.println("打乱顺序后的牌:" + pokerBox.size());
//3.发牌
//3.1 创建玩家1,玩家2,玩家3,底牌的集合,用来存储各自的牌
ArrayListplayer1 = new ArrayList<>();
ArrayListplayer2 = new ArrayList<>();
ArrayListplayer3 = new ArrayList<>();
ArrayListdiPai = new ArrayList<>();
//3.2 循环遍历扑克盒中的牌
for (int i = 0; i < pokerBox.size(); i++) {
//3.3 在循环中,根据索引获取遍历出来的牌
String pai = pokerBox.get(i);
//3.4 在循环中,判断遍历出来的牌的索引:
if (i >= 51) {
//3.5 如果索引>=51,牌就给底牌
diPai.add(pai);
} else if (i % 3 == 0) {
//3.5 如果索引%3==0,牌就给玩家1
player1.add(pai);
} else if (i % 3 == 1) {
//3.5 如果索引%3==1,牌就给玩家2
player2.add(pai);
} else {
//3.5 如果索引%3==2,牌就给玩家3
player3.add(pai);
}
}
//3.6 展示牌
System.out.println("玩家1的牌:"+player1+",牌数:"+player1.size());
System.out.println("玩家2的牌:"+player2+",牌数:"+player2.size());
System.out.println("玩家3的牌:"+player3+",牌数:"+player3.size());
System.out.println("底牌:"+diPai);
}
}
总结
必须练习:
1.总结单列集合继承体系和各个单列集合的特点---->默写
2.Collection集合的常用方法
3.List集合的常用方法
4.LinkedList集合的特有方法
5.迭代器的基本使用,增强for循环的使用--->使用迭代器,增强for循环遍历集合元素
6.使用含有泛型的类,含有泛型的接口,含有泛型的方法---->ArrayList类,List 接口....
7.斗地主案例---->选做
- 能够说出集合与数组的区别
- 长度:
- 数组长度是固定的
- 集合长度是不固定的
- 存储范围:
- 数组可以存储基本类型+引用类型 eg; int[],String[]
- 集合只能存储引用类型,如果要存储基本类型,需要存储基本类型对应的包装类类型
- 能够使用Collection集合的常用功能
- public boolean add(E e): 把给定的对象添加到当前集合中 。
- public void clear() :清空集合中所有的元素。
- public boolean remove(E e): 把给定的对象在当前集合中删除。
- public boolean contains(Object obj): 判断当前集合中是否包含给定的对象。
- public boolean isEmpty(): 判断当前集合是否为空。
- public int size(): 返回集合中元素的个数。
- public Object[] toArray(): 把集合中的元素,存储到数组中
- 能够使用迭代器对集合进行取元素
1.获取迭代器,使用Collection的iterator()方法
2.使用迭代器判断是否有元素可以迭代 hasNext()
3.使用迭代器取出可以迭代的元素 next()
4.一边迭代一边删除,使用迭代器的 remove()
- 能够使用增强for循环遍历集合和数组
格式:
for(数据类型 变量名: 数组名\集合名){
}
快捷键: 数组名.for 或者 集合名.for
- 能够理解泛型上下限
上限: <? extends 类名>
下限: <? super 类名>
- 能够阐述泛型通配符的作用
如果想要使变量在未来接收有泛型定义的对象,又不确定泛型要定义的类型可以使用泛型通配符?
- 能够说出常见的数据结构
栈,队列,数组,链表,树
- 能够说出数组结构特点
- 能够说出栈结构特点
- 能够说出队列结构特点
- 能够说出单向链表结构特点
- 栈结构: 先进后出
- 队列结构:先进先出
- 数组: 查询快,增删慢
- 链表; 查询慢,增删快
- 二叉查找树: 提高搜索效率
- 能够说出List集合特点
元素有索引,元素存取有序,元素可以重复
- 能够完成斗地主的案例
洗牌,发牌,造牌