算法分析与设计论文

27 篇文章 0 订阅
6 篇文章 0 订阅
1 递归算法
1.1 递归算法的定义
   程序直接或间接调用自身的编程技巧称为递归算法(Recursion)。
1.2 递归算法的说明
   一个过程或函数在其定义或说明中又直接或间接调用自身的一种方法.它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解。递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。
1.3 递归的条件
 递归需要有边界条件、递归前进段和递归返回段。当边界条件不满足时,递归前进,当边界条件满足时,递归返回。
1.4 递归的缺点
 递归算法解题的运行效率较低。在递归调用过程中,系统为每一层的返回点、局部变量等开辟了堆栈来存储。递归次数过多容易造成堆栈溢出等。
1.5 递归的典型例题
  1.5.1 Fibonacci数列

“菲波那切数列”是意大利数学家列昂纳多-斐波那契最先研究的一种递归数列,他的每一项都等于前两项制盒次数列的前几项为1,1,2,3,5等。在生物数学中许多生物现象都会出现菲波那切数列的规律,斐波那契数列相邻两项的比例近于黄金分割数,其递归定义为:

   f(n)=f(n-1)+f(n-2)

 
由上述数学公式我们可以轻松的得到如下的递归求解fibonacci数列的代码:
int fib(int n)
{
  if (n<=1) return 1;
  return fib(n-1)+fib(n-2);
}
但是我们也能看出,该算法的效率非常低,因为重复递归的次数太多。
因此我们可以在此基础上改进一下该算法:
int  fib[50]; //采用数组保存中间结果
void fibonacci(int n)
{
fib[0] = 1;
fib[1] = 1;
for (int i=2; i<=n; i++)
fib[i] = fib[i-1]+fib[i-2];
}
采用数组保存之前已经求出来的数据,减少了递归的次数,提高了算法的效率。
1.6 递归算法的学习心得
写好一个递归算法,我认为主要是要把握好三个方面:
1.6.1找到题目中能够重复的逻辑。
出现重复逻辑一定是必不可少的,因为递归的精髓就是loop。但是,重复的逻辑需要抽象。抽象出来一个干净利落的可循环逻辑对程序编写的帮助很大。
1.6.2控制边界条件。
控制边界保证了程序在正确的框架下运行。因为抽象出来的逻辑需要一个框架保证其可以递归执行,“刚刚好”是它的要点。写程序也经常因为边界把控的不准确容易留下bug。那么如何正确控制边界?一个比较好的办法,就是对边界也进行逻辑上的递归。因为递归是层层相同,那么第一层和第k层是一致的。第一层容易得出执行边界,那么相应的可以抽象第k层的执行边界。不过第k层的边界是逻辑上的,而第一层通常是数值上的。通常在第一层的辅助下抽象出严格的逻辑边界。
1.6.3恰当地退出。
递归的退出往往和逻辑边界是相辅相成的,一般递归的退出有两种表现形式:
1.下层递归检测边界溢出退出。其特点实在递归代码的开始会有边界控制。
2.本层递归检查边界。特点是在进入下层递归前检查边界。
    这两种方式最大的不同在于效率,因为每层递归会有对临时数据的保存,所以减少递归层数可以降低程序损耗。
你站在桥上看风景,看风景人在楼上看你,明月装饰了你的窗子,你装饰了别人的梦”
                                    ---卞之琳
这首取自卞之琳《断章》中诗句,简单中却蕴含着递归的思想,读后让人回味无穷。


