# 算法复杂度 |![algo3](http://39.99.183.126:8888/file/2/algo3.jpeg)| |:----:| || --- ## 一、算法的目标 算法是对问题的解决方案,但一个问题会有很多种算法,通常一个好的算法需要具备以下目标: - 正确性:对合法输入、非法输入、边界输入都能正确处理,输出合理的结果。 - 可读性:算法应该描述清晰,方便阅读、理解和交流。 - 健壮性:算法应运行一致,对于相同的输入始终输出相同的结果。 - 高效性:算法应占用最少的cpu和内存得到满足的结果,这通过时间复杂度和空间复杂度进行判定。 --- ## 二、算法时间复杂度 算法复杂度用来衡量算法的高效性,简单的说就是: 算法运行的有多快(时间效率) 内存占用的有多少(空间效率) 然而,运行时间和语言、机器、机器的状态、数据量的大小都有关系,不好横向比较,为此通常使用一个时间复杂度(相对度量)的概念来衡量算法有多快。 --- 我们假设其它状态不变,仅当问题规模(数据大小)增长时,指令执行的次数也在增长,那么指令执行次数相对于问题规模来说,会构成一个函数 $T(n)$。 例如-对于以下数组求和的算法 $1+2+3+...+n:$ ```c++ int sum = 0; //指令数为1 for(int i=0; i
=n0$,都有 $f(n) <= c T(n)$,则称 $f(n)$为T(n)的大O函数,写成:f(n) = O(T(n))。 --- ## 四、常见大O函数 |函数|名称|例子| |---|---|---| |O(1)|常数阶|交换算法| |O(logn)|对数阶|二分查找算法| |O(n)|线性阶|求和算法| |O($nlogn$)|线性对数阶|快速排序算法| |O($n^2$)|平方阶|冒泡排序算法| |O($n^c$)|多项式阶(c>1)|多重循环的算法| |O($c^n$)|指数阶|汉诺塔问题| |O(n!)|阶乘阶|旅行商问题| --- ## 五、扩展理解-复杂度计算与例子 --- ### 1、计算方法 对算法(或代码)的指令次数进行计算组成 $T(n)$,只保留最高阶项,然后去掉最高阶项前面的常数。 例如以下代码的$T(n) = 3$, 时间复杂度为O(1): ```c++ int a = 20; int b = a * 3 + 4; cout << b; ``` --- ### 2、O(n)的例子 输出数组元素: ```c++ for(int i=0; i
10^7$|O($\log_2n$)| |$10^7$|O(n)| |$10^5 \sim 5*10^5$|O($n\log_2n$)| |$1000 \sim 5000$|O($n^2$)| |200 ~ 500|O($n^3$)| |20 ~ 24|O($2^n$)| |12|O($n!$)| --- 在看到数据范围的时候,就知道大概需要选择什么算法了。 选择一个算法实现后,大概知道能通过多少测试点。 例如:一个排序的试题,30%数据<1000,50%数据<3000,100%数据<300,000,则: 如果想$\color{green} AC$(100分),需要用O($n\log_2n$)的算法,例如快速排序。 如果只会冒泡排序(O($n^2$)),能拿到50分。 --- 总结下算法复杂度和算法的关系: |数据规模|时间复杂度|相关算法| |-----|-----|-----| |$n≤30$| 指数级别| dfs+剪枝,状态压缩dp| |$n≤100$| $O(n^3)$| floyd,dp,高斯消元| |$n≤1000$| $O(n^2)$$O(n^2logn)$ | dp,二分,朴素版Dijkstra、朴素版Prim、Bellman-Ford| |$n≤10^4$|$ O(n∗\sqrt{n})$| 块状链表、分块、莫队| --- |数据规模|时间复杂度|相关算法| |-----|-----|-----| |$n≤10^5$|$ O(nlogn)$ | 各种sort,线段树、树状数组、set/map、heap、拓扑排序、dijkstra+heap、prim+heap、Kruskal、spfa、求凸包、求半平面交、二分、CDQ分治、整体二分、后缀数组、树链剖分、动态树| |$n≤10^6$|$ O(n)$ |以及常数较小的 $O(nlogn)$ 算法 => 单调队列、 hash、双指针扫描、并查集,kmp、AC自动机,常数比较小的 $O(nlogn)$ 的做法:sort、树状数组、heap、dijkstra、spfa| --- |数据规模|时间复杂度|相关算法| |-----|-----|-----| |$n≤10^7$|$ O(n)$| 双指针扫描、kmp、AC自动机、线性筛素数| |$n≤10^9$|$ O(\sqrt{n})$| 判断质数| |$n≤10^{18}$|$ O(logn)$| 最大公约数,快速幂,数位DP| |$n≤10^{1000}$|$ O((logn)^2)$ | 高精度加减乘除| |$n≤10^{100000}$|$ O(logk×loglogk)$ | k表示位数表示位数,高精度加减FFT/NTT| --- ## 2、竞赛策略(做题策略) --- ### 2.1 解题 仔细阅读试题,通过小数据模拟理解试题,大致明白试题的输入->输出之间的逻辑。 要求-在模拟、理解试题之后: 能提炼出试题对应的问题(去掉试题里的业务描述) 能自己生成更多的输入和输出样例(包括边界和普通情况)。 --- ### 2.2 朴素算法 实现一种朴素(暴力)算法,通过所有的样例,先不考虑性能。 要求:通过所有的测试样例,包括自己设计的样例,确保结果无误。 --- ### 2.3 优化和复杂度理解 根据数据的范围,分析出复杂度分级,并对朴素算法进行优化,大致划分出不同优化方案对应的复杂度分级,以及得分范围。 要求: 根据自己的能力积累,尽可能设计出更大得分范围的优化算法。 用所有测试样例对优化算法进行测试,确保结果无误。 --- ### 2.4 分支和对拍 对拍是指用脚本来调用朴素算法和优化算法两个程序,对所有输入数据做运算,比较其输出是否一致。 对拍被用来确保优化程序不会出现结果错误的情况,因为优化程序往往用到比较复杂的思路,难免编码出错。 不会对拍的情况,也可以通过对不同的数据范围实现分支控制,比如小数据调用朴素算法,大数据调用对应的优化算法,各施其责。 --- ### 2.5 测试数据 为确保代码正确及性能测试通过,需要编写程序生成符合数据范围的各级测试数据,以便对优化算法进行性能测试,以及对拍所用。 --- |例题| |---| |[CSPJ2022SDT1 植树节(planting)](http://39.99.183.126:8888/p/CSPJ2022SDT1)|