java基础学习-5


177-200

快乐算法

七大查找算法:
查找是在大量的信息中寻找一个特定的信息元素,在计算机应用中,查找是常用的基本运算,例如编译程序中符号表的查找。文本简单概括性的介绍了常见的其中查找算法,说是七种,其实是二分查找、插值查找以及斐波那契查找都可以归为一类–插值查找。插值查找和斐波那契查找是在二分查找的基础上的优化查找算法,树表查找和哈希查找。

顺序查找-----略

二分查找

前提条件:数组中的数据必须是有序的
升序或者降序排列
核心逻辑:每次排除一半的查找范围

请添加图片描述
1、min和max表示当前要查找的范围
2、mid是在min和max中间的
3、如果要查找的元素在mid的左边,缩小范围时,min不变,max等于mid减1
4、如果要查找的元素在mid的右边,缩小范围,max不变,min等于mid加1

//定义两个变量记录要查找的范围
int min = 0;
int max = arr.length-1;
//利用循环不断地取找要查找的数据
while(true){
	if(min>max){
		return -1;
	}
	//找到min和max的中间位置
	int mid = (min+max)/2;
	//拿着mid指向的元素跟要查找的元素进行比较
	//number在mid的右边
	//number跟mid指向的元素一样
	if(arr[mid]>number){
		//number在mid的左边
		//min不变,max=mid-1;
		max=mid-1;
	}else if(arr[mid]<number){
		//number在mid的右边
		//max不变,min=mid+1;
		min=mid+1;
	}else{
		//number跟mid指向的元素一样
		return mid;
	}
}

小总结

1、二分查找的优势?
提高查找效率
2、二分查找的前提条件?
数据必须是有序的
如果数据是乱的,先排序再用二分查找得到的索引没有实际意义,只能确定当前数字在数组中是否存在,因为排序之后数字的位置就可能发生变化了
3、二分查找的过程
min和max表示当前要查找的范围
mid是在min和max中间的
如果要查找的元素在mid的左边,缩小范围时,min不变,max等于mid-1
如果要查找的元素在mid的右边,缩小范围时,max不变,min等于mid+1

4、二分查找、插值查找、斐波那契查询各自的特点
相同点:
都是通过不断缩小范围来查找对应的数据的
不同点:
计算mid的方式不一样
二分查找:mid每次都是指向范围的中间位置
插值查找:mid尽可能地靠近要查找的数据,但是要求数据尽可能地分布均匀
斐波那契查找:根据黄金分割点来计算mid指向的位置

分块查找

分块的原则1:前一块中的最大数据,小于后一块中的所有数据(块内无序,块间有序)
分块的原则2:块数数量一般等于数字的个数开根号。比如:16个数字一般分为4个块左右

核心思路:先确定要查找的元素在哪一个块,然后在块中挨个查找

冒泡

冒泡排序:相邻的数据两两比较,小的放前面,大的放后面

1、相邻的元素两两比较,大的放右边,小的放左边
2、第一轮循环结束,最大值已经找到,,在数组的最右边
3、第二轮循环只要在剩余的元素找最大值就可以了
4、每二轮循环结束,次大值已经确定,第三轮循环继续在剩余数据中循环

1、相邻的元素两两比较,大的放右边,小的放左边
2、第一轮比较完毕之后,最大值就已经确定,第二轮可以少循环一次,后面以此类推
3、如果数组中有n个数据,总共我们只要执行n-1轮的代码就可以

选择

选择排序:从0索引开始,拿着每一个索引上的元素跟后面的元素依次比较,小的放前面,大的放后面,以此类推。

1、从0索引开始,跟后面的元素一一作比较
2、小的放前面,大的放后面
3、第一轮循环结束后,最小的数据已经确定
4、第二轮循环从1索引开始以此类推
5、第三轮循环从2索引开始以此类推
6、第四轮循环从3索引开始以此类推

插入排序

将0索引的元素到N索引的元素看作是有序的,把N+1索引的元素到最后一个当成是无序的。遍历无序的数据,将遍历到的元素插入有序序列中适当的位置,如遇到相同的数据,插入在后面

//1、找到无序的那一组数组是从哪个索引开始的
int startIndex=-1;
for(int i=0;i<arr.length;i++){
	if(arr[i]>arr[i+1]){
		startIndex=i+1;
		break;
	}
}
//2、遍历从startIndex开始到最后一个元素,依次得到无序的那一组数据中的每一个元素
for(int i=startIndex;i<arr.length;i++){
	//问题:如何把遍历到的数据,插入到前面有序的这一组当中
	//记录当前要插入数据的索引
	int j=i;
	while(j>0&&arr[j]<arr[j-1]){
		//交换位置
		int temp=arr[j];
		arr[j]=arr[j-1];
		arr[j-1]=temp;
		j--;
	}
	printArr(arr);
}

递归算法

递归指的是方法中调用方法本身的现象

