算法分析与设计总结
持续·八周的学习,经过八周时间的练习,我了解到:算法是一系列解决问题的清晰指令,代表着用系统的方法描述解决问题的策略机制。不同的算法可能用不同的时间、空间或效率来完成同样的任务。一个算法的优劣可以用空间复杂性和时间复杂度来衡量。算法可以使用自然语言、伪代码、流程图等多种不同的方法来描述。计算机系统中的操作系统、语言编译系统、数据库管理系统以及各种各样的计算机应用系统中的软件,都必须使用具体的算法来实现。算法设计与分析是计算机科学与技术的一个核心问题。
对于一个算法,他还必须具有以下特征:
1、 有穷性:算法在执行有限歩之后必须终止;
2、 确定性:算法的每一个步骤须有确切的定义;
3、 输入:一个算法有0个或多个输入,作为算法开始执行前的初始值,或初始状态;
4、 输出项:一个算法有一个或多个输出,以反映对输入数据加工后的结果;
5、 可行性:在有限时间内完成计算过程。
在一个算法设计的整个过程中,其包括对问题需求的说明、数学模型的拟制、算法的详
细设计、算法的正确性验证、算法的实现、算法分析、程序测试和文档资料的编制。算法可大致分为基本算法、数据结构的算法、数论与代数算法、计算几何的算法、图论的算法、动态规划以及数值分析、加密算法、排序算法、检索算法和并行算法。并大致分为以下三类:
1、 有限的、确定性算法;
2、 有限的、费确定性算法;
3、 无限的算法。
在本篇文章中,我们主要写出以下几种算法;
1、递归算法
递归算法是一种直接或间接的调用自身的算法。
能采用递归描述的算法通常有这样的特征:为求解规模为n的问题,设法将它分解成规模较小的问题,然后从这些小问题的解可以方便地构造出大问题的解,并且这些规模较小的问题也能采用同样的分解和综合方法,分解成规模更小的问题,并从这些更小问题的解构造出规模较大问题的解。特别的,当规模n=0或1时,能直接得解。
递归算法解决问题的特点有: (1) 递归就是在过程或函数里调用自身 (2) 在使用递归策略时,必须有一个明确的递归结束条件,称为递归出口 (3) 递归算法解题通常显得很简洁,但递归算法解题的运行效率较低 (4) 在递归调用的过程中系统为每一层的返回点、局部变量等开辟堆栈来存储。
递归算法一般用于解决3类问题:(1)数据的定义是按递归定义的。(2)问题解法用递归算法实现。(3)数据的结构形式是按递归定义的。
递归算法的优点:结构清晰,可读性强,而且容易用数学归纳法来证明算法的正确性,因此它为设计算法、调试程序带来很大方便。
递归算法的缺点:递归算法的运行效率较低,无论是耗费的计算时间还是占用的存储空间都比非递归算法要多。
2、 分治算法
分治算法是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题,直到最后子问题可以简单地直接求解,原问题的解即子问题解的合并。如果原问题可分割成k个子问题,且这些子问题都可解,并可利用这些子问题的解求出原问题的解,那么这种分治法就是可行的。由分治法产生的子问题往往是原问题的较小模式,这就为使用递归技术提供了方便。在这种情况下,反复应用分治手段,可以使子问题与原问题类型一致而其规模却不断缩小,最终使子问题缩小到很容易直接求出其解。这自然导致递归过程的产生。分治与递归像一对孪生兄弟,经常同时应用在算法设计之中,并由此产生许多高效算法。
分治算法的3个步骤:
(1) 分解:将原问题分解成若干个规模较小,相互独立,与原问题形式相同的子问题。
(2) 解决:递归解各个子问题。
(3) 合并:将子问题的解合并为原问题的解。
适用条件:
1) 该问题的规模缩小到一定的程度就可以容易地解决
2) 该问题可以分解为若干个规模较小的相同问题,即该问题具有最优子结构性质。
3) 利用该问题分解出的子问题的解可以合并为该问题的解;
4) 该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子问题。
分治策略的算法设计模式
Divide_and_Conquer(P)
{
if (|P|<=n0 )
return adhoc(P);
divide P into smaller substancesP1,P2,…,Pk;
for (i=1; i<=k; k++)
yi=Divide-and-Conquer(Pi) //递归解决
Pi Return merge(y1,y2,…,yk) //合并子问题
}
3、 贪心算法
贪心算法也称贪婪算法。它在对问题求解时,总是做出在当前看来是最好的选择。它不从整体最优上考虑,所得出的仅是在某种意义上的局部最优解。
贪心算法的基本思路如下:(1)建立数学模型来描述问题(2)把求解的问题分成若干个子问题(3)对每一子问题求解,得到子问题的局部最优解(4)把子问题的局部最优解合成原来问题的一个解。
贪心算法的求解过程:
(1)候选集合A:为了构造问题的解决方案,有一个候选集合A作为问题的可行解,即问题的最终解均取自候选集合A。
(2)解集合S:随着贪心选择的进行,解集合S不断扩展,直到构成满足问题的完整解。
(3)解决函数solution:检查解集合S是否构成问题的完整解。
(4)选择函数select:即贪心策略,这是关键,指出哪个候选对象最有希望构成问题的解,通常和目标函数有关。
(5)可行函数feasible:检查解集合中加入一个候选对象是否可行,即解集合扩展后是否满足约束条件。
贪心算法的一般流程:
Greedy(A) {
S={ }; //初始解集合为空集
while (not solution(S)) //集合S没有构成问题的一个解
{ x= select(A); //在候选集合A中做贪心选择
if feasible(S, x) //判断集合S中加入x后的解是否可行
S = S+{x};
A = A-{x};
}
return S;
}
4、回溯算法
回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。当探索到某一步时,发现原先的选择并不优或达不到目标,就回退一步重新选择,这种走不通就退回再走的技术成为回溯法,满足回溯条件的某个状态的点称为“回溯点”。迷宫问题算法所采用的就是回溯算法。
回溯算法的基本思想:
约束函数:约束函数是根据题意定出的。通过描述合法解的一般特征用于去除不合法的解,从而避免继续搜索出这个不合法解的剩余部分。因此,约束函数是对于任何状态空间树上的节点都有效、等价的。
状态空间树:状态空间树是一个对所有解的图形描述。树上的每个子节点的解都只有一个部分与父节点不同。
扩展节点、活结点、死结点:所谓扩展节点,就是当前正在求出它的子节点的节点,在深度优先搜索中,只允许有一个扩展节点。活结点就是通过与约束函数的对照,节点本身和其父节点均满足约束函数要求的节点;死结点反之。由此很容易知道死结点是不必求出其子节点的(没有意义)。
利用回溯法解题的具体步骤:
首先,要通过读题完成下面三个步骤:
(1)描述解的形式,定义一个解空间,它包含问题的所有解。
(2)构造状态空间树。
(3)构造约束函数(用于杀死节点)。
然后就要通过深度优先搜索思想完成回溯,完整过程如下:
(1)设置初始化的方案(给变量赋初值,读入已知数据等)。
(2)变换方式去试探,若全部试完则转(7)。
(3)判断此法是否成功(通过约束函数),不成功则转(2)。
(4)试探成功则前进一步再试探。
(5)正确方案还未找到则转(2)。
(6)已找到一种方案则记录并打印。
(7)退回一步(回溯),若未退到头则转(2)。
(8)已退到头则结束或打印无解
我们采用装载问题回溯算法数据结构
#define NUM 100
int n; //集装箱的数量
int c; //轮船的载重量
int w[NUM]; //集装箱的重量数组
int x[NUM]; //当前搜索的解向量
int r; //剩余集装箱的重量
int cw; //当前轮船的载重量
int bestw; //当前最优载重量
int bestx[NUM]; //当前最优解算法实现 //形参表示搜索第t层结点
void Backtrack(int t)
{
//到达叶子结点
if(t>n)
{
if(cw>bestw) //更新最优解
{
for(int i=1; i<=n; i++)
bestx[i] = x[i];
bestw = cw;
}
return;
}
//更新剩余集装箱的重量
r -= w[t]; //搜索左子树
if(cw+w[t]<=c)
{
x[t] = 1;
cw += w[t];
Backtrack(t+1);
cw -= w[t];
}
//搜索右子树
if(cw+r>bestw)
{
x[t]=0;
Backtrack(t+1);
}
r += w[t];
//恢复状态
}
5、分支限界算法
分支限界算法是一种在表示问题解空间的树上进行系统搜索的方法。该方法使用了广度
优先策略,同时采用最大收益(或最小损耗)策略来控制搜索的分支。
分支限界法的基本思想是对包含具有约束条件的最优化问题的所有可行解的解(数目有限)空间进行搜索。该算法在具体执行时,把全部可行的解空间不断分割为越来越小的子集,并为每个子集内的解计算一个下界或上界。在每次分支后,对所有界限超出已知可行解的那些子集不再做进一步分支,解的许多子集可不予考虑,从而缩小了搜索的范围。这一过程一直进行到找出可行解的值不大于任何子集的界限为止。这种算法一般可以求得最优解。
分支结点的选择
从活结点表中选择下一个活结点作为新的扩展结点,分支限界算法通常可以分为两种形式:
(1)FIFO(First In First Out)分支限界算法
按照先进先出(FIFO)原则选择下一个活结点作为扩展结点,即从活结点表中取出结点的顺序与加入结点的顺序相同。
(2)最小耗费或最大收益分支限界算法
在这种情况下,每个结点都有一个耗费或收益。
根据问题的需要,可能是要查找一个具有最小耗费的解,或者是查找一个具有最大收益的解。
提高分支限界算法的效率:
实现分支限界算法时,首先确定目标值的上下界,边搜索边减掉搜索树的某些分支,提高搜索效率。
在搜索时,绝大部分需要用到剪枝。“剪枝”是搜索算法中优化程序的一种基本方法,需要通过设计出合理的判断方法,以决定某一分支的取舍。
若我们把搜索的过程看成是对一棵树的遍历,那么剪枝就是将树中的一些“死结点”,不能到达最优解的枝条“剪”掉,以减少搜索的时间。
所遵循的原则:
(1) 正确性
(2) 准确性
(3) 高效性
我们同样采用装载问题的例子
装载问题分支限界算法的数据结构
#define NUM 100 int n; //集装箱的数量
int c; //轮船的载重量
int w[NUM]; //集装箱的重量数组算法实现
int MaxLoading()
{
queue
Q.push(-1);
int i = 0;
int Ew = 0;
int bestw = 0;
int r = 0;
for(int j=1; j r += w[j]; //搜索子空间树 while (true) { //检查左子树 int wt = Ew+w[i]; if(wt<=c) //检查约束条件 { if(wt>bestw) bestw = wt; //加入活结点队列 if (i } //检查右子树 //检查上界条件 if(Ew+r>bestw && i Q.push(Ew); //从队列中取出活结点 Ew = Q.front(); Q.pop(); if(Ew==-1) //判断同层的尾部 { if(Q.empty()) return bestw; //同层结点尾部标志 Q.push(-1); //从队列中取出活结点 Ew =Q.front(); Q.pop(); i++; r -=w[i]; } } returnbestw; } 6、 动态规划算法 动态规划算法是解决多阶段决策问题的一种方法。 其中多阶段决策问题: 如果一类问题的求解过程可以分为若干个互相联系的阶段,在每一个阶段都需要做出决策,并影响到下一个阶段的决策。 多阶段决策问题,就是要在可以选择的那些策略中间,选取一个最优策略,使在预定的标准下达到最好的效果。 最优性原理: 不论初始状态和第一步决策是什么,余下的决策相对于前一次决策所产生的新状态,构成一个最优决策序列。 最优决策序列的子序列,一定是局部最优决策子序列。 包含有非局部最优的决策子序列,一定不是最优决策序列。 其具体指导思想: 在做每一步决策时,列出各种可能的局部解。 依据某种判定条件,舍弃那些肯定不能得到最优解的局部解。 以每一步都是最优的来保证全局是最优的。 几个概念: 阶段:据空间顺序或时间顺序对问题的求解划分阶段。 状态:描述事物的性质,不同事物有不同的性质,因而用不同的状态来刻画。对问题的求解状态的描述是分阶段的。 决策:根据题意要求,对每个阶段所做出的某种选择性操作。 状态转移方程:用数学公式描述与阶段相关的状态间的演变规律。 动态规划算法的步骤: (1)分析最优解的性质,并刻画其结构特征; (2)递归地定义最优值(写出动态规划方程); (3)以自底向上的记忆化方法计算出最优值; (4)根据计算最优值时得到的信息,构造一个最优解。 与此同时在上课学习的过程中,我们还进行了一系列lintcode的题目训练,让我对这些算法的应用有更深的了解。在计算机行业中,这门课程是非常重要的一门课程,对我们这些往计算机方向发展的数学学子来说,这无疑是为我们同计算机专业的学生拓宽了逻辑思维能力,将会提高我们在未来工作中的竞争力。 该谢老师的指导,虽然这门课程结束了,但是我还会继续学习这门课程,让自己更加有竞争力,为自己增彩。