2 分治算法
2.1 分治策略
分治策略是对于一个规模为n的问题,若该问题可以容易地解决(比如说规模n较小)则直接解决,否则将其分解为k个规模较小的子问题,这些子问题互相独立且与原问题形式相同。递归地解这些子问题,然后将各子问题的解合并得到原问题的解。
2.2 分治法的基本步骤
分治法在每一层递归上都有三个步骤:
分解:将原问题分解为若干个规模较小,相互独立,与原问题形式相同的子问题;
解决:若子问题规模较小而容易被解决则直接解,否则递归地解各个子问题;
合并:将各个子问题的解合并为原问题的解。
2.2.1由此产生的问题
根据分治法的分割原则,原问题应该分为多少个子问题才较适宜?各个子问题的规模应该怎样才为适当?这些问题很难予以肯定的回答。在用分治法设计算法时,最好使子问题的规模大致相同。如分成大小相等的k个子问题,许多问题可以取k=2。这种使子问题规模大致相等的做法是出自一种平衡(Balancing)子问题的思想,它几乎总是比子问题规模不等的做法要好。
2.3 分治策略的算法设计模式
ivide_and_Conquer(P)
{
if (|P|<=n0 ) return adhoc(P);
divide P into smaller substances P1,P2,…,Pk;
for (i=1; i<=k; k++) 
yi=Divide-and-Conquer(Pi)     //递归解决Pi
Return merge(y1,y2,…,yk)    //合并子问题
}
2.4 分治法的适用条件
分治法所能解决的问题一般具有以下几个特征:
1.该问题的规模缩小到一定的程度就可以容易地解决;
2.该问题可以分解为若干个规模较小的相同问题,即该问题具有最优子结构性质;
3.利用该问题分解出的子问题的解可以合并为该问题的解;
4.该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子子问题。
2.5 学习心得
任何一个可以用计算机求解的问题所需的计算时间都与其规模n有关。问题的规模越小,越容易直接求解,解题所需的计算时间也越少。分治法的设计思想是,将一个难以直接解决的大问题,分割成一些规模较小的相同问题,以便各个击破,分而治之。如果原问题可分割成k个子问题(1<k≤n),且这些子问题都可解,并可利用这些子问题的解求出原问题的解,那么这种分治法就是可行的。由分治法产生的子问题往往是原问题的较小模式,这就为使用递归技术提供了方便。
但是这就出现了如何分的问题,以及分完后如何合并的问题。
如何分:我们往往先把输入分成两个与原来相同的子问题,如果规模还太大,我们对这些子问题再做上述处理,直到这些子问题容易解决为止.
合并子问题:合并是比较困难的,往往根据具体问题而定,没有固定的方法。
需要特别说明的是,分治问题往往用到递归算法,利用递归来实现大问题化成小问题,以及小问题最后合并到大问题会简单不少。
2.6 几种类型的分治问题:
1.把问题分成字问题后,如果某个子问题解决了则整个问题也就解决了,无须合并.
    典型事例:二分检索问题(前提是一组按照关键码排好顺序对象,不妨设按升序排列)
二分检索的解题思路是先看看中间那个数,如果这个数是要查找的,那么ok,问题解决,否则如果比key大,那么在前一半继续上述过程,否则在后一半继续上述过程,直到找到或者查找失败.
2. 把问题分了之后,再经过合并最终解决问题.
典型事例:归并排序问题:
  这个问题就象我们在一个很长的队,现在要按大小个排好,我们把这个队分成两个(如果还是太长,可以再分,先看分成两个的问题,这样容易看清合并过程),我们把这两队分别排好,然后合成一队,合并的过程很简单,就是弄一个新的队,从那两个个队的排头分别出列,比一下矮的站在第一个位置,高的在一边等着,等另一个队现在的排头出列,再比一下,矮的排在第二个位置,高的站在一边,重复上述过程,直到有一个队变空,然后把另一个队拉过来接在新队的队尾,这就排好了整个队了.


3 动态规划
动态规划一直是ACM竞赛中的重点,同时又是难点,因为该算法时间效率高,代码量少,多元性强,主要考察思维能力、建模抽象能力、灵活度。
3.1 什么是动态规划
  1.动态规划是解决多阶段决策问题的一种方法。
  2.动态规划实际上就是一种排除重复计算的算法,更具体的说,动态规划就是用空间换取时间。
3.2 多阶段决策问题
如果一类问题的求解过程可以分为若干个互相联系的阶段,在每一个阶段都需作出决策,并影响到下一个阶段的决策。多阶段决策问题,就是要在可以选择的那些策略中间,选取一个最优策略,使在预定的标准下达到最好的效果.利用动态规划求解多阶段问题最重要的是要找出状态转移方程。
3.3 最优性原理
不论初始状态和第一步决策是什么,余下的决策相对于前一次决策所产生的新状态,构成一个最优决策序列。最优决策序列的子序列,一定是局部最优决策子序列。包含有非局部最优的决策子序列,一定不是最优决策序列。
3.4 指导思想
1.在做每一步决策时,列出各种可能的局部解
2.依据某种判定条件,舍弃那些肯定不能得到最优解的局部解。
3.以每一步都是最优的来保证全局是最优的。
3.5 动态规划问题满足三大重要性质
1.最优子结构性质:如果问题的最优解所包含的子问题的解也是最优的,我们就称该问题具有最优子结构性质(即满足最优化原理)。最优子结构性质为动态规划算法解决问题提供了重要线索。
2.子问题重叠性质:子问题重叠性质是指在用递归算法自顶向下对问题进行求解时,每次产生的子问题并不总是新问题,有些子问题会被重复计算多次。动态规划算法正是利用了这种子问题的重叠性质,对每一个子问题只计算一次,然后将其计算结果保存在一个表格中,当再次需要计算已经计算过的子问题时,只是在表格中简单地查看一下结果,从而获得较高的效率。
3.无后效性:将各阶段按照一定的次序排列好之后,对于某个给定的阶段状态,它以前各阶段的状态无法直接影响它未来的决策,而只能通过当前的这个状态。换句话说,每个状态都是过去历史的一个完整总结。这就是无后向性,又称为无后效性。
3.6 动态规划学习心得
动态规划背后的基本思想非常简单。大致上,若要解一个给定问题,我们需要解其不同部分(即子问题),再合并子问题的解以得出原问题的解。 通常许多子问题非常相似,为此动态规划法试图仅仅解决每个子问题一次,从而减少计算量: 一旦某个给定子问题的解已经算出,则将其记忆化存储,以便下次需要同一个子问题解之时直接查表。 这种做法在重复子问题的数目关于输入的规模呈指数增长时特别有用。通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。 动态规划常常适用于有重叠子问题和最优子结构性质的问题,动态规划方法所耗时间往往远少于朴素解法。
动态规划主要是找到状态转移方程,一旦找到状态转移方程就找到了小问题转向大问题的方法,那么久很容易求解这个问题了,而状态转移方程往往根据题目所给的信息就能找到。