递归的注意点:递归一定要有出口,否则就会出现内存溢出

把一个复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解
递归策略只需少量的程序就可描述除解题过程所需要的多次重复计算

写递归的两个核心:
找出口:什么时候不再调用方法
找规则:如何把大问题变成规模较小的问题

方法内部再次调用方法的时候,参数必须要更加的靠近出口

public static int getFactorialRecursion(int number){
	if(number==1){
		return 1;
	}
	return number*getFactorialRecursion(number:number-1);
}

快速排序

第一轮:把0索引的数字作为基准数,确定基准数在数组中正确的位置
比基准数小的全部在左边,比基准数大的全部在右边

public static void quickSort(int[] arr,int i,int j){
	//定义两个变量记录要查找的范围
	int start = i;
	int end = j;
	if(start>end){
		return;
	}
	//记录基准数
	int baseNumber = arr[i];
	while(start!=end){
		//利用end,从后往前开始找,找比基准数小的数字
		while(true){
			if(end<=start||arr[end]<baseNumber){
				break;
			}
			end--;
		}
		//利用start,从前往后找,找比基准数大的数字
		while(true){
			if(end<=start||arr[start]>baseNumber){
				break;
			}
			start--;
		}
		//把end和start指向的元素进行交换
		int temp = arr[start];
		arr[start]=arr[end];
		arr[end]=temp;
	}
	//当start和end指向了同一个元素的时候,那么上面的循环就会结束
	//表示已经找到了基准数在数组中应存入的位置
	//基准数归位
	//就是拿着这个范围中的第一个数字,跟start指向的元素进行交换
	int temp = arr[i];
	arr[i] = arr[start];
	arr[start]=temp;
	quickSort(arr,i,j:start-1);
	quickSor(arr,i:start+1,j);
}

小总结

1、冒泡排序
相邻的元素两两比较,小的放前面,大的放后面
2、选择排序:
从0索引开始,拿着每一个索引上的元素跟后面的元素依次比较
小的放前面,大的放后面,依次类推
3、插入排序
将数组分为有序和无序两组,遍历无序数组,将元素插入有序序列中即可。
4、快速排序
将排序范围中的第一个数字作为基准数字,再定义两个变量start、end
start从前往后找比基准数大的,end从后往前找比基准数小的
找到之后交换start和end指向的元素,并循环这一过程,直到start和end处于同一个位置,该位置是基准数在数组中应存入的位置,再让基准数归位
归为后的效果:基准数左边的,比基准数小。基准数右边的,比基准数大

Arrays

操作数组的工具类
public static Stirng toString(数组)----把数组拼接成一个字符串
public static int binarySearch(数组,查找的元素)----二分查找法查找元素
public static int[] copyOf(原数组,新数组长度)----拷贝数组
public static int[] copyOfRange(原数组,起始索引,结束索引)----拷贝数组(指定范围)
public static void fill(数组,元素)----填充数组
public static void sort(数组)----按照默认方式进行数组排序
public static void sort(数组,排序规则)----按照指定的规则排序

binarySearch:二分查找法查找元素
细节1:二分查找的前提:数组中的元素必须是有序,数组中的元素必须是升序的
细节2:如果要查找的元素是存在的,那么返回的是真实的索引
但是,如果要查找的元素是不存在的,返回的是-插入点-1
疑问:为什么要减1呢?
解释:如果此时,我现在要查找数字0,那么如果返回的值是-插入点,就会出现问题了
如果要查找数字0,此时0是不存在的,但是按照上面的规则-插入点,应该就是-0
为了避免这样的情况,Java在这个基础上又减一。

copyOf:拷贝数组
参数一:老数组
参数二:新数组的长度
方法的底层会根据第二个参数来创建新的数组
如果新数组的长度是小于老数组的长度,会部分拷贝
如果新数组的长度是等于老数组的长度,会完全拷贝
如果新数组的长度是大于老数组的长度,会补上默认初始值

copyOfRange:拷贝数组(指定范围)
细节:包头不包尾,包左不包右

sort:排序,默认情况下,给基本数据类型进行升序排列,底层使用的是快速排序

sort(数组,排序规则)按照指定的规则排序
参数一:要排序的数组
参数二:排序的规则
细节:只能给引用数据类型的数组进行排序
如果数组是基本数据类型的,需要变成其对于的包装类

第二个参数是一个接口,所以我们在调用方法的时候,需要传递这个接口的实现类对象,作为排序的规则。
但是这个实现类,我只要使用依次,所以就没有必要单独的去写一个类,直接采取匿名内部类的方式就可以了

