算法概述

来源:互联网 发布:便笺元数据 编辑:程序博客网 时间:2024/05/21 18:38

算法描述

算法定义: 是通过一个有限的指令序列集合对特定问题进行求解的一种计算执行描述。
算法特征:输入,输出,确定性,有限性,正确性,通用性
算法代价:使用规模的函数来表示
算法代价评估:使用最差时间,最好时间,平均时间来评估
算法系统模型分类:随机存储模型,并行模型
算法代价计算方法:数学归纳法,计分方法,拆分方法,估值方法,代价树方法,带入方 法,master公式等
算法涉及到的结构:线性结构,树结构(大小堆,排序树,红黑树,B和B+树,区间数,t-tree等等),图(单源最短路经,任意两点最短路经,工作安排,最小支撑树,树图关系等等一用)
算法分类:分治法,线性法,贪心法,分支有限法,动态规划法,回搠法,随即算法,并行算法等等

具体算法简介:
分治法:它是基于递归算法的一种算法。有三步:Divid, conqure,combine三部分。应用有归并算法。

动态规划思想

动态规划的思想实质是分治思想和解决冗余。 
与分治法类似的是,将原问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。
与分治法不同的是,经分解的子问题往往不是互相独立的。若用分治法来解,有些共同部分(子问题或子子问题)被重复计算了很多次。
如果能够保存已解决的子问题的答案,在需要时再查找,这样就可以避免重复计算、节省时间。动态规划法用一个表来记录所有已解的子问题的答案。
这就是动态规划法的基本思路。具体的动态规划算法多种多样,但它们具有相同的填表方式

动态规划求解步骤:
1、找出最优解的性质,并刻画其结构特征;
2、递归地定义最优值(写出动态规划方程);
3、以自底向上的方式计算出最优值;
4、根据计算最优值时记录的信息,构造最优解。

动态规划两个条件:
最优子结构 、 重叠子结构

举例:
多段图问题:
描述:
多段图G=(V, E)是一个有向图,且具有以下特征:
(1)划为k≥2个不相交的集合Vi, 1≤i≤k;
(2) V1和Vk只有一个结点s(源点)和t(汇点);
(3)若<u, v>∈E(G),u∈Vi ,则v∈Vi+1 1≤i≤k, 边上成本记c(u,v);若<u, v>∈E(G),边上成本记c(u,v)=∞;
问题:求由s到t的最小成本路径。

代码:
多段图G=(V, E)是一个有向图,且具有以下特征:
void MultiStageGraph(Type **e, int k, int n, int p[])
{//输入n个结点的k段图,假设顶点按段的顺序编号
//e是边集,p[1..k]是最小成本路径
int *cost = new int[n]; //cost[j]相当于前面的cost(i,j)
int *d = new int[n]; //d[j]保存vj与下一阶段的最优连接点
cost[n]=0;
for(int i=n-1; i>=1; i--) { //计算cost[i]
cost[i]=∞;
while(任意<i, r>∈e) //r是下一阶段中的顶点
if( c(i, r)+cost[r]<cost[i] ) {
cost[i]=c(i, r)+cost[r]; O(n+e)
d[i]=r;
}
}
p[1]=1; p[k]=n; //以下是找一条最小成本路径(构造解)
for(int i=2; i<=k-1; i++) p[i]=d[p[i-1]]; O(k)
} ∴ T(n)=O(n+e) 

算法的应用问题:
有0-1背包问题,小数背包问题,矩阵链乘法,lcs问题,最大子段和问题,活动安排,最优装载等等
贪心算法
主要思想:
从问题的某一个初始解出发,通过一系列的贪心选择——当前状态下的最优选择,逐步逼近给定的目标,以尽可能快的求得更好的解。当达到算法中的某一步不能再继续前进时,算法停止。
在贪婪算法(greedy method)中采用逐步构造最优解的方法。在每个阶段,都作出一个看上去最优的决策(在一定的标准下)。决策一旦作出,就不可再更改。作出贪婪决策的依据称为贪婪准则(greedy criterion)。
求解步骤:
从问题的某一初始解出发;
while 依据贪心策略朝给定目标前进一步 do
  求出可行解的一个解元素;
