丛结果上看,我们希望我们的程序,占用低,速度快。怎么办呢,上更强的cpu,更大的内存,当然可以实现,但我们希望在同等规模的计算下,同时在相同的硬件配置下,占用低,速度快,就必须优化算法。复杂度分析就是用来衡量算法的好坏的。
对于空间复杂度,由于现在存储技术的发展,很容易有大内存,故主要优化时间复杂度。
一. 时间复杂度
时间复杂度描述算法中基本操作数量随输入规模 n 的增长趋势。
for i in range(n):
for j in range(i):
a += 1
我们可以看到 a+=1 运行了1一直加到n次,由等差数列公式n(n-1)/2,n小了,时间复杂度没意义,大了才有意义,取极限,就是O(n^2)
| 复杂度 | 示例算法 | 说明 |
|---|---|---|
| O(1) | 数组随机访问、变量交换 | 常数时间操作,与输入规模无关,最快 |
| O(log n) | 二分查找、平衡二叉树查找 | 每次将问题规模减半,增长极慢 |
| O(n) | 顺序遍历、线性查找 | 操作次数与输入规模成正比 |
| O(n log n) | 归并排序、快速排序、堆排序 | 高效的通用排序级别,常见于分治算法 |
| O(n²) | 冒泡排序、选择排序、插入排序 | 双重循环,适合小规模数据 |
| O(n³) | Floyd 全源最短路、三重循环矩阵运算 | 三重嵌套循环,增长速度快 |
| O(2ⁿ) | 斐波那契递归、子集枚举 | 指数爆炸,每增加一个元素都使运算量翻倍 |
| O(n!) | 全排列、旅行商问题(TSP) | 复杂度最高,几乎无法处理大规模输入 |
| O(√n) | 判断素数(试除法) | 随着 n 增大增长缓慢,常见于优化性算法 |
| O(n² log n) | 多维归并排序、高维计算几何算法 | 比平方略高,常见于嵌套分治问题 |
判断时间复杂度到底是O几,只需要有极限意识,到底是谁在发挥作用即可。最古朴的还是计算出有多少次,然后取极限。
for (int i = 0; i < n; i++)
for (int j = n; j > 0; j /= 2)
// 常数操作
O(n log n)
void f(int n) {
if (n == 0) return;
f(n - 1);
f(n - 1);
}
O(2^n)
void permute(int n) {
if (n == 0) return;
for (int i = 0; i < n; i++)
permute(n - 1);
}
O(n!)
注意1:关于时间复杂度,我们不能光看嵌套了多少循环,嵌套了两个循环就是O(N^2),还是要看具体的次数
void logLoop(size_t n)
{
for (size_t i = 1; i <= n; i *= 2)
{
// 循环体执行 O(1)
}
}
这个的时间复杂度只有log n。
注意2:你调用函数里面的时间复杂度也要算进去,不会因为你封装函数了,时间复杂度就下来了
void foo(char *dest, const char *src, int n)
{
for (int i = 0; i < n; ++i) // 循环 n 次
{
memcpy(dest, src, n); // 每次拷贝 n 个字节 → O(n)
}
}
对于时间复杂度,有最好的情况,最坏的情况,和平均情况。我们一般考虑最坏的情况,因为只有这样,才能对最坏的输入有抵抗力。假如时间复杂度既跟M相关,又跟N相关,时间复杂度为O(MN),这时也是取影响最大的那个O(M^2)或O(N^2)
二.空间复杂度
空间复杂度描述的是:算法在运行过程中,除输入数据本身外,所需的额外辅助存储空间随问题规模 n 的增长关系。
注意空间复杂度是针对额外存储空间的,不是说数有多少变量,有多少数组。
O(1):这个是有限个自变量
O(N):一维数组
O(N*N):二维数组
long long Fib(size_t N)
{
if(N < 3)
return 1;
return Fib(N-1) + Fib(N-2);
}
只创建了N个函数栈帧,所以,空间复杂度是O(N).
1237

被折叠的 条评论
为什么被折叠?