底层原理
利用插入排序+二分查找的方式进行排序
默认把0索引的数据当作是有序的序列,索引到最后认为是无序的序列
遍历无需的序列得到里面的每一个元素,假设当前遍历得到的元素是A元素
把A往有序序列中进行插入,在插入的时候,是利用二分查找确定A元素的插入点
拿着A元素,跟插入点的元素进行比较,比较的规则就是compare方法的方法体
如果方法的返回值是负数,拿着A继续跟前面的数据进行比较
如果方法的返回值是正数,拿着A继续跟后面的数据进行比较
如果方法的返回值是0,也拿着A跟后面的数据进行比较
直到能确定A的最终位置为止

compare方法的形式参数:
参数 o1:表示在无序序列中,遍历得到的每一个元素
参数o2:有序序列中的元素

返回值:
负数:表示当前要插入的元素是小的,放在前面
正数:表示当前要插入的元素是大的,放在后面
0:表示当前要插入的元素跟现在的元素比是一样的我们也会放在后面。

简单理解
o1-o2:升序排列
o2-o1:降序排列

Lambda表达式

函数式编程
函数式编程是一种思想特点
面向对象:先找对象,让对象做事情
函数式编程思想,忽略面向对象的复杂语法,强调做什么,而不是谁去做
而我们要学习的Lambda表达式就是函数式思想的体现

Lambda表达式是JDK8开始后的一种新语法形式
()->{

}
()对应着方法的形参
->固定格式
{}对应着方法的方法体

Lambda表达式可以用来简化匿名内部类的书写
Lambda表达式只能简化函数式接口的匿名内部类的写法
函数式接口:有且仅有一个抽象方法的接口叫做函数式接口,接口上方可以加@FunctionalInterface注解

小总结

1、Lambda表达式的基本作用?
简化函数式接口的匿名内部类的写法
2、Lambda表达式有什么使用前提?
必须是接口的匿名内部类,接口中只能有一个抽象方法
3、Lambda的好处?
Lambda是一个匿名函数,我们可以把Lambda表达式理解为是一段可以传递的代码,它可以写出更简洁、更灵活的代码,作为一种更紧凑的代码风格,使Java语言表达能力得到了提升

Lambda表达式的省略写法

省略核心:可推导,可省略
省略规则:
1、参数类型可以省略不写
2、如果只有一个参数,参数类型可以省略,同时()也可以省略
3、如果Lambda表达式的方法体只有一行,大括号,分号,return可以省略不写,需要同时省略

小练习

Lambda表达式简化Comparator接口的匿名形式
定义数组并存储一些字符串,利用Arrays中的sort方法进行排序
要求:
按照字符串的长度进行排序,短的在前面,长的在后面
(暂时不比较字符串里面的内容)

String[] arr = {"a","aaaa","aaa","aa"};
Arrays.sort(arr,(String o1,String o2)->{
		return o1.length()-o2.length();
	}
}
String[] arr = {"a","aaaa","aaa","aa"};
Arrays.sort(arr,(o1,o2)->o1.length()-o2.length());

集合进阶

集合体系结构
单列结构:每次添加一个
双列集合:每次添加两个

list系列集合:添加的元素是有序(存和取的顺序一样)、可重复(集合中存储的元素是可重复的)、有索引(可以通过索引值进行获取)

set系列集合:添加的元素是无序(存和取的顺序有可能是不一样的)、不重复(集合中不能存储重复的元素)、无索引(不能通过索引来获取set集合里的元素)

请添加图片描述

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()----返回集合中元素的个数/集合的长度

注意点:
Collection是一个接口,我们不能直接创建它的对象
所以,现在我们学习他的方法时,只能创建它实现类的对象

目的:为了学习Collection接口里面的方法
自己在做一些练习的时候,还是按照之前的方式去创建对象

Collection<String> coll = new ArrayList<>();

//1、添加元素
//细节1:如果我们要往List系列集合中添加数据,那么方法永远返回true,,因为List系列的是允许元素重复的。
//细节2:如果我们要往Set系列集合中添加数据,如果当前要添加元素不存在,方法返回true,表示添加成功;如果当前添加的元素已经存在,方法返回false,表示添加失败。因为Set系列的集合不允许重复。
coll.add("aaa");
coll.add("bbb");
coll.add("ccc");

System.out.println(coll)//2、清空
coll.clear();

System.out.println(coll);

//3、删除
//细节1:因为Collection里面定义的是共性的方法,所以不能通过索引进行删除,只能通过元素的对象进行删除
//细节2:方法会有一个布尔类型的返回值,删除成功返回true,删除失败返回false
//如果要删除的元素不存在,就会删除失败
System.out.println(coll.remove(o:"aaa"));
System.out.println(coll);

//4、判断元素是否包含
//细节:底层是依赖equals方法进行判断是否存在的
//所以,如果集合中存储的是自定义对象,也想通过contains方法来判断是否包含,那么在javabean类中,一定要重写equals方法
boolean result = coll.contains("aaa");
System.out.println(result);

//5、判断集合是否为空
boolean result2 = coll.isEmpty();
System.out.println(result2);