4 贪心算法
4.1 贪心算法的定义
   在求最优解问题的过程中,依据某种贪心标准,从问题的初始状态出发,直接去求每一步的最优解,通过若干次的贪心选择,最终得出整个问题的最优解,这种求解方法就是贪心算法。
从贪心算法的定义可以看出,贪心算法不是从整体上考虑问题,它所做出的选择只是在某种意义上的局部最优解,而由问题自身的特性决定了该题运用贪心算法可以得到最优解。如果一个问题可以同时用几种方法解决,贪心算法应该是最好的选择之一。
4.2 贪心算法基本思想
   从问题的某一个初始解出发,通过一系列的贪心选择-当前状态下的局部最优选择,逐步逼近给定的目标;在每个阶段,都作出一个按照某个评价函数最优的决策,这个评价函数最优称为贪心准则(类似于动态规划的状态转移方程)。
4.3 贪心算法的理论基础
    贪心算法是一种在每一步选择中都采取在当前状态下最好或最优的选择,希望得到结果是最好或最优的算法。
贪心算法是一种能够得到某种度量意义下的最优解的分级处理方法,通过一系列的选择得到一个问题的解,而它所做的每一次选择都是当前状态下某种意义的最好选择。即希望通过问题的局部最优解求出整个问题的最优解。这种策略是一种很简洁的方法,对许多问题它能产生整体最优解,但不能保证总是有效,因为它不是对所有问题都能得到整体最优解。
利用贪心策略解题,需要解决两个问题:
(1)该题是否适合于用贪心策略求解;
(2)如何选择贪心标准,以得到问题的最优/较优解。
4.4 贪心选择的性质
贪心选择性质是指所求问题的整体最优解可以通过一系列局部最优的选择,即贪心选择来达到。
这是贪心算法可行的第一个基本要素,也是贪心算法与动态规划算法的主要区别。
(1)在动态规划算法中,每步所做的选择往往依赖于相关子问题的解,因而只有在解出相关子问题后,才能做出选择。
(2)在贪心算法中,仅在当前状态下做出最好选择,即局部最优选择,然后再去解出这个选择后产生的相应的子问题。
4.5 最优子结构性质
当一个问题的最优解包含其子问题的最优解时,称此问题具有最优子结构性质。
运用贪心策略在每一次转化时都取得了最优解。问题的最优子结构性质是该问题可用贪心算法或动态规划算法求解的关键特征。
贪心算法的每一次操作都对结果产生直接影响,而动态规划则不是。贪心算法对每个子问题的解决方案都做出选择,不能回退;动态规划则会根据以前的选择结果对当前进行选择,有回退功能。
动态规划主要运用于二维或三维问题,而贪心一般是一维问题。
4.6 贪心算法学习心得
    由贪心算法的基本思想我们可以看出,在求解可以用贪心算法求解的问题时,最为关键的是找到相应的贪心策略。当然了,不同的人定的贪心策略可能不一样,但是如何找到一个好的贪心策略这才是我们应该关注的重点,好的贪心策略能使我们找到一个较优解(也可能是最优解),也能为我们写代码提供方便。


5 回溯算法
5.1 回溯法简介
    回溯法是一种组织搜索的一般技术,有“通用的解题法”之称,用它可以系统的搜索一个问题的所有解或任一解。
