我们经常把时间复杂度作为算法效率的指标之一。那么我们怎么去计算一个算法的时间复杂度呢?
计算原则
首先,算法时间复杂度并不是指算法的总执行时间,而是基本操作的总次数。用一句话来概括:算法中基本操作的执行次数可以作为算法时间复杂度的度量。
因此:对一个算法的复杂度分析有以下的几个步骤:
- 确定算法中的基本操作以及问题的规模
- 根据基本操作执行情况确定执行次数
- 利用大O表示法,省略系数、低阶、常量,具体就是找出
中变化最快的项作为时间复杂度的度量
从上面我们可以得到下面结论:
与
无关时,
与
是线性关系时,
与
是二次关系时,
- 以此类推
并且根据数学性质,我们还有以下结论:
上面说的比较抽象,我们来用demo来学习一下如何计算
案例
案例1
function run(n){
let i = 0,j=100;
while(i<n){
++j;
i+=2;
}
}
分析:
- 找出基本规模,确定规模
- 因为依赖于内层的
while循环,所以++j,i+=2为基本操作 - 循环条件
i<n,所以规模就是参数
n
- 因为依赖于内层的
- 确定执行次数
- 循环的开始与结束与
i有关,i每次自增2,假设自增m次后循环结束,最后,
(K为修正n和m的接近程度的常数)
- 最后,得到
,即
- 循环的开始与结束与
- 利用大O表示法,省略系数、低阶、常量
- 很显然,
是增长最快的项,去除系数
- 得到
- 很显然,
案例2
冒泡排序(简单版)
function run(){
let arr = [3,4,1,2];
for (let j = 0; j < arr.length - 1; j++) {
// 这里要根据外层for循环的 j,逐渐减少内层 for循环的次数
for (let i = 0; i < arr.length - 1 - j; i++) {
if (arr[i] > arr[i + 1]) {
let temp = arr[i];
arr[i] = arr[i + 1];
arr[i + 1] = temp;
}
}
}
}
分析:
- 找出基本规模,确定规模
- 因为依赖于内层的循环,所以
let temp = arr[i],arr[i] = arr[i + 1],arr[i + 1] = temp为基本操作 - 循环条件
arr的length,所以规模就是
arr.length
- 因为依赖于内层的循环,所以
- 确定执行次数
- 外层必定循环
次,内层因为
if的判断条件,分为两种情况 - 最好的情况(全部正序):
- 最坏的情况(全部倒序):
- 外层必定循环
- 利用大O表示法,省略系数、低阶、常量
- 因此,最好的情况是
,最坏的情况是
- 利用加权平均值得平均时间复杂度
(不懂的话可以看看概率论)
- 因此,最好的情况是
案例三
function run(n){
let i = 0,s = 0;
while(s<n){
++i;
s=s+i;
}
}
- 找出基本规模,确定规模
- 因为依赖于内层的
while循环,所以++i,s=s+i为基本操作 - 循环条件
s<n,所以规模就是参数
n
- 因为依赖于内层的
- 确定执行次数
- 循环的开始与结束与
s,i有关,假设自增m次后循环结束,因此,
,
,... ,
=
,因此
- 最后,得到
,即
- 循环的开始与结束与
- 利用大O表示法,省略系数、低阶、常量
- 得到
- 得到
到了,到此,我相信各位同学已经知道了计算时间复杂度的方式了,各位同学如果还有兴趣的话,可以自己去把排序算法都计算一下,可以加深一下自己的认知。
参考文档
- 《天勤数据结构》