//6、获取集合的长度
int size = coll.size();
System.out.println(size);
//1、创建集合的对象
Collection<Student> coll = new ArrayList<>();

//2、创建三个学生对象
Student s1 = new Student(name:"zhangsan",age:23);
Student s2 = new Student(name:"lisi",age:24);
Student s3 = new Student(name:"wangwu",age:25);

//3、把学生对象添加到集合当中
coll.add(s1);
coll.add(s2);
coll.add(s3);

//4、判断集合中某一个学生对象是否包含
Student s4 = new Student(name:"zhangsan",age:23);
//如果同姓名和同年龄就认为是同一个学生
//因为contains方法在底层依赖equals方法判断对象是否一致的。
//如果存的是自定义对象,没有重写equals方法,那么默认使用Object类中的equals方法进行判断,而Object类中的equals方法,依赖地址值进行判断
//需求:如果同姓名和童年林,就认为是同一个学生
//所以,需要在自定义的Javabean类中,重写equals方法就可以了

Colection的遍历方式

迭代器遍历
增强for遍历
Lambda表达式遍历

迭代器遍历

迭代器不依赖索引
迭代器在Java中的类是Iterator,迭代器是集合专用的遍历方式

Collection集合获取迭代器
Iterator< E> iterator()----返回迭代器对象,默认指向当前集合的0索引

Iterator中的常用方法
boolean hasNext()----判断当前位置是否有元素,有元素返回true,没有元素返回false
E next()----获取当前位置的元素,并将迭代器对象移向下一个位置

Iterator<String> it = list.iterator();
while(it.hasNext()){
	String str = it.next();
	System.out.println(str);
}

创建指针
判断是否元素
获取元素;移动·指针

//1、创建集合并添加元素
Collection<String> coll = new ArrayList<>();
coll.add("aaa");
coll.add("bbb");
coll.add("ccc");
coll.add("ddd");

//2、获取迭代器
//迭代器就好比是一个箭头,默认指向集合的0索引处
Iterator<String> it = coll.iterator();
//3、利用循环不断地去获取集合中的每一个元素
while(it.hasNext)){
	//4、next方法的两件事情:获取元素并移动指针
	String str = it.next();
	System.out.println(str);
}

细节注意点
1、如果在元素结束之后强行获取,会报错NoSuchElementException
2、迭代器遍历完毕,指针不会复位
3、循环中只能用一次next方法(指针不会复位)
4、迭代器遍历时,不能用集合的方法进行增加或者删除
如果我实在要删除,那么可以用迭代器提供的remove方法进行删除,如果我要添加,暂时没有办法

小总结

1、迭代器在遍历集合的时候是不依赖索引的
2、迭代器需要掌握三个方法

Iterator<String> it = list.iterator():
while(it.hasNext){
	String str = it.next();
	System.out.println(str);
}

3、迭代器的四个细节:
如果当前位置没有元素,还要强行获取,会报NoSuchElementException
迭代器遍历完毕,指针不会复位
循环中只能用一次next方法
迭代器遍历时,不能用集合的方法进行增加或者删除

增强for循环

增强for的底层就是迭代器,为了简化迭代器的代码书写的
它是JDK5之后出现的,其内部原理就是一个Iterator迭代器
所有的单列集合和数组才能用增强for进行遍历

快速生成方式
集合的名字+for 回车

Collection<String> coll = new ArrayList<>();
coll.add("zhangsan");
coll.add("lisi");
coll.add("wangwu");

//s其实就是一个第三方变量,在循环的过程中依次表示集合的每一个数据
for(String s:coll){
	System.out.println(s);
}

增强for的细节
修改增强for中的变量,不会改变集合中原本的数据(s只是个第三方变量)

Lambda表达式遍历

得益于JDK8开始的新技术Lambda表达式,,提供了一种更简单、更直接的遍历集合的方式
default void forEach(Consumer<? super T>action):----结合lambda遍历集合

Collection<String> coll = new ArrayList<>();
coll.add("zhangsan");
coll.add("lisi");
coll.add("wangwu");

//2、利用匿名内部类的形式
//底层原理:
//其实也会自己便利集合,依次得到每一个元素
//把得到的每一个元素,传递给下面的accept方法
//s依次表示集合中的每一个数据
coll.foreach(new Consumer<String>(){
	@Override
	//s依次表示集合中的每一个数据
	public void accept(String s){
		System.out.println(s);
	}
});

//lambda表达式
//()->()
coll.forEach(s->System.out.println(s));

小总结

1、Collection是单列集合的顶层接口,所有方法被List和Set系列集合共享
2、常见成员方法:
add、clear、remove、contains、isEmpty、size
3、三种通用的遍历方式
迭代器:在遍历的过程中需要删除元素,请使用迭代器。
增强for、Lambda:仅仅想遍历,那么使用增强for或Lambda表达式

List集合

特点:
有序:存和取的元素顺序一致
有索引:可以通过索引操作元素
可重复:存储的元素可以重复

