目录
- 算法基础
- 算法复杂性
- 递归与分治
- 回溯法与分支限界法
- 贪心算法
- 动态规划法
- NP问题
- 概率算法
- 现代优化算法
- 计算几何
0. 时间复杂度
时间复杂度其实还分为 平均时间复杂度、最好时间复杂度 和 最坏时间复杂度。对于一个算法来说,往往有很多特殊情况,一般而言,我们所说的时间复杂度都指最坏时间复杂度,因为在最坏的情况下,我们才能够评估一个算法的性能最差会到什么地步,这样我们才能更好地选择相应的算法去解决问题。
大O表示法
目前通用的时间复杂度的表示法是“大O表示法”,但其实同时还存在其他的符号。
- O: 表示的是最坏时间复杂度,即小于等于
- Ω: 表示的是最好时间复杂度,即大于等于,不常用,因为没有什么参考意义,过于乐观。
- θ: 表示确界,即明确等于
常见时间复杂度比较
- 常数阶O(1)
- 线性阶O(n)
- 平方阶O(n²)
- 对数阶O(logn)
- 线性对数阶O(nlogn)
注意:O(nlogn)、O(logn) 内部的内容在数学里是错误的,一般应该是 log2n 等,但是这里的系数并不在我们的考虑范围之内,所以我们一般在计算复杂度时直接将其表示为 O(nlogn) 和 O(logn)。
猴子排序等时间复杂度为O(n!),时间复杂度最高。
这里有一张图,可以直观看出各种时间复杂度的好坏:
时间复杂度最好的算法是O(1),即有限次数内得到结果。当然,一个算法能不能达到 O(1) 的时间复杂度,要看具体情况,我们当然希望程序的性能能够达到最优,所以算法的时间复杂度能够低于 O(n2) 一般来说已经很不错了。不要忘了,算法的性能除考虑时间复杂度外还要考虑空间复杂度,在大多数情况下往往需要在时间复杂度和空间复杂度之间进行权衡。
我们在上面提到的情况都只有一个规模参数,有时规模参数也可能有两个。比如两层嵌套循环的规模不一样,我们假设分别为 m 和 n,这时我们一般会把时间复杂度写为 O(m×n),但是我们自己需要明确,如果 m 和 n 非常相近,则这个时间复杂度趋于 O(n2);如果 m 通常比较小(也就是说我们能够明白 m 的范围是多少),则这个时间复杂度趋于 O(n)。在这两种情况下,虽然时间复杂度都是 O(m×n),但是真实的时间复杂度可能相差很大。
1. 分治法
概念:
将一个难以直接解决的大问题,分割成一些规模较小的相同问题,以便各个击破,分而治之。
思想策略:
对于一个规模为n的问题,若该问题可以容易地解决(比如说规模n较小)则直接解决,否则将其分解为k个规模较小的子问题,这些子问题互相独立且与原问题形式相同,递归地解这些子问题,然后将各子问题的解合并得到原问题的解。
特征:
- 该问题的规模缩小到一定的程度就可以容易地解决 。
- 该问题可以分解为若干个规模较小的相同问题,即该问题具有最优子结构性质。
- 利用该问题分解出的子问题的解可以合并为该问题的解。
- 该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子问题。
第一条特征是绝大多数问题都可以满足的,因为问题的计算复杂性一般是随着问题规模的增加而增加;
第二条特征是应用分治法的前提它也是大多数问题可以满足的,此特征反映了递归思想的应用;、
第三条特征是关键,能否利用分治法完全取决于问题是否具有第三条特征,如果具备了第一条和第二条特征,而不具备第三条特征,则可以考虑用贪心法或动态规划法。
第四条特征涉及到分治法的效率,如果各子问题是不独立的则分治法要做许多不必要的工作,重复地解公共的子问题,此时虽然可用分治法,但一般用动态规划法较好。
基本步骤:
1 分解:将原问题分解为若干个规模较小,相互独立,与原问题形式相同的子问题;
2 解决:若子问题规模较小而容易被解决则直接解,否则递归地解各个子问题
3 合并:将各个子问题的解合并为原问题的解。
适用分治法求解的经典问题:
1)二分搜索
2)大整数乘法
3)Strassen矩阵乘法
4)棋盘覆盖
5)合并排序
6)快速排序
7)线性时间选择
8)最接近点对问题
9)循环赛日程表
10)汉诺塔
2. 回溯法
概念:
回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。
回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。
许多复杂的,规模较大的问题都可以使用回溯法,有“通用解题方法”的美称。
思想策略:
在包含问题的所有解的解空间树中,按照 深度优先搜索 的策略,从根结点出发深度探索解空间树。当探索到某一结点时,要先判断该结点是否包含问题的解,如果包含,就从该结点出发继续探索下去,如果该结点不包含问题的解,则逐层向其祖先结点回溯。(其实回溯法就是 对隐式图的深度优先搜索 算法)。
若用回溯法求问题的所有解时,要回溯到根,且根结点的所有可行的子树都要已被搜索遍才结束。
特征:
(1) 针对所给问题,确定问题的解空间:首先应明确定义问题的解空间,问题的解空间应至少包含问题的一个(最优)解。
(2) 确定结点的扩展搜索规则
(3) 以深度优先方式搜索解空间,并在搜索过程中用剪枝函数避免无效搜索。
适用回溯法求解的经典问题:
八皇后问题,
图的着色问题,
装载问题,
批处理作业调度问题,
再再论背包问题,
最大团问题,
连续邮资问题,
符号三角形问题,
3. 分支限界法
概述:
类似于回溯法,也是一种在问题的解空间树T上搜索问题解的算法。但在一般情况下,分支限界法与回溯法的求解目标不同。回溯法的求解目标是找出T中满足约束条件的所有解,而分支限界法的求解目标则是找出满足约束条件的一个解,或是在满足约束条件的解中找出使某一目标函数值达到极大或极小的解,即在某种意义下的最优解。
策略:
在扩展结点处,先生成其所有的儿子结点(分支),然后再从当前的活结点表中选择下一个扩展对点。为了有效地选择下一扩展结点,以加速搜索的进程,在每一活结点处,计算一个函数值(限界),并根据这些已计算出的函数值,从当前活结点表中选择一个最有利的结点作为扩展结点,使搜索朝着解空间树上有最优解的分支推进,以便尽快地找出一个最优解。
与回溯法的区别:
回溯法:【方式不同】深度优先搜索 堆栈活结点的所有可行子结点被遍历后才被从栈中弹出找出满足约束条件的所有解 【目标不同】。
分支限界法:【方式不同】广度优先或最小消耗优先搜索 队列、优先队列每个结点只有一次成为活结点的机会找出满足约束条件的一个解或特定意义下的最优解 【目标不同】。
4. 贪心法
概念:
在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的仅是在某种意义上的局部最优解。
思想策略:
贪心算法没有固定的算法框架,算法设计的关键是贪心策略的选择。必须注意的是,贪心算法不是对所有问题都能得到整体最优解,选择的贪心策略必须具备无后效性,即某个状态以后的过程不会影响以前的状态,只与当前状态有关。所以对所采用的贪心策略一定要仔细分析其是否满足无后效性。
基本步骤:
1.建立数学模型来描述问题。
2.把求解的问题分成若干个子问题。
3.对每一子问题求解,得到子问题的局部最优解。
4.把子问题的解局部最优解合成原来解问题的一个解。
适用贪心法求解的经典问题:
活动选择问题,
钱币找零问题,
再论背包问题,
小船过河问题,
区间覆盖问题,
销售比赛,
Huffman编码,
Dijkstra算法(求解最短路径),
最小生成树算法
5. 动态规划
概念:
每次决策依赖于当前状态,又随即引起状态的转移。一个决策序列就是在变化的状态中产生出来的,所以,这种多阶段最优化决策解决问题的过程就称为动态规划。
思想策略:
将待求解的问题分解为若干个子问题(阶段),按顺序求解子阶段,前一子问题的解,为后一子问题的求解提供了有用的信息。在求解任一子问题时,列出各种可能的局部解,通过决策保留那些有可能达到最优的局部解,丢弃其他局部解。依次解决各子问题,最后一个子问题就是初始问题的解。
特征:
能采用动态规划求解的问题的一般要具有3个性质:
(1) 最优化原理:如果问题的最优解所包含的子问题的解也是最优的,就称该问题具有最优子结构,即满足最优化原理。
(2) 无后效性:即某阶段状态一旦确定,就不受这个状态以后决策的影响。也就是说,某状态以后的过程不会影响以前的状态,只与当前状态有关。
(3)有重叠子问题:即子问题之间是不独立的,一个子问题在下一阶段决策中可能被多次使用到。(该性质并不是动态规划适用的必要条件,但是如果没有这条性质,动态规划算法同其他算法相比就不具备优势)
基本步骤:
(1) 分析最优解的性质,并刻画其结构特征。
(2) 递归的定义最优解。
(3) 以自底向上或自顶向下的记忆化方式(备忘录法)计算出最优值
(4) 根据计算最优值时得到的信息,构造问题的最优解
适用动态规划求解的经典问题:
矩阵连乘,
走金字塔
最长公共子序列(LCS) ,
最长递增子序列(LIS) ,
凸多边形最优三角剖分 ,
背包问题 ,
双调欧几里得旅行商问题
【参考】
作者:Kevins life
原文:https://blog.csdn.net/ght886/article/details/80289142
作者:Katou_Megumi
链接:https://www.jianshu.com/p/399a92e8b389