趁热打铁
2.认识O(NlogN)的排序
2021年9月26日 11点33分
1、 递归行为 (02:53)
求最大值为例
给定一个乱序数组,求数组的最大值
流程:
①找到最中间的数 mid
②找左边的最大值和右边的最大值并比较
③递归调用
无懈可击的中点表示法 (04:36)
求中点mid时通常采用
int mid = ( L + R ) / 2;
这种写法可能会出现问题,即当开辟的数组很大很长时,L+R可能存在溢出的风险;所以采用以下的写法:
int mid = L + ((R - L)>> 1);
这种方法是一定不会出现溢出的,不解释了。
贴一张代码:
2、 master公式(16:54)
公式的形式:T(N) = a * T(N/b)+ O(N^d)
以下的log都是以b为底,a的对数
log < d , O(N^d);
log > d , O(N^log) ;
log == d,O(N^d * logN(这个是以2为底的))
解释一下,
等号左边的T(N)是母问题的数据量(又看了一遍改的之前写错的写成了时间复杂度)
右边的a是指该递归算法将问题转化成了几部分(调用了几次),
T(N/b)指的是子问题的数据量就是递归的一部分有多少数据量
O(N^d)指的是除去递归调用,算法中其他代码的时间复杂度
以求最大值为例:
如果选择将整个数组分成两部分前一半求最大,后一半求最大,那么整个子问题的数据量就是N/2,a = 2,b = 2,根据代码可以计算O(N),即d = 0;
如果选择前2/3求最大,后2/3求最大,那么子问题数据量为2N/3,a仍然是2,b = 3/2,d = 0;
如果选择调用三次,前1/3,中间1/3,后1/3,那么这个时候,a = 3;b = 3,d = 0;
如果分成了前2/3,后1/3那么就不符合master公式了,因为不符合等规模了。
如果给源代码加入了一个遍历数组,那么会令d = 1,不解释。
3、 利用master公式求解递归的时间复杂度 (30:42)
公式的形式:T(N) = a * T(N/b)+ O(N^d)
以下的log都是以b为底,a的对数
log < d , O(N^d);
log > d , O(N^log) ;
log == d,O(N^d * logN(这个是以2为底的))
这些公式的应用都是只需要看顶层递归函数就可以的不要展开啊啥的;就是看代码,对数值,计算比较即可。
4、 归并排序 (37:52)
流程:
①找到mid,左边排序,右边排序
②合并两边的有序数组(merge)
合并过程是外排序,开辟辅助数组和两个指针分别指向两个已排好数组的最左端,比较所指数字小的copy到辅助数组中,重复此过程
直到有一个指针越界(有一个数组空了),将未越界指针所在数组全部copy到辅助数组中
最后将辅助数组copy回原数组即可。
代码 (37:52)
5、 归并排序的拓展——小和问题 (01:01:25)
小和问题描述 :对于一个乱序数组,从第一个数开始到最后一个,其左侧较小的数的总和叫做小和;
比如:
1 3 2 5 3 4
对于1左侧没有;
对于3左侧有1,此时小和 = 1;
对于2左侧有1,此时小和 = 1 + 1 = 2;
对于5左侧有1 3 2,此时小和 = 2 + 1 + 3 + 2 = 8;
对于3左侧有1 2,此时小和 = 8 + 3 =11;
对于4左侧有1 3 2 3,此时小和 = 20.
转换一种思路:
1右侧比他大的数有3 2 5 3 4,五个所以1对于小和的贡献为1*5;
3右侧的较大数有5 4,两个所以3对于小和的贡献为3*2;
2右侧3个贡献为2*3;
5没有;
3一个,3*1;
4右侧没有数。
小和 = 5+6+6+3 = 20。
代码(01:18:06)
注意利用merge计算小和时,当遇到指针所指相等情况,与归并不同的时,求小和时要先将,右侧的指针右移,为的是能够更便捷的计算得出,右侧数组中比左侧 [i] 大的数有几个。
不会重不会漏不好解释,忘了看原视频;理解记忆
逆序对(01:28:45)
一样的
6、 快排之荷兰国旗简化 (01:39:59)
问题描述:对于乱序数组,给定一个num,设计算法使得≤num的数在数组的左侧(不需要有序),大于num的数在数组的右侧;
流程:
①设置一个小于等于的标记less,指向第一个元素之前,设置指针 i 指向数组中第一个元素;
②i所指元素与num比较大小,如果 [i] > num,i++; 如果 [i] < num , [i] 和less右侧第一个元素交换,less ++;i++重复②;
③当i越界时,目标达成。
实质:使用较小数推动较大的数,直到待定区域没有元素(i越界)。
7 、 荷兰国旗问题
在上述问题基础上要求将等于num的数放在中间,荷兰国旗问题。
实现过程与上题相似:
流程:
①设置一个小于的标记less,指向第一个元素之前,大于的标记more,指向最后一个标记之后,设置指针 i 指向数组中第一个元素;
②i所指元素与num比较大小,如果 [i] == num,i++; 如果 [i] < num , [i] 和less右侧第一个元素交换,less ++,i++;如果 [i] > num,[i]和more左侧第一个元素交换,more--,i保持不动,重复②;
③直到 i 和 more重合。
实质:小数推动等于部分,一个一个减少待定。
8、 快排1.0 (02:02:08)
流程:
①找数组中最后一个数,按照荷兰国旗问题算法将数组分成左右和最后一个单独数字的形式,交换该数字和大数第一个;
②递归调用;
③全部有序。
如果每次选择的都是最大或最小的数字作为划分,那么就是最坏情况。(时间复杂度是多少?)
9、 快排2.0 (02:06:02)
比1.0 多了一部分就是把等于划分依据的数字又划了一片,这样的话就把整个数组分成了三片,小于等于大于然后把最后面的划分依据和第一个大于的数字交换
10 、 快排3.0 (02:16:42)
3.0版本优化了可能会遇到的最坏情况,当每次划分的依据都是最坏的时候就是最坏情况。
利用随机数生成就可以避免这种情况的发生。
最终利用概率求期望得到该算法的时间复杂度是O(N*logn)
代码(02:21:43)