方法:
Collection的方法List都继承了
List集合因为有索引,所以多了很多索引操作的方法
void add(int index , E element)----在此集合中的指定位置插入指定元素
E remove (int index)----删除指定索引处的元素,返回被删除的元素
E set(int index ,E element)----修改指定索引处的元素,返回被修改的元素
E get(int index)----返回指定索引处的元素

List<String> list = new ArrayList<>();

list.add("aaa");
list.add("bbb");
list.add("ccc");

//细节:原来索引上的元素会依次往后移
list.add(index:1,element:"QQQ")

//删除指定索引处的元素,返回被删除的元素
String remove = list.remove(index:0);
System.out.println(remove);

//修改指定索引处的元素,返回被修改的元素
String result = list.set(0,"QQQ");
System.out.println(result);

System.out.println(list);

删除元素小细节:
list.add(1);
list.add(2);
list.add(3);
请问:此时删除的是1这个元素,还是1索引上的元素?
为什么?
因为在调用方法的时候,如果方法出现了重载现象
优先调用,实参跟形参类型一致的那个方法。

手动装箱:手动把基本数据类型的1,变成Integer类型
Integer i = Integer.valueOf(1);

List的遍历方式

迭代器遍历
列表迭代器遍历
增强for遍历
Lambda表达式遍历
普通for循环(因为List集合存在索引)

迭代器遍历

Iterator< String> it = list.iterator();
while(it.hasNext()){
	String str = it.next();
	System.out.println(str);
}

增强for

下面的变量s,其实就是一个第三方的变量而已
在循环的过程中,以此表示集中的每一个元素

for(String s:list){
	System.out.println(s);
}

Lambda

forEach方法的底层其实就是一个循环遍历,依次得到集合中的每一个元素
并把每一个元素传递给下面的accept方法
accept方法的形参s,依次表示集合中的每一个元素

list.forEach(new Consumer<String>(){
	@Override
	public void accept(String s){
		System.out.println(s);
	}
});
list.forEach(s->System.out.println(s));

普通for循环

size方法跟get方法还有循环结合的方式,利用索引获取到集合中的每一个元素

for(int i=0;i<list.size();i++){
	String s=list.get(i);
	System.out.println(s);
}

列表迭代器

获取一个列表迭代器的对象,里面的指针默认也是指向0索引的

ListIterator<String> it = list.listIterator();
while(it.hasNext()){
	String str = it.next();
	if("bbb".equals(str)){
		it.add("qqq");
	}
}

五种遍历方式对比

迭代器遍历:在遍历的过程中需要删除元素,请使用迭代器
列表迭代器:在遍历的过程中需要添加元素,请使用列表迭代器
增强for循环遍历:仅仅想遍历,那么使用增强for或Lambda表达式
Lambda表达式:仅仅想遍历,那么使用增强for或Lambda表达式
普通for:如果遍历的时候想操作索引,可以用普通for

数据结构

什么是数据结构呢?
计算机存储、组织数据的方式

不同的业务场景要选择不同的数据结构

数据结构的概述
数据结构是计算机底层存储、组织数据的方式
是指数据相互之间是以什么方式排列在一起的
数据结构是为了更加方便的管理和使用数据,需要结合具体的业务场景来进行选择
一般情况下,精心选择的数据结构可以带来更高的运行或者存储效率

1、每种数据结构长什么样子
2、如何添加数据
3、如何删除数据

栈、队列、数组、链表、二叉树、二叉查找树、平衡二叉树、红黑树

栈的特点:后进后出,先进先出
一端开口,栈顶
一端封闭,栈底

数据进入栈模型的过程称为:压/进栈
数据离开栈模型的过程称为:弹/出栈
请添加图片描述

队列

队列的特点:先进先出,后进后出
一端开口:后端
一段开口:前端
数据从后端进入队列模型的过程称为:入队列
数据从前端离开队列模型的过程称为:出队列

栈和队列的小总结

请添加图片描述

数组

数组是一种查询块,增删慢的模型
查询速度快:查询数据通过地址值和索引定位,查询任意数据耗时相同。元素在内存中是连续存储的
删除效率低:要将原始数据删除,同时后面每个数据前移
添加效率低:添加位置后的每个数据前移,再添加元素

链表

单向链表:
链表中的结点都是独立的对象,在内存中是不连续的,每个节点包含数据值和下一个节点的地址
链表查询慢,无论查询哪个数据都要从头开始找
链表增删相对快
每一个都是独立的节点
节点里面有具体的数据,和下一个数据的地址

创建一个链表
会有一个头head,里面有下一个数据的地址,

双向链表
有前后数据的地址值

小总结

栈:后进先出,先进后出
队列:先进先出,后进后出
数组:内存连续区域,查询快,增删慢
链表:元素是游离的,查询慢,首尾操作极快

ArrayList集合

