大 O
复杂度表示法
复杂度: T(n)=(2n+2)*unit_time
int cal(int n) {
int sum = 0;
int i = 1;
for (; i <= n; ++i) {
sum = sum + i;
}
return sum;
}
复杂度: T(n)=(2n+2n+3)*unit_time
int cal(int n) {
int sum = 0;
int i = 1;
int j = 1;
for (; i <= n; ++i) {
j = 1;
for (; j <= n; ++j) {
sum = sum + i * j;
}
}
}
公式总结:
可以只记录最大的数量级: T(n)=O(n)
, T(n)=O(n)
大 O 表示法不是真正的执行时间, 而是表示代码执行时间随数据规模增长的变化趋势, 所以也叫作渐进时间复杂度, 简称时间复杂度.
分析时间复杂度的方法
- 只关注循环执行次数最多的一段代码
- 加法法则: 总复杂度等于量级最大的那段代码的复杂度
- 乘法法则: 嵌套代码的复杂度等于嵌套内外代码复杂度的乘积
几种常见时间复杂度实例分析
非多项式量级 (NP, Non-Deterministic Polynomial, 非确定多项式) (递增):
- 指数阶
O(2)
- 阶乘阶
O(n!)
多项式时间复杂度 (递增):
- 常量阶
O(1)
- 对数阶
O(logn)
- 线性阶
O(n)
- 线性对数阶
O(nlogn)
- 平方阶
O(n)
立方阶O(n)
k 次方阶O(n)
O(1)
不是指只运行一行代码, 而是代码执行时间不随 n 的增大而增长.
int i = 8;
int j = 6;
int sum = i + j;
O(logn)
, O(nlogn)
这段代码的复杂度是第3行的执行次数, 而该行的执行次数为 x=logn
, 所以复杂度为 O(logn)
i=1;
while (i <= n) {
i = i * 2;
}
同理, 以下例子的复杂度为 O(logn)
i=1;
while (i <= n) {
i = i * 3;
}
O(nlogn)
其实是 n
次 logn
O(m+n)
, O(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;
}
空间复杂度分析
渐进空间复杂度.
空间复杂度为 O(n)
:
void print(int n) {
int i = 0;
int[] a = new int[n];
for (i; i <n; ++i) {
a[i] = i * i;
}
for (i = n-1; i >= 0; --i) {
print out a[i]
}
}
各复杂度图示
最好, 最坏情况时间复杂度
复杂度为 O(n)
:
// n表示数组array的长度
int find(int[] array, int n, int x) {
int i = 0;
int pos = -1;
for (; i < n; ++i) {
if (array[i] == x) pos = i; // 注意这里并没有直接返回
}
return pos;
}
优化后的时间复杂度没那么简单:
- 最好
- 最坏
// n表示数组array的长度
int find(int[] array, int n, int x) {
int i = 0;
int pos = -1;
for (; i < n; ++i) {
if (array[i] == x) {
pos = i;
break; // 找到后就退出
}
}
return pos;
}
平均情况时间复杂度
上面例子中, 有 n+1
种情况: 在数组中和不在数组中, 计算它的平均复杂度就是将每种情况相加, 再除以 n+1
:
这个式子可以看成复杂度为 O(n)
. 但是这个推理过程没有考虑出现概率. 如果目标在或不在数组中的概率各为 1/2
, 在数组中的位置出现的概率为 1/2n
, 那么平均出现的次数相当于计算加权平均值, 也叫作期望值:
复杂度仍为 O(n)
.
均摊时间复杂度
使用摊还分析.
// array表示一个长度为n的数组
// 代码中的array.length就等于n
int[] array = new int[n];
int count = 0;
void insert(int val) {
if (count == array.length) {
int sum = 0;
for (int i = 0; i < array.length; ++i) {
sum = sum + array[i];
}
array[0] = sum;
count = 1;
}
array[count] = val;
++count;
}
- 最好时的复杂度为
O(1)
- 最坏时的复杂度为
O(n)
- 平均时间复杂度为
O(1)
上面例子中, 一共有 n+1
种情况: 数组不满和数组满, 它们的出现概率是相等的 1/(n+1)
. 复杂度计算就是计算其加权平均值:
前面有 n
个 1*1/(n+1)
, 后跟一个统计 sum
的 n
次遍历. 从规律的角度来看, 每有一次 O(n)
的插入 (求和), 后面都会跟着 O(1)
的插入操作, 然后均摊.