导学
这次课程主要涉及到的是List和Map相关的面试题,比较高频就是
-
ArrayList
-
LinkedList
-
HashMap
-
ConcurrentHashMap
- ArrayList底层实现是数组
- LinkedList底层实现是双向链表
- HashMap的底层实现使用了众多数据结构,包含了数组、链表、散列表、红黑树等
在讲解这些集合之后,我们会讲解数据结构,知道了数据结构的特点之后,熟悉集合就更加简单了。在讲解数据结构之前,我们也会简单普及一下算法复杂度分析,让大家能够评判代码的好坏,也能更加深入去理解数据结构和集合。
1 算法复杂度分析
1.1 为什么要进行复杂度分析?
我们先来看下面这个代码,你能评判这个代码的好坏吗?
/**
** *求**1~n**的累加和
** @param* *n
** @return
*/
public int sum(int n) {
int sum = 0;
for ( int i = 1; i <= n; i++) {
sum = sum + i;
}
return sum;
}
其实学习算法复杂度的好处就是:
-
指导你编写出性能更优的代码
-
评判别人写的代码的好坏
相信你学完了算法复杂度分析,就有能力评判上面代码的好坏了
关于算法复杂度分析,包含了两个内容,一个是时间复杂度,一个是空间复杂度,通常情况下说复杂度,都是指时间复杂度,我们也会重点讲解时间复杂度
1.2 时间复杂度
1.2.1 案例
时间复杂度分析:简单来说就是评估代码的执行耗时的,大家还是看刚才的代码:
/**
** *求**1~n**的累加和
** @param* *n
** @return
*/
public int sum(int n) {
int sum = 0;
for ( int i = 1; i <= n; i++) {
sum = sum + i;
}
return sum;
}
分析这个代码的时间复杂度,分析过程如下:
1.假如每行代码的执行耗时一样:1ms
2.分析这段代码总执行多少行?3n+3
3.代码耗时总时间: T(n) = (3n + 3) * 1ms
T(n):就是代码总耗时
我们现在有了总耗时,需要借助大O表示法来计算这个代码的时间复杂度
1.2.2 大O表示法
大O表示法:不具体表示代码真正的执行时间,而是表示代码执行时间随数据规模增长的变化趋势。
刚才的代码示例总耗时公式为:T(n) = (3n + 3) * 1ms
其中 (3n + 3) 是代码的总行数,每行执行的时间都一样,所以得出结论:
T(n)与代码的执行次数成正比(代码行数越多,执行时间越长)
不过,大O表示法只需要代码执行时间与数据规模的增长趋势,公式可以简化如下:
T(n) =O(3n + 3)------------ T(n) = O(n)
当n很大时,公式中的低阶,常量,系数三部分并不左右其增长趋势,因此可以忽略,我们只需要记录一个最大的量级就可以了
下图也能表明数据的趋势
1.2.3 常见复杂度表示形式
速记口诀:常对幂指阶
越在上面的性能就越高,越往下性能就越低
下图是一些比较常见时间复杂度的时间与数据规模的趋势:
1.2.4 时间复杂度O(1)
实例代码:
public int test01(int n){
int i=0;
int j = 1;
return i+j;
}
代码只有三行,它的复杂度也是O(1),而不是O(3)
再看如下代码:
public void test02(int n){
int i=0;
int sum=0;
for(;i<100;i++){
sum = sum+i;
}
System.out.println(sum);
}
整个代码中因为循环次数是固定的就是100次,这样的代码复杂度我们认为也是O(1)
一句话总结:只要代码的执行时间不随着n的增大而增大,这样的代码复杂度都是O(1)
1.2.5 时间复杂度O(n)
实例代码1:
/**
* 求1~n的累加和
* @param n
* @return
*/
public int sum(int n) {
int sum = 0;
for ( int i = 1; i <= n; i++) {
sum = sum + i;
}
return sum;
}
一层for循序时间复杂度就是O(n)
实例代码2:
public static int sum2(int n){
int sum = 0;
for (int i = 1; i < n; ++i) {
for (int j = 1; j < n; ++j) {
sum = sum + i * j;
}
}
return sum;
}
这个代码的执行行数为:O( 3n^2 + 3n + 3 ),不过,依据大O表示的规则:常量、系数、低阶,可以忽略
所以这个代码最终的时间复杂度为:O(n^2)
1.2.6 时间复杂度O(logn)
对数复杂度非常的常见,但相对比较难以分析,实例代码:
public void test04(int n){
int i=1;
while(i<=n){
i = i * 2;
}
}
分析这个代码的复杂度,我们必须要再强调一个前提:复杂度分析就是要弄清楚代码的执行次数和数据规模n之间的关系
以上代码最关键的一行是:i = i * 2
,这行代码可以决定这个while循环执行代码的行数,i
的值是可以无限接近n
的值的。如果i
一旦大于等于了n
则循环条件就不满足了。也就说达到了最大的行数。我们可以分析一下i
这个值变化的过程
分析过程如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-emjcEdoe-1683451730102)(Java集合相关面试题.assets/image-20230427174832858.png)]
由此可知,代码的时间复杂度表示为O(log n)
1.2.7 时间复杂度O(n * log n)
分析完O( log n ),那O( n * log n )就很容易理解了,比如下列代码:
public void test05(int n){
int i=0;
for(;i<=n;i++){
test04(n);
}
}
public void test04(int n){
int i=1;
while(i<=n){
i = i * 2;
}
}
1.3 空间复杂度
空间复杂度全称是渐进空间复杂度,表示算法占用的额外存储空间与数据规模之间的增长关系
看下面代码
public void test(int n){
int i=0;
int sum=0;
for(;i<n;i++){
sum = sum+i;
}
System.out.println(sum);
}
代码执行并不需要占用额外的存储空间,只需要常量级的内存空间大小,因此空间复杂度是O(1)
再来看一个其他例子:
void print(int n) {
int i = 0;
int[] a = new int[n];
for (i; i <n; ++i)