学习地址Hello算法:https://www.hello-algo.com/
文章目录
一、复杂度分析
1.1 时间复杂度
时间复杂度分析统计的不是算法运行时间,而是算法运行时间随着数据量变大时的增长趋势。
1.1.1 时间复杂度分析有哪些特点?
- 时间复杂度能够有效评估算法效率。输入的数据越大,时间复杂度更优的算法,效率更高。
- 时间复杂度的推算方法更简便。可以简单地将所有计算操作的执行时间视为相同的“单位时间”,从而将“计算操作运行时间统计”简化为“计算操作数量统计”
- 时间复杂度也存在一定的局限性。时间复杂度不能等同于实际运行时间差别很大,在实际情况下,我们很难仅凭时间复杂度判断算法效率的高低。尽管存在上述问题,复杂度分析仍然是评判算法效率最有效且常用的方法。
1.1.2 计算方法:
-
第一步:统计操作数量——操作数量 T(n) 中的各种系数、常数项都可以忽略,循环嵌套时使用乘法。
-
第二步:判断渐近上界——时间复杂度由 T(n) 中最高阶的项来决定。这是因为在 n 趋于无穷大时,最高阶的项将发挥主导作用,其他项的影响都可以忽略。
函数渐近上界:
1.1.3 常见类型的时间复杂度:
- 即使操作数量
size
可能很大,操作数量与输入数据大小 n 无关,时间复杂度仍为 常数阶O(1) - 操作数量相对于输入数据大小 n 以线性级别增长 线性阶 O(n);比如遍历数组和遍历链表等操作
- O(n2) 平方阶通常出现在嵌套循环中。比如冒泡
- 生物学的“细胞分裂”是指数阶增长的典型例子,时间复杂度为 指数阶O(2n) 。对于数据规模较大的问题,指数阶是不可接受的,通常需要使用动态规划或贪心算法等来解决。
- 与指数阶相反,对数阶反映了“每轮缩减到一半”的情况。时间复杂度为 O(log2n) ,简记为 O(logn)
1.1.4 最差、最佳、平均时间复杂度
算法的时间效率往往不是固定的,而是与输入数据的分布有关。同样的一个算法会根据输入的数据不同,而体现不同的运算效率。
- 输入最适合的数据时,达到最佳时间复杂度 Ω(1) 。
- 输入的数据最差时,达到最差时间复杂度 O(n) 。
值得说明的是,我们在实际中很少使用最佳时间复杂度,因为通常只有在很小概率下才能达到,可能会带来一定的误导性。而最差时间复杂度更为实用,因为它给出了一个效率安全值,让我们可以放心地使用算法。
相比之下,平均时间复杂度可以体现算法在随机输入数据下的运行效率,用 Θ 记号来表示。
但对于较为复杂的算法,计算平均时间复杂度往往比较困难,因为很难分析出在数据分布下的整体数学期望。在这种情况下,我们通常使用最差时间复杂度作为算法效率的评判标准。
1.2 空间复杂度
1.2.1 算法相关空间
- 输入空间:用于存储算法的输入数据。
- 暂存空间:用于存储算法在运行过程中的变量、对象、函数上下文等数据。
- 暂存数据:用于保存算法运行过程中的各种常量、变量、对象等。
- 栈帧空间:用于保存调用函数的上下文数据。系统在每次调用函数时都会在栈顶部创建一个栈帧,函数返回后,栈帧空间会被释放。
- 指令空间:用于保存编译后的程序指令,在实际统计中通常忽略不计。
- 输出空间:用于存储算法的输出数据。
/* 类 */
class Node {
int val;
Node next;
Node(int x) { val = x; }
}
/* 函数 */
int function() {
// 执行某些操作...
return 0;
}
int algorithm(int n) { // 输入数据
final int a = 0; // 暂存数据(常量)
int b = 0; // 暂存数据(变量)
Node node = new Node(0); // 暂存数据(对象)
int c = function(); // 栈帧空间(调用函数)
return a + b + c; // 输出数据
}
一般情况下,空间复杂度的统计范围是“暂存空间”加上“输出空间”。所以在分析一段程序的空间复杂度时,我们通常统计暂存数据、栈帧空间和输出数据三部分
1.2.2 推算方法
空间复杂度的推算方法与时间复杂度大致相同,只需将统计对象从“操作数量”转为“使用空间大小”。
而与时间复杂度不同的是,我们通常只关注最差空间复杂度。“最差”有两层含义。
- 以最差输入数据为准
- 以算法运行中的峰值内存为准
在递归函数中,需要注意统计栈帧空间。
- 循环
loop()
的时间复杂度为 O(n) ,空间复杂度 O(1) - 递归函数
recur()
虽然时间复杂度为 O(n) ,但在运行过程中会同时存在 n 个未返回的 结果recur()
,从而占用 O(n) 的栈帧空间。
1.2.3 常见的类型
- 常量、变量、对象占用 O(1) 空间
- 循环中的变量和函数占用 O(1) 空间
- 递归中的变量和函数占用 O(n) 空间
- 长度为 n 的数组/列表/哈希表占用 O(n) 空间
- 矩阵、二维列表占用 O(n2) 空间
- 层数为 n 的“满二叉树”的节点数量为 2n−1 ,占用 O(2n) 空间:
- 常用于分治算法,占用O(logn) 的栈帧空间,