算法概述
算法基本概念
- 算法是一系列解决问题的清晰指令,也就是说,对于符合一定规范的输入,算法能够在有限时间内获得所要求的输出
- 算法是解决问题的一种方法或过程,它是由若干条指令组成的有穷序列
算法的特征
特点 | 描述 |
---|---|
输入 | 有0个或多个外部量作为算法的输入 |
输出 | 至少一个量作为输出 |
确定性 | 组成算法的每条指令清晰,无歧义 |
有效性 | 算法中执行的任何计算步骤都是可以被分解为基本的可执行的操作步 |
有限性 | 算法中每条指令的执行次数有限,执行每条指令的时间也有限 |
算法效率分析
- 算法效率分为时间效率和空间效率
- 定义
符号 | 描述 |
---|---|
N N N | 解决问题的规模 |
I I I | 算法的输入 |
A A A | 算法本身 |
C C C | 复杂性 |
T T T | 时间复杂度 |
S S S | 空间复杂度 |
C = F ( N , I , A ) C=F(N,I,A) C=F(N,I,A)
T = T ( N , I ) T=T(N,I) T=T(N,I)
S = S ( N , I ) S=S(N,I) S=S(N,I)
- 函数的渐进的界
渐进界 | 名称 | 含义(存在正数c和 n 0 n_0 n0使得对于一切 n ≥ n 0 n\geq n_0 n≥n0) |
---|---|---|
O ( g ( n ) ) O(g(n)) O(g(n)) | 上界 | 0 ≤ f ( n ) ≤ c g ( n ) 0\leq f(n)\leq cg(n) 0≤f(n)≤cg(n) |
Ω ( g ( n ) ) \Omega(g(n)) Ω(g(n)) | 下界 | 0 ≤ c g ( n ) ≤ f ( n ) 0\leq cg(n)\leq f(n) 0≤cg(n)≤f(n) |
o ( g ( n ) ) o(g(n)) o(g(n)) | 上界 | 0 ≤ f ( n ) < c g ( n ) 0\leq f(n)< cg(n) 0≤f(n)<cg(n) |
w ( g ( n ) ) w(g(n)) w(g(n)) | 下界 | 0 ≤ c g ( n ) < f ( n ) 0\leq cg(n)< f(n) 0≤cg(n)<f(n) |
Θ ( g ( n ) ) \Theta(g(n)) Θ(g(n)) | 阶 | f ( n ) = O ( g ( n ) ) = Ω ( g ( n ) ) f(n)=O(g(n))=\Omega(g(n)) f(n)=O(g(n))=Ω(g(n)) |
1 | 常数函数 |
类型 | 名称 | 注释 |
---|---|---|
1 | 常量 | 为数极少的效率最高的算法 |
l o g n logn logn | 对数 | 算法每次循环都会消去问题规模的一个常数因子 |
n n n | 线性 | 扫描规模为n的列表算法 |
n l o g n nlogn nlogn | n − l o g n − n n-logn-n n−logn−n | 诸多分值算法,包括合并排序和快速排序平均效率属于此类型 |
n 2 n^{2} n2 | 二次 | 一般来说,包含两重嵌套循环的算法。n阶方阵的某些特定算法 |
n 3 n^3 n3 | 三次 | 一般来说,包含三层嵌套循环的算法 |
2 n 2^n 2n | 指数 | 求n个元素集合的所有自己的算法。也泛指比它增长速度更快的算法,如阶乘 |
n ! n! n! | 阶乘 | 求n个元素集合的完全排列算法 |
-
算法复杂度分析步骤
- 确定表示输入规模的参数
- 找出算法的基本操作
- 检查基本操作的执行次数是否只依赖于输入规模。这决定是否需要考虑最差、平均以及最优情况下的复杂性
- 对于非递归算法,建立算法基本操作执行次数的求和表达式;对于递归算法,建立算法基本操作执行次数的递推关系及其初始条件
- 利用求和公式和法则建立一个操作次数的闭合公式,或者求解递推公式,确定增长的阶
-
递归算法两类复杂度分析
T ( n ) = { O ( 1 ) n = 1 a T ( n − 1 ) + f ( n ) n > 1 T(n)= \begin{cases} O(1) & n=1\\ aT(n-1)+f(n)& n>1 \end{cases} T(n)={O(1)aT(n−1)+f(n)n=1n>1
T ( n ) = a n − 1 T ( 1 ) + ∑ i = 2 n a n − i f ( i ) T(n)=a^{n-1}T(1)+\sum_{i=2}^n a^{n-i}f(i) T(n)=an−1T(1)+i=2∑nan−if(i)
T ( n ) = { O ( 1 ) n = 1 a T ( n b ) + f ( n ) n > 1 T(n)= \begin{cases} O(1) & n=1\\ aT(\frac nb)+f(n)& n>1 \end{cases} T(n)={O(1)aT(bn)+f(n)n=1n>1
T ( n ) = n l o g b a T ( 1 ) + ∑ j = 0 l o g b n − 1 a j f ( n b j ) T(n)=n^{log_b a}T(1)+\sum_{j=0}^{log_b n-1}a^jf(\frac n{b^j}) T(n)=nlogbaT(1)+j=0∑logbn−1ajf(bjn)
其他
- 算法描述:自然语言、流程图、伪代码
- 算法正确性证明:归纳法,反证法
- 算法分析:正确性、效率、简单性、一般性
分治法
基本思想
将一个难以直接解决的大问题,分解为规模较小的相同子问题,直至这些子问题容易直接求解,并且可以利用这些子问题的解求出原问题的解。各个击破,分而治之。
适用特征
- 该问题的规模小到一定的程度就可以容易解决
- 该问题可以分解为若干个规模较小的相同问题
- 利用该问题分解出的子问题的解可以合并为该问题的解
- 该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子问题
基本步骤:分解、求解子问题、合并
divide-and-conquer(P)
{
if(|P|<=n_0)adhoc(P);//求解最小的子问题
divide P into smaller subinstances P_1,P_2,...,P_k;//分解
for(i=1;i<=k;i++)
y_i = divide-and-conquer(P_i);//递归求解子问题
return merge(y_1,...,y_k);//合并
}
求解方法
- 迭代法
- 递归树
动态规划
基本思想
将带求解问题分解成若干子问题,先求解子问题,再结合这些子问题得到原问题的解。同时,在求解过程中,保留已解决的子问题的解,在需要时再查找已求得的解,就可以避免大量重复计算。
适用特征
- 求解的问题是组合优化问题
- 求解过程需要多步判断,从小到大依次求解
- 子问题目标函数最优解之间存在依赖关系
基本步骤
- 分析最优解结构
- 递归的定义最优值
- 以自底向上的方式计算最优值
- 根据计算最优值时得到的信息构造最优解
要素
- 最优子结构:当问题的最优解包含其子问题的最优解时,称该问题具有最优子结构性质。
- 重叠子问题:当用递归算法自顶向下求解此问题时,每次产生的子问题并不总是新问题,有些子问题被反复计算。
- 备忘录(表格):用表格保存已解决的子问题的答案,在下次需要求解子问题时,只要简单的查看该子问题的解,而不用重新计算
贪心算法
基本思想
贪心算法总是作出在当前看来最好的选择。也就是说贪心算法并不从整体最优考虑,它所做出的选择只是在某种意义上的局部最优选择。
适用特征
- 最优子结构性质
- 贪心选择性质:所求问题的整体最优解可以通过一系列局部最优的选择,即贪心选择来达到。对于一个具体问题,要确定它是否具有贪心选择性质,必须证明每一步所做的贪心选择最终导致问题的整体最优解。
证明方法
- 对算法步数的归纳
- 对问题规模的归纳
总结
- 贪心法需要正确性证明
- 贪心法一般需要对原始数据预处理(排序)
- 程序结构一般自顶向下,一次扫描
回溯法
基本思想
从初始状态出发,搜索其所能到达的所有状态。当一条路走到尽头,再后退一步或若干步,从另外一种状态出发,继续搜索,直到所有的路径都搜索过。
相关概念
- 搜索策略:深度优先为主,也可以采用广度优先,函数优先,广度深度结合等
- 约束函数:在扩展节点处减去不满足约束条件的子树
- 界限函数:在扩展节点处剪去得不到最优解的子树
- 结点状态:白结点(尚未访问)、灰节点(正在访问以该节点为根的子树)、黑结点(以该节点为根的子树遍历完成),白结点和灰节点为活结点,黑结点为死结点
- 子集树:当所给的问题是从n个元素的集合S中找出满足某种性质的子集是,相应的解空间树称为子集树。子集树通常有 m n m^n mn个叶结点(n皇后问题)
- 排列树:当所给的问题是确定n个元素满足某种性质的排列时,相应的解空间树称为排列树。排列树通常有 n ! n! n!个叶结点(货郎担问题)
相关概念 | 描述 |
---|---|
问题解的表示 | 将问题的解表示成一个n元式 ( x 1 , x 2 , . . . , x n ) (x_1,x_2,...,x_n) (x1,x2,...,xn)的形式。如: ( x 1 , x 2 , . . . , x 4 ) (x_1,x_2,...,x_4) (x1,x2,...,x4) |
显示约束 | 对分量 x i x_i xi的取值限定。如: x i ∈ { 1 , 2 , 3 , 4 } x_i \in \{1,2,3,4\} xi∈{1,2,3,4} |
隐示约束 | 为满足问题的解而对不同分量之间施加的约束。如: ( x i , x i + 1 ) ∈ E , ∀ i , k ∈ { 1 , 2 , 3 , 4 } , x i ≠ x k , ( x n , x 1 ) ∈ E (x_i,x_{i+1}) \in E,\forall i,k \in \{1,2,3,4\},x_i \neq x_k,(x_n,x_1) \in E (xi,xi+1)∈E,∀i,k∈{1,2,3,4},xi=xk,(xn,x1)∈E |
解空间 | 解向量满足约束条件的所有多元组,构成了问题的一个解空间。如:满足 x i ∈ { 1 , 2 , 3 , 4 } , ( x i , x i + 1 ) ∈ E , ∀ i , k ∈ { 1 , 2 , 3 , 4 } , x i ≠ x k , ( x n , x 1 ) ∈ E 的 所 有 ( x 1 , x 2 , . . . , x 4 ) x_i \in \{1,2,3,4\},(x_i,x_{i+1}) \in E,\forall i,k \in \{1,2,3,4\},x_i \neq x_k,(x_n,x_1) \in E的所有(x_1,x_2,...,x_4) xi∈{1,2,3,4},(xi,xi+1)∈E,∀i,k∈{1,2,3,4},xi=xk,(xn,x1)∈E的所有(x1,x2,...,x4) |
存储 | 当前路径 |
适用特征
-
适用于搜索问题和优化问题
-
必要条件:多米诺性质
设 P ( x 1 , x 2 , . . . , x i ) P(x_1,x_2,...,x_i) P(x1,x2,...,xi)是关于向量 < x 1 , x 2 , . . . , x i > <x_1,x_2,...,x_i> <x1,x2,...,xi>的某个性质,那么 P ( x 1 , x 2 , . . . , x i + 1 ) P(x_1,x_2,...,x_{i+1}) P(x1,x2,...,xi+1)真蕴含 P ( x 1 , x 2 , . . . , x i ) P(x_1,x_2,...,x_i) P(x1,x2,...,xi)为真,即 P ( x 1 , x 2 , . . . , x i + 1 ) − > P ( x 1 , x 2 , . . . , x i ) ( 0 < i < n ) [ n 为 向 量 维 数 ] P(x_1,x_2,...,x_{i+1})->P(x_1,x_2,...,x_i)(0<i<n)[n为向量维数] P(x1,x2,...,xi+1)−>P(x1,x2,...,xi)(0<i<n)[n为向量维数]
设计要素
- 针对问题定义解空间
- 问题解向量
- 解向量分量取值集合
- 构造解空间树
- 判断问题是否满足多米诺性质
- 搜索解空间树,确定剪枝函数
- 确定存储搜索路径的数据结构
算法框架
- 递归回溯
//t:递归深度
//n:最大递归深度
//x:解向量
//f(n,t),g(n,t):当前扩展结点处未搜索的子树的起始编号和终止编号
//h(i):当前扩展结点x[t]的第i个可选值
//Constraint(t),Bound(t):当前扩展结点的约束函数,界函数
void Backtrack(int t){
if(t>n)//搜索到叶结点
Output(x);
else{
for(int i=f(n,t);i<=g(n,t);i++){
x[n] = h[i];
if(Constraint(t)&&Bound(t))
Backtrack(t+1);
}
}
}
- 迭代回溯
//t:迭代深度
//Solution(t):判断当前扩展结点处是否得到问题的可行解
void IterativeBacktrack(void){
int t = 1;
while(t>0){//退到无路可退,结束
if(f(n,t) <= g(n,t)){
for(int i=f(n,t);i <= g(n,t);i++){
x[t] = h[i];
if(Constrain(t)&&Bound(t)){
if(Solution(t))
Output(x);
else
t++;//等价于 Backtrack(t+1)
}
}
}
else
t--;//走不通了后退
}
}
优点
在搜索过程中动态产生问题的解空间。在任何时刻,算法只保存从根结点到扩展结点的路径。
分支限界法
基本思想
- 分支限界法类似于回溯法,也是在问题解空间中搜索问题的一种算法。
- 在分支限界法中,每个活结点只有一次机会成为扩展结点。一旦成为扩展,就一次性产生其所有儿子节点。在这些儿子结点中,导致不可行解或导致非最优解的儿子结点被舍弃,其余儿子结点被加入活结点表中。
- 此后,从活结点表中取下一结点成为当前扩展结点,并重复上述结点扩展过程。这个过程一直持续到找到所需的解或活结点表为空时为止。
相关概念
- 常见的两种分支搜索法:
- 队列式搜索法(FIFO):按照队列先进先出原则选取下一个结点为扩展结点
- 优先队列式搜索法:按照优先队列中规定的优先级选取优先级最高的节点成为当前扩展节点
- 最大堆(最大优先队列):最大效益优先
- 最小堆(最下优先队列):最小耗费优先
- 节点v上界( UB(v) ):从v出发得到的所有叶子节点的效益值均不大于UB(v),则UB(v)为节点v的上界。如果所有叶子节点的最大效益值等于UB(v),则UB(v)为节点v的上确界
- 节点v下界( LB(v) ):从v出现得到的所有叶子节点的效益均不小于LB(v),则LB(v)为节点v的下界。如果所有叶子节点的最小收益值等于LB(v),则:LB(v)为节点的下确界
分支限界法 V S VS VS回溯法对比
- 求解目标不同:回溯法的可以用于求解目标是找出解空间树中满足约束条件的所有解,而分支限界法的求解目标通常是找出满足约束条件的一个解或最优解。
- 搜索方式不同:回溯法主要以深度优先的方式搜索解空间树,而分支限界法则主要以广度优先或以函数优先的方式搜索解空间树。
最小值优化问题
- 对于求最小值的优化问题,如果 L B ( v ) ≥ c B e s t LB(v) \geq cBest LB(v)≥cBest,则节点v可以加入黑名单,不再对其搜索
- UB(v)通常可以利用贪心思路或者其它方式得到一个解,令其作为UB(v),而LB(v)通常要经过严格的证明
- 如果 U B ( v 0 ) = L B ( v 0 ) UB(v_0) = LB(v_0) UB(v0)=LB(v0).则直接结束,输出对应于 U B ( v 0 ) UB(v_0) UB(v0)的解即可
- 如果节点v的 U B ( v ) = L B ( v ) UB(v)=LB(v) UB(v)=LB(v),则该节点不用再继续搜索,直接用 U B ( v ) UB(v) UB(v)作为其可到达的最优值,对 c B e s t cBest cBest进行更新
- 如果找到节点v使得 f ( v ) = L B ( v 0 ) f(v)=LB(v_0) f(v)=LB(v0)或者所有的节点搜索完毕,则搜索完毕
- c B e s t cBest cBest初始等于 ( U B ( v 0 ) ) (UB(v_0)) (UB(v0)),找到 U B ( v ) UB(v) UB(v)小于 c B e s t cBest cBest,则可以更新 c B e s t cBest cBest,另外可以减掉 L B ( v ) ≥ c B e s t LB(v) \geq cBest LB(v)≥cBest的点v
最大值优化问题
- 对于求最大值的优化问题,如果 U B ( v ) ≤ c B e s t UB(v) \leq cBest UB(v)≤cBest,则节点v可以加入黑名单,不再对其搜索
- L B ( v ) LB(v) LB(v)通常可以利用贪心思路或者其它方式得到一个解,令其作为 L B ( v ) LB(v) LB(v),而 U B ( v ) UB(v) UB(v)通常要经过严格的证明
- 如果 U B ( v 0 ) = L B ( v 0 ) UB(v_0) = LB(v_0) UB(v0)=LB(v0).则直接结束,输出对应于 L B ( v 0 ) LB(v_0) LB(v0)的解即可
- 如果节点v的 U B ( v ) = L B ( v ) UB(v)=LB(v) UB(v)=LB(v),则该节点不用再继续搜索,直接用 L B ( v ) LB(v) LB(v)作为其可到达的最优值,对 c B e s t cBest cBest进行更新
- 如果找到节点v使得 f ( v ) = U B ( v 0 ) f(v)=UB(v_0) f(v)=UB(v0)或者所有的节点搜索完毕,则搜索完毕
- c B e s t cBest cBest初始等于 ( L B ( v 0 ) ) (LB(v_0)) (LB(v0)),找到 L B ( v ) LB(v) LB(v)大于 c B e s t cBest cBest,则可以更新 c B e s t cBest cBest,另外可以减掉 U B ( v ) ≤ c B e s t UB(v) \leq cBest UB(v)≤cBest的点v
代码框架
Q = {q_0};//存储所有的活结点,初始化为根结点
void Branch&Bound(){
while(Q!=空集){
select a node q from Q;//从Q中选择一个节点
Branch(1,Q_1);//对q进行分支,产生Q_1,分支时利用约束和界进行剪枝
add(Q_1,Q);//将新产生的活结点加入Q
}
}
设计要素
- 针对问题定义解空间
- 问题解向量
- 解向量分量取值集合
- 构造解空间树
- 判断问题是否满足多米诺性质
- 确定减值函数与界函数
适用特征
- 多米诺性质
- 适用问题:适用于求解最优解或一个可行解
- 程序结构:节点扩展适合采用迭代法进行
随机化算法
基本思想
当一个算法面临某种选择的时候,有时候随机化选择比耗时做最优选择更好,尤其是当最优选择所花费的时间大于随机选择的平均时间的时候
特点
- 不可再现性:每次执行的结果不尽相同
- 分析困难:需要数学基础
分类
- 数值随机化算法:主要用于找到一个数值问题的近似解
- 舍伍德算法:平滑不同输入实例的执行时间
- 拉斯维加斯算法:用随机序列代替有规律的枚举
- 蒙特卡洛算法:蒙的次数越多,蒙对的概率越大
遗传算法
基本思想
基本遗传算法(Simple Genetic Algorithms,SGA),又称简单遗传算法。
算法组成
-
编码(产生初始种群):把对象抽象为由二进制或数字串进行编码的串
-
适应度函数:用于描述一个个体(解)的好坏,适应度函数值越大,解的质量越好
-
遗传算子(选择、交叉、变异):轮盘赌选择法、交叉算子、基本位变异算子
-
运行参数
-
M M M:种群规模
-
P c P_c Pc:交叉概率
-
P m P_m Pm:变异概率
-
T T T:终止进化代数(停止规则)
-
特点
- 群体搜素,易于并行化处理
- 自组织,自适应和自学习特征
- 不需要求导和其他辅助知识,只需要知道适应度函数
- 强调概率转换规则,而非确定的转换规则
实例
- 混合遗传算法
- 免疫遗传算法
- 小生境遗传算法
- 单亲遗传算法
- 并行遗传算法