一、评估算法优劣的核心指标是什么?
1、时间复杂度(流程决定)
2、额外空间复杂度(流程决定)
3、常数项时间(实现细节决定)
二、常数操作
1、什么是常数时间的操作?
如果一个操作的执行不以具体样本量为转移,每次执行时间都是固定时间。称这样的操作为常数时间的操作。
比如:执行一个数组的寻址,执行的时间和数据量没关系,第1000个位置和第1000万个位置,时间是一样的,计算机用偏移量获得。
2、常见的常数时间的操作
(1)常见的算术运算(+、-、*、/、%等)
(2)常见的位运算(>>、>>>、<<、|、&、^等)
>>是带符号右移,>>>是不带符号右移
(3)赋值、比较、自增、自减操作等
(4)数组寻址操作
总之,执行时间固定的操作都是常数时间的操作。
反之,执行时间不固定的操作,跟样本量有关的,都不是常数时间的操作。
3、什么不是常数时间操作
LinkedList,底层是一个双向列表。
要在list里面get一个数据,它不会像数组寻址一样,因为它不是连续区间。它是从0位置往下数。
4、时间复杂度就是来衡量流程中发生了多少次常数操作这件事
举例:选择排序
(1)先在0到n-1中,找到最小值位置在哪儿,然后把最小值和0位置的数交换
(2)0位置的数搞定了
(3)在1到n-1中,找到最小值的位置在哪儿,然后把最小值和1位置的数交换
(4)然后重复2到n-1,3到n-1。。。一直到n-1到n-1
这个流程发生了多少次常数操作?
步骤:
(1)单次:看+比
(2)一次换
n * (看+比) + 1
(n-1) * (看+比) + 1
(n-2) * (看+比) + 1
...
看是1次,比也是1次
总的表达式变为:
n * (2) + 1
(n-1) * (2) + 1
(n-2) * (2) + 1
...
= 2 * (n + n-1 + n-2 + ... + 1) + n
等差数列可以化简为(n(1+n)/2把系数换成a、b加个常数c)
= 2an² + bn + n + c
a、b、c是常数
最终的时间复杂度是抹掉常数项、高阶项系数和低阶项,最终得到n²(n的平方)
三、如何确定算法流程的总操作数量与样本数量之间的表达式关系?
1、想象该算法流程所处理的数据状况,要按照最差情况来。
2、把整个流程彻底拆分为一个个基本动作,保证每个动作都是常数时间操作。
3、如果数据量为N,看基本动作的数量和N是什么关系。
四、如何确定算法流程的时间复杂度?
当完成了表达式的建立,只要把最高阶项留下即可。低阶项都去掉,高阶项的系数也去掉。
记为:O(忽略掉系数的高阶项)
当样本量大到足够大的时候,决定算法快慢的就是高阶项的东西。
五、时间复杂度的意义
1、抹掉了好多东西,只剩下了一个最高阶项,那这个东西有什么意义呢?
2、时间复杂度的意义在于:
当我们要处理的样本量很大很大时,我们会发现低阶项是什么不是最重要的;每一项的系数是什么,不是最重要的。真正重要的就是最高阶项是什么。
3、这就是时间复杂度的意义,它是衡量算法流程的复杂度的一种指标,该指标只与数据量有关,与过程之外的优化无关。
六、实践时间复杂度的估算
1、选择排序
分析:见上面
public static void selectionSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
// 0~n-1
// 1~n-1
// 2~n-1
for (int i=0; i<arr.length-1; i++) { // i~n-1
// 最小值在哪个位置上i~n-1
int minIndex = i;
for (int j=0; j<arr.length; j++) { // i~n-1上找最小值的下标
minIndex = arr[j] < arr[minIndex] ? j : minIndex;
}
}
}
public static void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
2、冒泡排序
3、插入排序