算法复杂度分析
时间复杂度分析
假设每行代码的执行时间是相同的,因为每行都是类似的操作,读数据-运算-写数据,那么所有代码的执行时间T(n)与每行代码的执行次数成正比,这就是常见的大O时间复杂度表示法 T(n) = O (f(n)), f(n)表示的是执行次数,这个也被称作渐进时间复杂度,表示的是代码执行时间随数据规模增长的变化趋势。
因为关注的是趋势,所以在数据规模很大的时候,分析一段代码的时间复杂度,只需要关注循环执行次数最多的那一段代码,也就是说可以忽略掉公式中的常量,低阶和系数,只需要记录一个最大阶的量级。
但是在数据规模很小的时候,低阶和系数这些东西不能直接忽略。
时间复杂度分析的基本方法
- 只关注循环执行次数最多的那段代码
- 加法原则:总复杂度等于量级最大的那段代码的复杂度
- 乘法原则:嵌套代码的复杂度等于嵌套内外代码复杂度的乘积(就是说循环嵌套的数据规模大小不一样)
int cal(int n) {
int ret = 0;
int i = 1;
for (; i < n; ++i) {
ret = ret + f(i);
}
}
int f(int n) {
int sum = 0;
int i = 1;
for (; i < n; ++i) {
sum = sum + i;
}
return sum;
}
单独看 cal() 函数。假设 f() 只是一个普通的操作,那第 4~6 行的时间复杂度就是,T1(n) = O(n)。但 f() 函数本身不是一个简单的操作,它的时间复杂度是 T2(n) = O(n),所以,整个 cal() 函数的时间复杂度就是,T(n) = T1(n) * T2(n) = O(n*n) = O(n2)
常见的时间复杂度
时间复杂度总体上可以分为两类,多项式量级和非多项式量级
非多项式量级:指数阶 O(2^n) 和 阶乘阶 O(n!), 时间复杂度为非多项式量级的算法问题被称为NP(非确定多项式)问题
多项式量级:常量阶 O(1) 对数阶 O(logn) 线性阶 O(n) 线性对数阶 O(nlogn) 平方阶 O(n^2) 立方阶 O(n^3)
注意点
-
常量阶指的是代码的执行时间与数据规模无关,也就是说算法中不存在循环语句,递归语句,时间复杂度都为常量阶O(1)
-
对数阶最终都用logn来进行表示,原因在于不同对数之间是可以相互转换的,那么任何对数阶的时间复杂度都相当于logn乘了一个系数,而系数是可以忽略的
-
O(m+n) 是指的m和n表示两个数据规模,无法实现评估m和n的量级大小,所以在计算复杂度时就不用直接用加法原则,省略掉其中一个
int cal(int m, int n) { int sum_1 = 0; int i = 1; for (; i < m; ++i) { sum_1 = sum_1 + i; } int sum_2 = 0; int j = 1; for (; j < n; ++j) { sum_2 = sum_2 + j; } return sum_1 + sum_2; }
时间复杂度的几种情况
最好情况时间复杂度:在最理想的情况下,执行这段代码的时间复杂度
最坏情况时间复杂度:在最糟糕的情况下,执行这段代码的时间复杂度
平均情况时间复杂度:其实平均情况时间复杂度就是期望时间复杂度,跟各种情况出现的概率有关
均摊情况时间复杂度:均摊是一种特殊的平均情况时间复杂度,常见的场景为对于一个数据结构进行一组连续操作中,大部分情况时间复杂度都很低,只有个别情况下时间复杂度比较高,而且这些操作之间存在前后连贯的时序关系,这个时候可以将这一组操作放在一块进行分析,将较高时间复杂度的那次操作的耗时平均摊到其他时间复杂度比较低的操作上。
// 全局变量,大小为10的数组array,长度len,下标i。
int array[] = new int[10];
int len = 10;
int i = 0;
// 往数组中添加一个元素
void add(int element) {
if (i >= len) { // 数组空间不够了
// 重新申请一个2倍大小的数组空间
int new_array[] = new int[len*2];
// 把原来array数组中的数据依次copy到new_array
for (int j = 0; j < len; ++j) {
new_array[j] = array[j];
}
// new_array复制给array,array现在大小就是2倍len了
array = new_array;
len = 2 * len;
}
// 将element放到下标为i的位置,下标i加一
array[i] = element;
++i;
}
最好时间复杂度:插入元素时,数组未满,直接插入,时间复杂度为O(1)
最坏时间复杂度:插入元素时,数组满了,需要进行扩容,时间复杂度为O(n)
平均时间复杂度:插入元素时,时间复杂度为 O(1),其实可以分为 n + 1种情况,计算方法为
(1+1 + 1 + … + 1) / (n + 1) + n / (n+ 1) = 2n / (n + 1) = O(1)
均摊时间复杂度:大部分情况下,复杂度为O(1), 间隔n次,复杂度为O(n),均摊到前面,最终的时间复杂度为 O(1)
空间复杂度
空间复杂度的分析跟时间复杂度相同,就是申请给变量的空间大小与数据规模之间的关系。常见的就是空间复杂度就是 O(1)、O(n)、O(n^2 )
以上是学习极客时间中王争的课程《数据结构与算法》的笔记,如有侵权,请及时联系,笔记只是记录个人收获,大家想学习的话,建议去看原版课。
相关链接:https://time.geekbang.org/