底层原理:
利用空参创建的集合,在底层创建一个默认长度为0的数组(elementDate)
添加第一个元素时,底层会创建一个新的长度为10的数组
存满时,会扩容1.5倍
如果一次添加多个元素,1.5倍还放不下,则新创建数组的长度以实际为准

LinkedList集合

底层数据结构是双链表,查询慢,增删快,但是如果操作的是首尾元素,速度也是极快的.
LinkedList本身多了很多直接操作首尾元素的特有API
底层数据结构是双链表,查询慢,首尾操作的速度是极快的,所以多了很多首尾操作的特有API
public void addFirst(E e)----在列表开头插入指定的元素
public void addLast(E e)----将指定的元素追加到此列表的末尾
public E getFirst()----返回此列表中的第一个元素
public E getLast()----返回此列表中的第一个元素
public E removeFirst()----从此列表中删除并返回第一个元素
public E removeLast()----从此列表中删除并返回最后一个元素

在底层实际上就是创建了一个内部类的对象
这个内部类就表示是ArrayList的迭代器
所以当我们调用多次这个方法的时候,
那么相当于就是创建了多个迭代器的对象

光标:表示是迭代器里面的那个指针,默认指向0索引的位置

结论:在以后如何避免并发修改异常
在使用迭代器或者是增强for遍历集合的过程中,不要使用集合的方法去添加或者删除元素即可

泛型深入

泛型:是JDK5中引入的特性,可以在编译阶段约束操作的数据类型,并进行检查
泛型的格式:<数据类型>
注意:泛型只能支持引用数据类型

如果我们没有给集合指定类型,默认认为所有的数据类型都是Object类型
此时可以往集合添加任意的数据类型
带来一个坏处:我们在获取数据的时候,无法使用它的特有行为
此时推出了泛型,可以在添加数据的时候就把类型进行统一
而且我们在获取数据的时候,也省的强转了,非常的方便

泛型的好处:
统一数据类型
把运行时期的问题提前到来了编译期间,避免了强制类型转换可能出现的异常,因为在编译阶段类型就能确定下来

泛型的细节:
泛型中不能写基本数据类型
指定泛型的具体类型后,传递数据时,可以传入该类类型或者其子类类型
如果不写泛型,类型默认是Object

泛型可以在很多地方进行定义

写在类后面----泛型类
写在方法上面----泛型方法
写在接口后面----泛型接口

泛型类
使用场景:当一个类中,某个变量的数据类型不确定时,就可以定义带有泛型的类

public classArrayList<E>{

}

此时E可以理解为变量,但是不是用来记录数据的,而是记录数据的类型,可以写成T、E、K、V等

当我编写一个类的时候,如果不确定类型,那么这个类就可以定义为泛型类

泛型方法
方法中形参类型不确定时,可以使用类名后面定义的泛型< E>

方法中形参类型不确定时:
1、使用类名后面定义的泛型----所有方法都能用
2、在方法申明上定义自己的泛型----只有本方法能用

修饰符<类型>返回值类型 方法名(类型 变量名){

}
public <T> void show(T t){

}

此处的T可以理解为变量,但是不是用来记录数据的,而是记录类型的

泛型接口

修饰符 interface 接口名<类型>{

}
public interface List<E>{

}

重点:如何使用一个带泛型的接口
方式1:实现类给出具体类型
方法2:实现类延续泛型,创建对象时再确定

泛型的继承和通配符
泛型不具备继承性,但是数据具备继承性

泛型里面写的是什么类型,那么只能传递什么类型的数据

//泛型不具备继承性,但是数据具备继承性
ArrayList<Ye>list1 = new ArrayList<>();
ArrayList<Fu>list2 = new ArrayList<>();
ArrayList<Zi>list3 = new ArrayList<>();

//调用method方法
method(list1);
method(list2);//报错
method(list3);//报错
public static void method(ArrayList<Ye> list){
}

需求:定义一个方法,形参是一个集合,但是集合中的数据类型不确定

ArrayList<Ye>list1 = new ArrayList<>();
ArrayList<Fu>list2 = new ArrayList<>();
ArrayList<Zi>list3 = new ArrayList<>();

method(list1);
method(list2);
method(list3);
public static<E> void (ArrayList<E> list){
}

每一个节点都是独立的对象
每个节点有四个部分
父节点地址、值、左子节点地址、右子节点地址
如果没有,就记录为null

度:每一个节点的子节点数量
二叉树中,任意节点的度<=2

树高:数的总层数

根节点:最顶层的节点

左子节点:左下方的节点

右子节点:右下方的节点

根节点的左子树
根节点的右子树
请添加图片描述

二叉查找树

二叉查找树又称二叉排序树或者二叉搜索树

特点:
每一个节点上最多有两个子节点
任意节点左子树上的值都小于当前节点
任意节点右子树上的值都大于当前节点