由所有解元素组合成问题的一个可行解;
求解问题性质:
贪心选择性质:可通过局部最优(贪心)选择达到全局最优解;
- 通常以自顶向下的方式进行,每次选择后将问题转化为规模更小的子问题;
- 该性质是贪心法使用成功的保障,否则得到的是近优解;
最优子结构性质:问题的最优解包含它的子问题的最优解;
- 并不是具有最优子结构性质的问题都可以采用贪心策略;
- 往往利用最优子结构性质来证明贪心选择性质;

求解例子:最优装载
void ContainerLoading(int x[], float w[], float c, int n)
{ //x[i] =1当且仅当货箱i被装载,对重量按间接寻址方式排序
int *t = new int [n+1]; //t是间接寻址表
IndirectSort(w, t, n); //此时, w[t[i]]≤w[t[i+1]], 1≤i<n
for(int i = 1; i <= n; i++) //初始化x
x[i] = 0;
for(i = 1; i <= n && w[t[i]] <= c; i++) { //按重量次序选择物品
x[t[i]] = 1;
c -= w[t[i]];
} // 剩余容量
delete t[];
} T(n)=O(nlogn)

搜索算法
(1)穷举搜索(Exhaustive Search)
(2)盲目搜索(Blind or Brute-Force Search)
- 深度优先(DFS)或回溯搜索(Backtracking); 
- 广度优先搜索(BFS);
- 迭代加深搜索(Iterative Deepening); 
- 分枝限界法(Branch & Bound);
- 博弈树搜索(α-β Search)
(3)启发式搜索(Heuristic Search)
- A*算法和最佳优先(Best-Fist Search) 
- 迭代加深的A*算法
- B*,AO*,SSS*等算法 
- Local Search, GA等算法
搜索空间的三种表示
- 表序表示: 搜索对象用线性表数据结构表示;
- 显式图表示: 搜索对象在搜索前就用图(树)的数据结构表示;
- 隐式图表示: 除了初始结点, 其他结点在搜索过程中动态生成. 缘于搜索空间大, 难以全部存储. 
回溯算法描述:
回溯法是一个既带有系统性又带有跳跃性的搜索算法 。
它在包含问题的所有解的解空间树中,按照深度优先的策略,从根结点出发搜索解空间树。——系统性
算法搜索至解空间树的任一结点时,判断该结点为根的子树是否包含问题的解,如果肯定不包含,则跳过以该结点为根的子树的搜索,逐层向其祖先结点回溯。否则,进入该子树,继续按深度优先的策略进行搜索。——跳跃性
这种以深度优先的方式系统地搜索问题的解的算法称为回溯法,它适用于解一些组合数较大的问题。
求解步骤:
(1)针对问题,定义问题的解空间(对解进行编码);
(2)确定易于搜索的解空间组织结构(按树或图组织解);
(3)以深度优先方式搜索解空间,搜索过程中裁减掉死结点的子树提高搜索效率。
二类常见的解空间树
①子集树:如0-1背包,叶结点数2n,总结点数2n+1,遍历时间为Ω(2n);
②排列树:如TSP问题,叶结点数n!,遍历时间为Ω(n!)。 
例子:
tsp问题:
void main(int n) 

float a[n][n]; int x[n]={1,2,…,n}; int bestx[]; float cc=0.0; 
float bestv=∞; //bestx保存当前最佳路径,bestv保存当前最优值 
input(a); //输入邻接矩阵 
TSPBacktrack(2); 
output(bestv, bestx[]); 

