算法评价标准
认识时间复杂度:
常数时间的操作:一个操作如果和数据量没有关系,每次都是固定时间内完成的操作,叫做常数操作。
时间复杂度为一个算法流程中,常数操作数量的指标。常用O(读作big O)来表示。具体的说,在常数操作数量的表达式中,只要高阶项,不要低阶项,也不要高阶项的系数,剩下部分如果记作f(N),那么时间复杂度为O(f(N))。
比较两个算法好坏,先拼指标,再拼低阶项和系数。
评价一个算法流程的好坏,先看时间复杂度的指标,然后再分析不同数据样本的下的实际运行时间,也就是常数项的时间。
一个简单例子理解:
第一个算法O(M*N);
第二个算法O(M*logN),所有不带底数的log,默认2为底;
第三个算法O(M*logM)+O(N+M),分析如下:
外排:先B排序,再两个指针依次进行比较,当b <= a,则b指针移动,否则 a指针移动。复杂度分为两步进行分析:
通过常数时间操作数量进行分析。ps:如果有两个样本量,此时复杂度不能化简,除非明确知道谁大谁小(样本量确定了),则可以化简。
那么在什么情况下2算法低,什么情况下3算法低?
通过具体样本量分析:如果B大A小,则2算法好,如果A大B小,则3算法好。
排序算法:
冒泡排序
end每次–,依次比较,交换,每次循环遍历得到最大的数,沉到最底。
可看for循环估计时间复杂度: O(N2)
选择排序
时间复杂度:O(N2)
大循环:
minIndex记录每次循环的最小值(三目运算),然后再跟第一个数交换。
冒泡排序和选择排序工程上基本用不到了,只是用于教程。
插入排序
类似扑克牌,先抓牌然后整牌,看看新抓的牌能滑倒哪个位置。
该排序方法就涉及到最好情况,平均情况,最坏情况,实际时间和数据状况有关系。最好情况O(N),最坏情况O(N2)。
一个算法流程怎样评价?当数据状况不同产生的效果不同时,一律按照最坏情况估计
于是插入排序时间复杂度O(N2)。
对数器
好处:自己验证,用于调试。
- 没找到线上测试online judge,则可以使用对数器。
- 大数据样本出错时,快速找到出错地方。
- 贪心策略使用,直接验证是否正确
代码实现
-
有一个你想要测试的方法,例如上文所示算法。
-
准备一个随机数组(样本)生成器:
-
再准备一个绝对正确的方法(不用管时间复杂度,好写,好实现,绝对正确即可):
-
大样本测试(包括实现比对方法)
测试次数500000;
生成数组,分别用好的/和自己写的算法进行排序判断两个数组相等算法,长度一样,数一样。
拷贝数组算法
-
如果有一个样本由于数组相对较短,所以几乎穷尽了所有情况,如果都正确,则返回正确,一个出错则返回打印看看哪个方法出错。
所需的正确的方法写不对怎么办:则先用小样本测试,手动改正正确的方法,因为自己知道正确答案,则我们可以手动去修改,不断迭代找到完全正确的方法。
额外空间复杂度:
算法流程走完,需要额外申请开辟的空间,和原来目标存储的数据结构没关系,类比时间复杂度。
递归行为时间复杂度:
什么是递归?
即:自己调用自己
例如:数组中找最大值
递归函数就是系统在帮你压栈,在函数调用子过程之前,会自动保存执行当前函数的行数、局部变量、指针等等信息,相当于保存函数执行的现场,回到该过程时从栈中弹出还原现场,直到执行完毕。
因此任何递归行为都可以改成非递归,每次通过压栈来分析即可。
通过自己压栈就是迭代了。
递归行为时间复杂度
如何分析递归的时间复杂度呢?
如下图:
总的时间复杂度(T):子过程时间之和(aT(n/b)),加上除去子过程调用时间,剩下过程的额外花费(O(nd))(看一步即可)。
凡是符合上述公式的过程,都可用master公式可以直接套用估算时间复杂度(见下图)。
套用公式可得上述算法复杂度为O(N):
如果满足下图,则时间复杂度为O(N*logN):
如果满足下图,则时间复杂度为O(N2):
而下述递归时间复杂度公式,不可套用master公式。
划分子问题的规模必须一样!
归并排序时间复杂度
归并排序
左右侧部分分别排好序,准备一个辅助数组,用外排方式,用两个指针指向左右侧部分的数组,依次比较填入,最后拷贝回原数组。
时间复杂度计算:
根据master公式,可得时间复杂度O(N*logN),相较于O(N2)好太多,额外空间复杂度O(N)。代码实现:
不返回help
小和问题和逆序对问题
第一种解法,直接2pass遍历,但是复杂度高O(N2),可用作对数器的绝对正确的方法。
第二种方法,采用归并排序,思路为寻找该数有多少后面的数比他大,就产生多少个该数做累加,通过归并分批榨取。如下图:
代码如下所示:
跟上述归并排序代码基本一致,多了一行代码,利用res变量存储当前归并函数下榨取出来的小和,依次归并,则不会漏算,重复计算。
上述写法可以防止溢出,安全。且位运算比算数运算快很多.
逆序对问题
同样的思路,归并排序中求后面的部分有多少个数比该数小。
由于使用归并:一个组内比较次数不会浪费,且跨组比较,所以快。而遍历,会浪费比较次数。