添加节点:
按照规则将下列节点添加到二叉查找树中
规则:
小的存左边
大的存右边
一样的不存

二叉树遍历方式

前序遍历
中序遍历
后序遍历
层序遍历

前序遍历

从根节点开始,然后按照当前节点,左子节点,右子节点的顺序遍历

中序遍历

从最左边的子节点开始,然后按照左子节点,当前节点,右子节点的顺序遍历

后序遍历

从最左边的子节点,然后按照左子节点,右子节点,当前节点的顺序遍历

层序遍历:
从根节点开始一层一层的遍历

二叉查找树的弊端
有些只有一个方向一支

平衡二叉树

规则:任意节点左右子树高度差不超过1

通过旋转机制保持树的平衡

旋转机制:左旋、右旋
触发时机:当添加一个节点之后,该树不再是一颗平衡二叉树

旋转前要确定支点,从添加的节点开始,不断的往父节点找不平衡的节点

旋转的步骤:(左旋)
1、
以不平衡的点作为支点
把支点左旋降级,变成左子节点
晋升原来的右子节点

2、
以不平衡的点作为支点
将根节点的右侧往左拉
原先的右子节点变成新的父节点,并把多余的左子节点出让,给已经降级的根节点当右子节点

旋转的步骤:(右旋)
1、
以不平衡的点作为支点
把支点右旋降级,变成右子节点
晋升原来的左子节点
2、
一不平衡的点作为支点
就是将根节点的左侧往右拉
原先的左子节点变成新的父节点,并把多余的右子节点出让,给已经降级的根节点当左子节点

需要旋转的四种情况
左左->一次右旋
当根节点左子树的左子树有节点插入,导致二叉树不平衡

左右->先局部左旋,再整体右旋
当根节点左子树的右子树有节点插入,导致二叉树不平衡

右右->一次左旋
当根节点右子树的右子树有节点插入,导致二叉树不平衡

右左->先局部右旋,再整体左旋
当根节点右子树的左子树有节点插入,导致二叉树不平衡

平衡二叉树小总结

在平衡二叉树中,如何添加节点?

在平衡二叉树中,如何查找单个节点?

为什么要旋转?

旋转的触发时机

左左是什么意思?如何旋转?

左右是什么意思?如何旋转?

右右是什么意思?如何旋转?

右左是什么意思?如何旋转?

红黑树

红黑树是一种自平衡的二叉查找树,是计算机科学中用到的一种数据结构
1972年出现,当时被称为平衡二叉B树。后来,1978年被修改为如今的“红黑树”。
它是一种特殊的二叉查找树,红黑树上的每一个节点都有存储位表示节点的颜色
每一个节点可以是红或者黑;红黑树不是高度平衡的,它的平衡时通过“红黑规则”进行实现的

红黑规则:
1、每一个节点或是红色的,或者是黑色的
2、根节点必须是黑色
3、如果一个节点没有子节点或者父节点,则该节点相应的指针属性值为Nil,这些Nil视为叶节点,每个叶节点(Nil)是黑色的
4、如果某一个节点是红色,那么它的子节点必须是黑色的(不能出现两个红色节点相连的情况)
5、对每一个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点

请添加图片描述

Set系列集合

无序:存取顺序不一致
不重复:可以去除重复
无索引:没有带索引的方法,所以不能使用普通for循环遍历,也不能通过索引来获取元素

Set集合的实现类
HashSet:无序、不重复、无索引
LinkedHashSet:有序、不重复、无索引
TreeSet:可排序、不重复、无索引

Set接口中的方法上基本与Collection的API一致

//1、创建一个Set集合的对象
Set<Stirng> s = new HashSet<>();

//2、添加元素
//如果当前元素是第一次添加,那么可以添加成功,返回true
//如果当前元素是第二次添加,那么添加失败,返回false
boolean r1 = s.add("张三");
boolean r2 = s.add("张三");

System.out.println(r1);//true
System.out.println(r2);//false
System.out.println(s);//[张三]
//迭代器遍历
Iteartor<String> it = s.iterator();
while(it.hasNext()){
	String str = it.next();
	System.out.println(str);
}

//增强for
for(String str : s){
	System.out.println(str);
}

//Lambda表达式
s.forEach(str->System.out.println(str));

小总结

1、Set系列集合的特点
无序、不重复、无索引
Set集合的方法上基本上与Collection的API一致
2、Set集合的实现类特点
HashSet:无序、不重复、无索引
LinkedHashSet:有序、不重复、无索引
TreeSet:可排序、不重复、无索引

HashSet

HashSet集合底层采取哈希表存储数据
哈希表是一种对于增删改查数据性能都较好的结构

哈希表的组成
JDK8之前:数组+链表
JDK8开始:数组+链表+红黑树

哈希值:对象整数的表现形式
根据hashCode方法算出来的int类型的整数
该方法定义在Object类中,所有对象都可以调用,默认使用地址值进行计算
一般情况下,会重写hashCode方法,利用对象内部的属性值进行计算哈希值