void TSPBacktrack(int i)
{//cc记录(x[1],x[2]), … ,(x[n-1],x[n])的距离和
if( i>n ) { //搜索到叶结点,输出可行解与当前最优解比较
if( cc+a[x[n]][1]<bestv || bestv==∞ ) {
bestv=cc+a[x[n]][1];
for( int j=1; j<=n; j++ ) bestx[j]=x[j];
}
}
else {
for( int j=i; j<=n; j++ ) 
if( cc+a[x[i-1]][x[j]]<bestv || bestv==∞ ) {//限界搜索子树
swap(x[i], x[j]);
cc+=a[x[i-1]][x[j]];
Backtrack(i+1);
cc-=a[x[i-1]][x[j]];
swap(x[i], x[j]);
}
}


分支限界法
其本思想:
在解空间树中, 以广度优先BFS或最佳优先方式搜索最优解, 利用部分解的最优信息, 裁剪那些不能得到最优解的子树以提高搜索效率。
搜索策略是:在扩展结点处,先生成其所有的儿子结点(分支),然后再从当前的活结点表中选择下一个扩展结点。为了有效地选择下一扩展结点,以加速搜索的进程,在每一活结点处,计算一个函数值(优先值),并根据这些已计算出的函数值,从当前活结点表中选择一个最有利的结点作为扩展结点,使搜索朝着解空间树上有最优解的分支推进,以便尽快地找出一个最优解。
与回溯法的不同:
求解目标不同 :
一般而言,回溯法的求解目标是找出解空间树中满足约束条件的所有解,而分支限界法的求解目标则是尽快地找出满足约束条件的一个解; 
搜索方法不同:
回溯算法使用深度优先方法搜索,而分枝限界一般用宽度优先或最佳优先方法来搜索; 
对扩展结点的扩展方式不同:
分支限界法中,每一个活结点只有一次机会成为扩展结点。活结点一旦成为扩展结点,就一次性产生其所有儿子结点;
存储空间的要求不同:
分枝限界法的存储空间比回溯法大得多,因此当内存容量有限时,回溯法成功的可能性更大;
求解步骤:
①定义解空间(对解编码);
②确定解空间的树结构;
③按BFS等方式搜索:
a.每个活结点仅有一次机会变成扩展结点;
b.由扩展结点生成一步可达的新结点;
c.在新结点中, 删除不可能导出最优解的结点; //限界策略
d.将余下的新结点加入活动表(队列)中;
e.从活动表中选择结点再扩展; //分枝策略
f.直至活动表为空;
两种节点扩充方式:
先进先出队列(F I F O) : 从活结点表中取出结点的顺序与加入结点的顺序相同,因此活结点表的性质与队列相同; 
优先队列(耗费用小根堆,受益用大根堆): 每个结点都有一个对应的耗费或收益。
-如果查找一个具有最小耗费的解,则活结点表可用最小堆来建立,下一个扩展结点就是具有最小耗费的活结点;
-如果希望搜索一个具有最大收益的解,则可用最大堆来构造活结点表,下一个扩展结点是具有最大收益的活结点。
例子:
最优装载问题:
Typedef struct Treenode{
float wt; //搜索到该结点时的载重量
int level; //结点所处的层次
struct Treenode *parent; //指向父结点的指针
}*qnode;
void MaxLoading(float w[], float c, int n)
{//返回最优值bestw和解向量x[1..n]
iniqueue(Q); //建立空队列Q,Q中元素为qnode类型 
p=new qnode; //生成一个qnode结点,装入①结点
p->wt=0; p->level=1; p->parent=NIL;
enqueue(Q, p); //入队
float bestw=0; qnode bestp=p; 
while( !empty(Q) ) { //BFS遍历
p=dequeue(Q); //出队,p结点成为扩展结点
//扩展左孩子结点
if( p->wt+w[p->level]<=c ) { //p的左孩子是可行结点
q=new qnode; 
q->wt=p->wt+w[p->level]; q->level=p->level+1; q->parent=p;
enqueue(Q, q);
if( bestw<q->wt ) {
bestw=q->wt; bestp=q;
}
}
//扩展右孩子结点
{ q=new qnode;
q-wt=p->wt; q->level=p->level+1; q->parent=p;
enqueue(Q, q);
}
}
//构造解
for( int i=1; i<=n; i++ ) x[i]=0;
p=bestp;
while( p!=NIL ) {
float tempw=p->wt; 
p=p->parent;
if( p->wt!=tempw ) x[p->level]=1;
}
}//end

随机算法:
其本信息:
定义:是一个概率图灵机. 也就是在算法中引入随机因素, 即通过随机数选择算法的下一步操作。
三要素:输入实例、随机源和停止准则.
特点:简单、快速和易于并行化.
一种平衡:随机算法可以理解为在时间、空间和随机三大计算资源中的平衡(Lu C.J. PhD Thesis 1999)
常见的两类随机算法:
Las Vegas算法运行结束时总能给出正确的解,但其运行时间每次有所不同。
Monte Carlo算法可能得到不正确的结果,但这种概率是小的且有界的。
运行时间的期望和方差
(1)实例的运行时间期望
对固定实例x, 设随机算法A的运行时间 是一个[0,+∞)上的随机变量, 定义随机算法A在实例x上的运行时间期望为 , 也称为随机算法A在实例x上的执行时间.
(2)算法的运行时间期望
如果对一个规模为n问题的所有实例是均匀选取的, 则定义各个实例上的平均执行时间为随机算法在该问题上的运行时间期望, 记为T(n)
注: 类似地可以定义方差. 

原创粉丝点击