有许多问题,当需要找出它的解集或者要求回答什么解是满足某些约束条件的最佳解时,往往要使用回溯法。可以系统地搜索一个问题的所有解或任意解,既有系统性又有跳跃性。
回溯法的基本做法是搜索,或是一种组织得井井有条的,能避免不必要搜索的穷举式搜索法。这种以深度优先的方式系统地搜索问题的解的方法称为回溯法。
5.2 回溯算法的理论基础
    应用回溯法求解时,需要明确定义问题的解空间。问题的解空间应至少包含问题的一个(最优)解。
    在确定了解空间的组织结构后,回溯从开始结点(根结点)出发,以深度优先的方式搜索整个解空间。
5.3 回溯法的基本思想
    在回溯法搜索解空间树时,通常采用两种策略(剪枝函数)避免无效搜索以提高回溯法的搜索效率:
用约束函数在扩展结点处减去不满足约束条件的子树
用限界函数减去不能得到最优解的子树。
5.4 回溯法的分类
     有时问题是要从一个集合的所有子集中搜索一个集合,作为问题的解。或者从一个集合的排列中搜索一个排列,作为问题的解。
回溯算法可以很方便地遍历一个集合的所有子集或者所有排列。
当问题是要计算n个元素的子集,以便达到某种优化目标时,可以把这个解空间组织成一棵子集树。
例如,n个物品的0-1背包问题相应的解空间树就是一棵子集树。
这类子集树通常有2n个叶结点,结点总数为2n +1-1。
遍历子集树的任何算法,其计算时间复杂度都是Ω(2n)。
5.5 回溯法学习心得
    对于回溯法,其实跟深度优先搜索很类似,都是一直往下遍历,在一条路上搜索完毕时,才会返回到另一条,继续遍历。说白了就是遍历所有可能的解,只不过加了一些限制条件,对于一些不满足条件(减枝条件或限界条件)的解,就不再遍历。


6 分支限界算法
分支限界法是一个用途十分广泛的算法,运用这种算法的技巧性很强,不同类型的问题解法也各不相同。
6.1 分支定界法基本思想
分支限界法的基本思想是对有约束条件的最优化问题的所有可行解(数目有限)空间进行搜索。
该算法在具体执行时,把全部可行的解空间不断分割为越来越小的子集(称为分支),并为每个子集内的解的值计算一个下界或上界(称为限界)。在每次分支后,对凡是界限超出已知可行解值那些子集不再做进一步分支。这样,解的许多子集(即搜索树上的许多结点)就可以不予考虑,从而缩小了搜索范围。这一过程一直进行到找出可行解为止,该可行解的值不大于任何子集的界限。这种算法一般可以求得最优解。
6.2 分支限界算法策略
在分支限界法中,每一个活结点只有一次机会成为扩展结点。活结点一旦成为扩展结点,就一次性产生其所有儿子结点。在这些儿子结点中,导致不可行解或导致非最优解的儿子结点被舍弃,其余儿子结点被加入活结点表中。活结点表中取下一结点成为当前扩展结点,并重复上述结点扩展过程。这个过程一直持续到找到所需的解或活结点表为空时为止。
6.3 分支结点的选择
从活结点表中选择下一个活结点作为新的扩展结点,分支限界算法通常可以分为两种形式:
FIFO(First In First Out)分支限界算法:按照先进先出(FIFO)原则选择下一个活结点作为扩展结点,即从活结点表中取出结点的顺序与加入结点的顺序相同。
最小耗费或最大收益分支限界算法:在这种情况下,每个结点都有一个耗费或收益。根据问题的需要,可能是要查找一个具有最小耗费的解,或者是查找一个具有最大收益的解。
6.4 提高分支限界算法的效率
实现分支限界算法时,首先确定目标值的上下界,边搜索边减掉搜索树的某些分支,提高搜索效率。在搜索时,绝大部分需要用到剪枝。“剪枝”是搜索算法中优化程序的一种基本方法,需要通过设计出合理的判断方法,以决定某一分支的取舍。若我们把搜索的过程看成是对一棵树的遍历,那么剪枝就是将树中的一些“死结点”,不能到达最优解的枝条“剪”掉,以减少搜索的时间。
6.5 分支限界法学习心得
类似于回溯法,也是一种在问题的解空间树T上搜索问题解的算法。但在一般情况下,分支限界法与回溯法的求解目标不同。回溯法的求解目标是找出T中满足约束条件的所有解,而分支限界法的求
解目标则是找出满足约束条件的一个解,或是在满足约束条件的解中找出使某一目标函数值达到极大或极小的解,即在某种意义下的最优解。
回溯法以深度优先的方式搜索解空间树T,而分支限界法则以广度优先或以最小耗费优先的方式搜索解空间树T。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值