对象的哈希值特点
如果没有重写hashCode方法,不同对象计算出的哈希值是不同的
如果已经重写hashCode方法,不同的对象只要属性值相同,计算出来的哈希值就是一样的
在小部分情况下,不同的属性值或者不同的地址值计算出来的哈希值也有可能一样(哈希碰撞)

Student s1 = new Student(name:"zhangsan",age:23);
Student s2 = new Student(name:"zhangsan",age:23);

System.out.println(s1.hashCode());
System.out.println(s2.hashCode());

System.out.println("abc".hasCode());
System.out.println("acD".hasCode());
HashSet 底层逻辑

1、创建一个默认长度为16.默认加载因子为0.75(hashSet的扩容时机,当长度为16时,内部已经存入了12个就会扩容至原来的两倍)的数组,数组名table
2、根据元素的哈希值根数组的长度计算出应存入的位置(int index = (数组长度-1)&哈希值;)
3、判断当前位置是否为null,如果是null直接存入
4、如果位置不为null,表示有元素,则调用equals方法比较属性值
5、一样:不存 不一样:存入数组,形成链表
JDK8以前:新元素存入数组,老元素挂在新元素下面
JDK8以后:新元素直接挂在老元素下面

JDK8以后,当链表长度大于8而且数组长度大于等于64就会转成红黑树

如果集合中存储的是自定义对象,必须要重写hashCode和equals方法

HashSet的三个问题

问题1、HashSet为什么存和取的顺序不一样?
问题2、HashSet为什么没有索引?
问题3、HashSet是利用什么机制保证数据去重的?

问题1:
HashSet为什么存和取的顺序不一样?
它是通过索引链表的方式,如果索引里面存的是链表,那就先遍历链表

问题2:
HashSet为什么没有索引
因为HashSet不够纯粹,底层是数组、链表、红黑树三个组合而成的

问题3:
HashSet是利用什么集至保证数据去重的
HashCode方法和equals方法

LinkedHashSet

有序、不重复、无索引
这里的有序指的是保证存储和取出的元素顺序一致
原理:底层数据结构依然是哈希表,只是每个元素又额外多了一个双链表的机制记录存储的顺序

LinkedHashSet集合的特点和原理是怎么样的?
有序、不重复、无索引
底层基于哈希表,使用双链表记录添加顺序
在以后如果要数据去重,我们使用哪个?
默认使用HashSet
如果要求去重且存取有序,才使用LinkedHashSet

TreeSet

不重复、无索引、可排序
可排序:按照元素的默认规则(从小到大)排序
TreeSet集合底层是基于红黑树的数据结构实现排序的,增删改查性能都较好

需求:利用TreeSet存储整数并排序

TreeSet<Integer> ts = new TreeSet<>();

ts.add(4);
ts.add(5);
ts.add(1);
ts.add(3);
ts.add(2);

System.out.println(ts);
//迭代器
Iterator<Integer> it = ts.iterator();
while(it.hasNext()){
	int i = it.next();
	System.out.println(i);
}

//增强for
for(int t:ts){
	System.out.println(t);
}

//Lambda表达式

ts.forEach(i->System.out.println(i));

TreeSet集合默认的规则
对于数值类型:Integer,Double,默认按照从小到大的顺序进行排列
对于字符、字符串类型:按照字符的ASCII码表中的数字升序进行排列

TreeSet的两种比较方式
方式一:默认排序/自然排序:Javabean类是实现Comparable接口指定比较规则

方式二:比较器排序:创建TreeSet对象时候,传递比较器Comparator制定规则

使用原则:
默认使用第一种,如果第一种不能满足当前需求,就使用第二种

小总结

1、TreeSet集合的特点是怎么样的?
可排序、不重复、无索引
底层基于红黑树实现排序,增删改查性能较好
2、TreeSet集合自定义排序规则有几种方式
方式一:Javabean类实现Comparable接口,指定比较规则
方式二:创建集合时,自定义Comparator比较器对象,指定比较规则
3、方法返回值的特点
负数:表示当前要添加的元素是小的,存左边
正数:表示当前要添加的元素是大的,存右边
0:表示当前要添加的元素已经存在,舍弃

小总结

1、如果想要集合中的元素可重复
用ArrayList集合,基于数组的(用的最多)
2、如果想要集合中的元素可重复,而且当前的增删操作明显多于查询
用LinkedList集合,基于链表
3、如果想对集合中的元素去重
用HashSet集合,基于哈希表的(用的最多)
4、如果想对集合中的元素去重,而且保证存取顺序
用LinkedHashSet集合,基于哈希表和双链表,效率低于HashSet
5、如果想对集合中的元素进行排序
用TreeSet集合,基于红黑树。后续也可以使用List集合实现排序

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

优降宁

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值