算法概述章节目录
常见复杂度比较排序
c < log2N < n < n * Log2N < n^2 < n^3 < 2^n < 3^n < n!
当n>=1时,(logn)的三次方 < n
算法和编程
计算复杂度
…上面两个不知道怎么分的,先按照自己的想法分标题:
三个例子
Ex.1最短路径
- 最直接解法:穷举
- 好一些的解法:Djkstra
Ex.2整数乘法
- 复杂度
整数乘法复杂度就是两个相乘的数的位数之积(n^2) - 优化
简单来说:将大数分解成小数的和,如 10030 化成 1×10000 + 3×10
具体地说:将两个乘数都各自取一半的长度,再乘以10的对应次方进行分解
比如:2345 = 23×100 + 45
公式:x×y
= (10的n次×a + b) × (c × 10的n次 + d)
= ac × 10的2n次 + (ad + bc)×10的n次 + bd
ad + bc
= (a+b) × (c+d) - ac - bd(这里化简容易错化成-ab-cd 或者 -ad-bc,都会导致三次乘法没法出现,进而计算次数没法减少)
故
x×y
= ac × 10的2n次 + (
(a+b) × (c+d) - ac - bd )×10的n次 + bd
举例:
x = 2019, y = 5533
x×y = (100×20 + 19) * (100×55 + 33)
= 20 × 55 × 10000 + (33 × 20 + 19 × 55) × 100 + 19 × 33
= 20 × 55 × 10000 + 19 × 33 + 100 × [
(33 + 55) × (20 + 19) - 20 × 55 - 19 × 33 ]
= 20 × 55 × 10000 + 19 ×33 - 20 × 55 × 100 - 19 × 33 × 100 + (33 + 55) × (20 + 19)
Ex.3插入排序
时间耗费与输入顺序、数据量都有关。参考抓扑克牌后在手中整理顺序
空间复杂度为O(1)、时间复杂度为O(N) ~ O(N方)
一般是找复杂度的上限
如果是已经排好序的,且目标顺序不是已有顺序的逆序,则每次插入都只需要比较1次,故为O(N),排序更容易
单次插入最好比较1次最坏第k个元素比较k-1次
RAM Model
计算复杂度的模型就是将单次运算或者操作当作是单位时间,然后通过计算次数来代替具体的时间。
具体的时间是无效的。
特征:
串行、算术或者逻辑操作当作一步、每步耗时恒定、循环和分支不是基本操作
具体每步的耗时没有意义,用次数代替时间
空间复杂度和时间复杂度都是计算量n的函数
四种复杂度
一个好的算法需要有2个特征:正确、效率
算法应该是与语言无关、与机器无关,复杂度是理想状态的值
- 最好情况的
- 最坏情况的
- 平均情况的
- 最优情况的:不一定是最快的或者最省空间的,但是一定是在解决问题上最适合的
常见复杂度比较排序
c < log2N < n < n * Log2N < n^2 < n^3 < 2^n < 3^n < n!
当n>=1时,(logn)的三次方 < n
- 在位算法(in-place):额外需要的空间数恒定,与输入的数据量无关
- 矩阵运算的复杂度,理论最好情况为n的平方 (但实际还没达到) ,正常情况下是在2次方到3次方之间,已知最好为2.376次方
- 归并排序时间复杂度永远是nlogn,也是稳定排序
多项式与指数复杂度
如果算法能在多项式复杂度内完成,则也是可以接受的。
只有同一个问题的不同算法才有可比性,才能比较谁更优。
比较公式复杂度大小需要放缩的时候,整数可以考虑放缩为等值的指数形式(可能是对数做指数,可能是数字做指数)
常见复杂度比较排序
c < log2N < n < n * Log2N < n^2 < n^3 < 2^n < 3^n < n!
当n>=1时,(logn)的三次方 < n
Big-O估算
存在两个常数c和m,当n>=m时,所有的n都满足f(n)<c×g(n),此时写作f(n) ∈ O(g(n)),称g(n)为f(n)的上限
证明复杂度上限,用放缩法
常见复杂度比较排序
c < log2N < n < n * Log2N < n^2 < n^3 < 2^n < 3^n < n!
当n>=1时,(logn)的三次方 < n
证明的时候要写明是在什么情况下两个复杂度相等
底数不同但对数相同的时候,底数越大值越小,但复杂度分析往往都不写底数
f(n) ∈ Ωg(n)) 代表g(n)比f(n)增长慢,即g(n)是f(n)的下界,即能找到一个n0当n>n0时,f(n) > c × g(n) 恒成立
f(n) ∈ θ(g(n)) 代表f(n)与g(n)是同阶的,即g(n)同时是f(n)的上界和下届,即 能找到一个n0、c1、c2,使得n > n0时,g(n) × c1 < f(n) < g(n)×c2
两个复杂度为大O的表达式相加,则整体的复杂度为二者中较大的复杂度
两个有下界的表达式相加,整体的下界为二者较大的下界
两个表达式的复杂度相乘,代表两个函数嵌套地运行,即乘积整体的上界、下界、同阶都是两个表达式相乘
常见复杂度比较排序
c < log2N < n < n * Log2N < n^2 < n^3 < 2^n < 3^n < n!
当n>=1时,(logn)的三次方 < n
比较复杂度大小:可以相除再求极限
递归的复杂度
求递归复杂度的三个方法:
①递归树:如果问的是具体是多少,则还需要证明
②替代法:先猜测一个复杂度的结果A,再证明猜测的结果成立
③主方法:适用于T(b) = a × T(n/b) + n的d方的形式。把大问题分成子问题或者将小问题合并成最后的结果所需的时间为n的d方,然后根据结论得到复杂度结果。
ex: 归并排序,先把n个数对半分,然后对两个部分各自再对半分,对四个部分分别再对半分,一直分到都只有一个数,然后两两比较并排序,然后2+2合并并排序,然后4+4合并并排序,然后8+8合并并排序,直到最后合成一个整体。
①递归树:复杂度为cnlogn + Θ(n),但由于未经验证,所以通常只用来验证复杂度。
求解:T(n) = T(n/4) + T(n/2) + n方
(问题就在这个树不是平衡二叉树,最左侧会优先到达1个单位)
但是n方后面括号内的等比数列并没有证明,只是人为地总结得到的,所以 递归树通常用于验证,而不是证明
②针对上述“只能用于验证”的问题,可用 替代法(数学归纳法) 来解决:
就是通过递归树猜测一个可能的结果,然后要推理演算得到想要的结果能减去一个正数,然后小于等于这个想要的结果。
③另外一个方法,也叫做主方法:
对a、b、c三个参数的解释:
①a:
如果已经给了递归式,则a就等于递归式等号右侧的较小规模的T()的系数;
如果未给递归式,则需要根据具体算法的流程判断每次递归将当前的问题规模分成了几个更小的子问题,有几个子问题那么a就为多少。
②b:
如果给了递归式,则较小规模的T()括号中的分母是多少,则b就为多少;
如果没有给递归式,则假设划分前、后的问题分别为M和N,M和N的规模分别为m和n(已知m一定是大于n的),则b = m / n。
③d:
如果给了递归式且递归式末尾的这一项是 以n为底、以常数数字为指数、系数为1 的,则d就是指数这个常数数字;
如果没有给递归式,就根据递归算法的实际流程,得到对数据进行合并所需花费的时间的复杂度y,通过y = n^d,即可解出d的值。
如何记忆三种情况的结果:
首先记住:之所以有这三种分类,是因为在推理过程中出现了 a / (b的d次方),需判断随着递归调用层数的增加,这个商如何变
①二者相等:商为1,即随递归调用层数增加,每一层的时间复杂度都相同,所以整体的复杂度等于每一层的复杂度之和,即层数×每层的复杂度。
②a较小:商大于零且小于1,即随递归调用层数增加,每一层的时间复杂度呈递减趋势,所以第一层的时间复杂度最大,故整体的时间复杂度变化趋势以第一层的复杂度为准。
③a较大:商大于1,即随递归调用层数增加,每一层的时间复杂度呈递增趋势,所以最后一层、也是递归树中的最下一层、也是最后一次递归调用的时间复杂度最大,故整体的时间复杂度以最后一次调用的时间复杂度为准,即 叶子节点个数 × 每个叶子节点的时间复杂度。
举例:归并排序每次划分为2个规模较小的子问题,所以a为2;划分后每个子问题的规模为原问题的1/2,故b=2; 又因为归并排序的合并过程时间复杂度为O(n),所以n = (n的d次方),即d=1。再判断a和(b的d次方)的大小,二者相等,所以属于case1(即每一层的时间复杂度都相同),即复杂度为n×logn。
归并排序的时间复杂度,无论是最优最差还是平均,都是nlogn。
二分查找的时间复杂度,首先是分成两半,所以规模减小为原来的1/2,但是另一半就已经丢弃了,所以a=1,而不是2,最后需要进行一次比较大小,时间为O(1),所以d=0,即a=1,b=2,d=0,即时间复杂度为nlogn