1.1.集合框架简介
官方教程
集合框架:用于存储数据的容器。
Java 集合框架Java Collection Framework
,又被称为容器 container
,是定义在 java.util
包下的一组接口 interfaces
和其实现类 classes 。
1.2. 数据结构
数据结构是计算机存储和组织数据的方式,互相之间存在一种或多种特定关系的数据元素的集合。
- 🟠类和接口:
- Collection:是一个接口,包含了大部分容器常用的一些方法。
- List:是一个接口,规范了
ArrayList
和 LinkedList中要实现的方法。- ArrayList:实现了
List接口
po,底层为动态类型顺序表。
- ArrayList:实现了
- LinkedList:实现了
List接口
,底层为双向链表。 - Stack:底层是栈,栈是一种特殊的顺序表。
- Queue:底层是队列,队列是一种特殊的顺序表。
- Deque:是一个接口。
- Set:集合,是一个接口,里面放置的是K模型
- HashSet:底层为哈希桶,查询的时间复杂度为
O(1)
。 - TreeSet:底层为红黑树,查询的时间复杂度为O( log 2 N \log_2N log2N),关于key有序的。
- Map:映射,里面存储的是K-V模型的键值对
- HashMap:底层为哈希桶,查询时间复杂度为O(1)。
- TreeMap:底层为红黑树,查询的时间复杂度为O( log 2 N \log_2N log2N),关于key有序。
注释:
- O( log 2 N \log_2N log2N):以2为底多少次方等于N,一般写成O(logN),表示的就是O( log 2 N \log_2N log2N)。
1.3. 数组和集合的区别
数组和集合的区别:
- 数组是固定长度的;集合可变长度的。
- 数组可以存储基本数据类型,也可以存储引用数据类型;集合只能存储引用数据类型。
- 数组存储的元素必须是同一个数据类型;集合存储的对象可以是不同数据类型。
2. 时间和空间复杂度
2.1 算法效率
算法(Algorithm):就是定义良好的计算过程,他取一个或一组的值为输入,并产生出一个或一组值作为输出。简单来说算法就是一系列的计算步骤,用来将输入数据转化成输出结果,而算法效率就是根据每一条语句执行的次数和产生的空间大小来估算出一个程序的效率。
- 算法效率分为两种:
- 时间效率:也被称为时间复杂度,主要用来衡量一个算法的运行速度。
- 空间效率:也被称为空间复杂度,主要用来衡量一个算法所需要的额外空间。
在计算机发展的早期,计算机的存储容量很小,所以对空间复杂度很是关注,但是经过计算机行业的迅速发展,如今的存储容量已经达到很高的程度,比如512GB、1TB已经不再像从前那么罕见了, 所以我们现在更加关注一个算法的时间复杂度。
2.2 时间复杂度
时间复杂度说一个数学函数,一个算法所花费的时间与程序中语句的执行次数成正比,我们编译的算法基本操作次数,为算法的时间复杂度。
大O渐进表示法
func()函数基本操作的次数: F(N)=N^2+2*N+10。
- N = 10 : F(N) =130 。
- F(N) = 10^2+2*10+10 = 130。
- N = 100 : F(N) = 10210。
- N = 1000 : F(N) = 1002010。
实际上我们只需要大概的执行次数,那么我们应该用大O
的渐进表示法。
- 大O符号(Big O notation):是用于描述函数渐进行为的数学符号。
推导大O 阶方法:F(N)=N^2+2*N+10。
- 用常数1取代运行时间中的所有加法常数。(F(N)=N^2+2*N+1。)
- 修改后的运行次数函数中,只保留最高阶项。(F(N)=N^2
。)+2*N+1
- 如果最高阶项存在且不是1,则去除这个项相
乘的常数,得到的结果就是大O阶。(eg:10*N2, 去除10,只保留N2)
使用大O阶的渐进表示法后,func()函数的时间复杂度为:O(N2)。
- 时间复杂度有最好、最坏、平均之分:
- 最好的情况:程序运行最少的次数。(下限)
- 最坏的情况:程序运行最多的次数。(上限)
- 平均情况:程序期望的运行次数。
int[] arr = {1,23,5,6,7,8,9,10};
int targer = 9;
for (int j = 0; j < arr.length; j++) {
if(arr[i] == target){
return;
}
}
- 例如:在一个为N个数组长度中查找一个数据target。
- 最好的情况:数组一个元素就是target,1次找到。
- 最坏的情况:数组最后一个元素说target,N次找到。
- 平均的情况:N/2次找到。
在一般情况下关注的说算法最坏的运行情况,所以数组查找一个元素的时间复杂度为O(N)。
- 常见的时间复杂度量级
- 常数阶O(1)
- 对数阶O(logN)
- 线性阶O(n)
- 线性对数阶O(nlogN)
- 平方阶O(n2)
- 立方阶O(n3)
- K次方阶O(nk)
- 指数阶(2n)
上面从上至下依次的时间复杂度越来越大,执行的效率越来越低
时间复杂度练习题
- 例题1
// 计算func2的时间复杂度?
public void func2 ( int N){
int count = 0;
for (int k = 0; k < 2 * N; k++) {
count++;
}
int M = 10;
while ((M--) > 0) {
count++;
}
System.out.println(count);
}
解题:实例1基本操作执行了2N+10次,通过推导大O阶方法知道,时间复杂度为 O(N)
- 例题2
// 计算func3的时间复杂度?
void func3(int N, int M) {
int count = 0;
for (int k = 0; k < M; k++) {
count++;
}
for (int k = 0; k < N ; k++) {
count++; }System.out.println(count);
}
解题: 实例2基本操作执行了M+N次,有两个未知数M和N,时间复杂度为 O(N+M)
- 例题3
// 计算func3的时间复杂度?
public void func4(int N) {
int count = 0;
for (int k = 0; k < 100; k++) {
count++; }System.out.println(count);
}
解题:实例3基本操作执行了100次,通过推导大O阶方法,时间复杂度为 O(1)。
- 例题4
// 计算bubbleSort的时间复杂度?
void bubbleSort(int[] array) {
for (int end = array.length; end > 0; end--) {
boolean sorted = true;
for (int i = 1; i < end; i++) {
if (array[i - 1] > array[i]) {
Swap(array, i - 1, i);
sorted = false;
}
}
if (sorted == true) {
break;
}
}
}
**解题:*实例4基本操作执行最好N次,最坏执行了(N(N-1))/2次,通过推导大O阶方法+时间复杂度一般看最坏,时间复杂度为 O(N2)
- 例题5
// 计算binarySearch的时间复杂度?
int binarySearch(int[] array, int value) {
int begin = 0;
int end = array.length - 1;
while (begin <= end) {
int mid = begin + ((end-begin) / 2);
if (array[mid] < value){
begin = mid + 1;
} else if (array[mid] > value) {
end = mid - 1;
}else{
return mid;
}
}
return -1;
}
解题: 实例5基本操作执行最好1次,最坏 次
log
2
N
\log_2N
log2N,时间复杂度为 O(
log
2
N
\log_2N
log2N)
ps:
log
2
N
\log_2N
log2N 在算法分析中表示是底数为2,对数为N,有些地方会写成lgN(文章前段已经讲解了)。(二分查找每次排除掉一半的不适合值,一次二分剩下:n/2两次二分剩下:n/2/2 = n/4。二分查找的时间复杂度为O(
log
2
N
\log_2N
log2N ))
- 例题6
// 计算阶乘递归factorial的时间复杂度?
long factorial(int N) {
return N < 2 ? N : factorial(N-1) * N;
}
解题: 实例6通过计算分析发现基本操作递归了N次,时间复杂度为O(N)。
- 例题7
// 计算斐波那契递归fibonacci的时间复杂度?
int fibonacci(int N) {
return N < 2 ? N : fibonacci(N-1)+fibonacci(N-2);
}
解题: 实例7通过计算分析发现基本操作递归了2N次,时间复杂度为O(2N )。(建议画图递归栈帧的二叉树理解)
3.空间复杂度
空间复杂度表示的说临时占用存储空间大小的量度。也可以使用大O的渐进表示法。
- 空间复杂度比较常用的有:O(1)、O(n)、O(n²)。
时间复杂度O(1):
使用了常数个额外空间,所以空间复杂度为O(1)。
// 计算bubbleSort的空间复杂度?
void bubbleSort2(int[] array) {
for (int end = array.length; end > 0; end--) {//int end 开辟1个空间
boolean sorted = true;//开辟1个空间
for (int i = 1; i < end; i++) {//int i 开辟1个空间
if (array[i - 1] > array[i]) {
Swap(array, i - 1, i);
sorted = false;
}
}
if (sorted == true) {
break;
}
}
}
时间复杂度O(N):动态开辟了N个空间,空间复杂度为 O(N)
// 计算fibonacci的空间复杂度?
public int[] fibonacci2(int n) {
int[] fibArray = new int[n + 1];//开辟了N+1个空间
fibArray[0] = 0;
fibArray[1] = 1;
for (int i = 2; i <= n ; i++) {//int i 开辟一个空间
fibArray[i] = fibArray[i - 1] + fibArray [i - 2];
}
return fibArray;
}