五大算法.docx

上传人:b****6 文档编号:6838820 上传时间:2023-01-11 格式:DOCX 页数:19 大小:45.02KB
下载 相关 举报
五大算法.docx_第1页
第1页 / 共19页
五大算法.docx_第2页
第2页 / 共19页
五大算法.docx_第3页
第3页 / 共19页
五大算法.docx_第4页
第4页 / 共19页
五大算法.docx_第5页
第5页 / 共19页
点击查看更多>>
下载资源
资源描述

五大算法.docx

《五大算法.docx》由会员分享,可在线阅读,更多相关《五大算法.docx(19页珍藏版)》请在冰豆网上搜索。

五大算法.docx

五大算法

一、分治算法

在计算机科学中,分治法是一种很重要的算法。

字面上的解释是“分而治之”,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。

这个技巧是很多高效算法的基础,如排序算法(快速排序,归并排序),傅立叶变换(快速傅立叶变换)……

   任何一个可以用计算机求解的问题所需的计算时间都与其规模有关。

问题的规模越小,越容易直接求解,解题所需的计算时间也越少。

例如,对于n个元素的排序问题,当n=1时,不需任何计算。

n=2时,只要作一次比较即可排好序。

n=3时只要作3次比较即可,…。

而当n较大时,问题就不那么容易处理了。

要想直接解决一个规模较大的问题,有时是相当困难的。

   分治法的设计思想是,将一个难以直接解决的大问题,分割成一些规模较小的相同问题,以便各个击破,分而治之。

   分治策略是:

对于一个规模为n的问题,若该问题可以容易地解决(比如说规模n较小)则直接解决,否则将其分解为k个规模较小的子问题,这些子问题互相独立且与原问题形式相同,递归地解这些子问题,然后将各子问题的解合并得到原问题的解。

这种算法设计策略叫做分治法。

   如果原问题可分割成k个子问题,1

由分治法产生的子问题往往是原问题的较小模式,这就为使用递归技术提供了方便。

在这种情况下,反复应用分治手段,可以使子问题与原问题类型一致而其规模却不断缩小,最终使子问题缩小到很容易直接求出其解。

这自然导致递归过程的产生。

分治与递归像一对孪生兄弟,经常同时应用在算法设计之中,并由此产生许多高效算法。

   分治法所能解决的问题一般具有以下几个特征:

   1)该问题的规模缩小到一定的程度就可以容易地解决

   2)该问题可以分解为若干个规模较小的相同问题,即该问题具有最优子结构性质。

   3)利用该问题分解出的子问题的解可以合并为该问题的解;

   4)该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子子问题。

   上述的第一条特征是绝大多数问题都可以满足的,因为问题的计算复杂性一般是随着问题规模的增加而增加;第二条特征是应用分治法的前提它也是大多数问题可以满足的,此特征反映了递归思想的应用;第三条特征是关键,能否利用分治法完全取决于问题是否具有第三条特征,如果具备了第一条和第二条特征,而不具备第三条特征,则可以考虑用贪心法或动态规划法。

第四条特征涉及到分治法的效率,如果各子问题是不独立的则分治法要做许多不必要的工作,重复地解公共的子问题,此时虽然可用分治法,但一般用动态规划法较好。

   分治法的基本步骤

   分治法在每一层递归上都有三个步骤:

   分解:

将原问题分解为若干个规模较小,相互独立,与原问题形式相同的子问题;

   解决:

若子问题规模较小而容易被解决则直接解,否则递归地解各个子问题

   合并:

将各个子问题的解合并为原问题的解。

   它的一般的算法设计模式如下:

   Divide-and-Conquer(P)

   1.if|P|≤n0

   2.thenreturn(ADHOC(P))

   3.将P分解为较小的子问题P1,P2,...,Pk

   4.fori←1tok

   5.doyi←Divide-and-Conquer(Pi)△递归解决Pi

   6.T←MERGE(y1,y2,...,yk)△合并子问题

   7.return(T)

   其中|P|表示问题P的规模;n0为一阈值,表示当问题P的规模不超过n0时,问题已容易直接解出,不必再继续分解。

ADHOC(P)是该分治法中的基本子算法,用于直接解小规模的问题P。

因此,当P的规模不超过n0时直接用算法ADHOC(P)求解。

算法MERGE(y1,y2,...,yk)是该分治法中的合并子算法,用于将P的子问题P1,P2,...,Pk的相应的解y1,y2,...,yk合并为P的解。

   分治法的复杂性分析

   一个分治法将规模为n的问题分成k个规模为n/m的子问题去解。

设分解阀值n0=1,且adhoc解规模为1的问题耗费1个单位时间。

再设将原问题分解为k个子问题以及用merge将k个子问题的解合并为原问题的解需用f(n)个单位时间。

用T(n)表示该分治法解规模为|P|=n的问题所需的计算时间,则有:

         

   通过迭代法求得方程的解:

        

   递归方程及其解只给出n等于m的方幂时T(n)的值,但是如果认为T(n)足够平滑,那么由n等于m的方幂时T(n)的值可以估计T(n)的增长速度。

通常假定T(n)是单调上升的,从而当mi≤n

二、动态规划法

最优化原理

   1951年美国数学家R.Bellman等人,根据一类多阶段问题的特点,把多阶段决策问题变换为一系列互相联系的单阶段问题,然后逐个加以解决。

一些静态模型,只要人为地引进“时间”因素,分成时段,就可以转化成多阶段的动态模型,用动态规划方法去处理。

与此同时,他提出了解决这类问题的“最优化原理”(Principleofoptimality):

   “一个过程的最优决策具有这样的性质:

即无论其初始状态和初始决策如何,其今后诸策略对以第一个决策所形成的状态作为初始状态的过程而言,必须构成最优策略”。

简言之,一个最优策略的子策略,对于它的初态和终态而言也必是最优的。

   这个“最优化原理”如果用数学化一点的语言来描述的话,就是:

假设为了解决某一优化问题,需要依次作出n个决策D1,D2,…,Dn,如若这个决策序列是最优的,对于任何一个整数k,1

   最优化原理是动态规划的基础。

任何一个问题,如果失去了这个最优化原理的支持,就不可能用动态规划方法计算。

能采用动态规划求解的问题都需要满足一定的条件:

   

(1)问题中的状态必须满足最优化原理;

   

(2)问题中的状态必须满足无后效性。

   所谓的无后效性是指:

“下一时刻的状态只与当前状态有关,而和当前状态之前的状态无关,当前的状态是对以往决策的总结”。

   问题求解模式

   动态规划所处理的问题是一个多阶段决策问题,一般由初始状态开始,通过对中间阶段决策的选择,达到结束状态。

这些决策形成了一个决策序列,同时确定了完成整个过程的一条活动路线(通常是求最优的活动路线)。

如图所示。

动态规划的设计都有着一定的模式,一般要经历以下几个步骤。

   初始状态→│决策1│→│决策2│→…→│决策n│→结束状态

   图1动态规划决策过程示意图

   

(1)划分阶段:

按照问题的时间或空间特征,把问题分为若干个阶段。

在划分阶段时,注意划分后的阶段一定要是有序的或者是可排序的,否则问题就无法求解。

   

(2)确定状态和状态变量:

将问题发展到各个阶段时所处于的各种客观情况用不同的状态表示出来。

当然,状态的选择要满足无后效性。

   (3)确定决策并写出状态转移方程:

因为决策和状态转移有着天然的联系,状态转移就是根据上一阶段的状态和决策来导出本阶段的状态。

所以如果确定了决策,状态转移方程也就可写出。

但事实上常常是反过来做,根据相邻两段各状态之间的关系来确定决策。

   (4)寻找边界条件:

给出的状态转移方程是一个递推式,需要一个递推的终止条件或边界条件。

   算法实现

   动态规划的主要难点在于理论上的设计,也就是上面4个步骤的确定,一旦设计完成,实现部分就会非常简单。

使用动态规划求解问题,最重要的就是确定动态规划三要素:

问题的阶段,每个阶段的状态以及从前一个阶段转化到后一个阶段之间的递推关系。

递推关系必须是从次小的问题开始到较大的问题之间的转化,从这个角度来说,动态规划往往可以用递归程序来实现,不过因为递推可以充分利用前面保存的子问题的解来减少重复计算,所以对于大规模问题来说,有递归不可比拟的优势,这也是动态规划算法的核心之处。

确定了动态规划的这三要素,整个求解过程就可以用一个最优决策表来描述,最优决策表是一个二维表,其中行表示决策的阶段,列表示问题状态,表格需要填写的数据一般对应此问题的在某个阶段某个状态下的最优值(如最短路径,最长公共子序列,最大价值等),填表的过程就是根据递推关系,从1行1列开始,以行或者列优先的顺序,依次填写表格,最后根据整个表格的数据通过简单的取舍或者运算求得问题的最优解。

下面分别以求解最大化投资回报问题和最长公共子序列问题为例阐述用动态规划算法求解问题的一般思路。

1.最长公共子串问题的实现

最长公共子串问题:

一个给定序列的子序列是在该序列中删去若干元素后得到的序列。

给定两个序列X和Y,当另一序列Z既是X的子序列又是Y的子序列时,称Z是序列X和Y的公共子序列。

最长公共子串就是求给定两个序列的一个最长公共子序列。

例如,X=“ABCBDAB”,Y=“BCDB”是X的一个子序列。

   问题分析:

   给定两个序列A和B,称序列Z是A和B的公共子序列,是指Z同是A和B的子序列。

问题要求已知两序列A和B的最长公共子序列。

如采用列举A的所有子序列,并一一检查其是否又是B的子序列,并随时记录所发现的子序列,最终求出最长公共子序列。

这种方法因耗时太多而不可取。

   考虑最长公共子序列问题如何分解成子问题,设A=“a0,a1,…,am-1”,B=“b0,b1,…,bm-1”,并Z=“z0,z1,…,zk-1”为它们的最长公共子序列。

不难证明有以下性质:

   

(1)如果am-1=bn-1,则zk-1=am-1=bn-1,且“z0,z1,…,zk-2”是“a0,a1,…,am-2”和“b0,b1,…,bn-2”的一个最长公共子序列;

   

(2)如果am-1!

=bn-1,则若zk-1!

=am-1,蕴涵“z0,z1,…,zk-1”是“a0,a1,…,am-2”和“b0,b1,…,bn-1”的一个最长公共子序列;

   (3)如果am-1!

=bn-1,则若zk-1!

=bn-1,蕴涵“z0,z1,…,zk-1”是“a0,a1,…,am-1”和“b0,b1,…,bn-2”的一个最长公共子序列。

   这样,在找A和B的公共子序列时,如有am-1=bn-1,则进一步解决一个子问题,找“a0,a1,…,am-2”和“b0,b1,…,bm-2”的一个最长公共子序列;如果am-1!

=bn-1,则要解决两个子问题,找出“a0,a1,…,am-2”和“b0,b1,…,bn-1”的一个最长公共子序列和找出“a0,a1,…,am-1”和“b0,b1,…,bn-2”的一个最长公共子序列,再取两者中较长者作为A和B的最长公共子序列。

   为了节约重复求相同子问题的时间,引入一个数组,不管它们是否对最终解有用,把所有子问题的解存于该数组中,这就是动态规划法所采用的基本方法,具体说明如下。

   定义c[i][j]为序列“a0,a1,…,ai-2”和“b0,b1,…,bj-1”的最长公共子序列的长度,计算c[i][j]可递归地表述如下:

   

(1)c[i][j]=0                        如果i=0或j=0;

   

(2)c[i][j]=c[i-1][j-1]+1            如果i,j>0,且a[i-1]=b[j-1];

   (3)c[i][j]=max{c[i][j-1],c[i-1][j]}如果i,j>0,且a[i-1]!

=b[j-1]。

   按此算式可写出计算两个序列的最长公共子序列的长度函数。

由于c[i][j]的产生仅依赖于c[i-1][j-1]、c[i-1][j]和c[i][j-1],故可以从c[m][n]开始,跟踪c[i][j]的产生过程,逆向构造出最长公共子序列。

细节见程序。

   #include

   #include

   #defineN100

   chara[N],b[N],str[N];

   intc[N][N];

   intlcs_len(char*a,char*b,intc[][N])

   {

   intm=strlen(a),n=strlen(b),i,j;

   for(i=0;i<=m;i++)

   c[i][0]=0;

   for(i=0;i<=n;i++)

   c[0][i]=0;

   for(i=1;i<=m;i++)

   {

   for(j=1;j<=n;j++)

   {

   if(a[i-1]==b[j-1])

   c[i][j]=c[i-1][j-1]+1;

   elseif(c[i-1][j]>=c[i][j-1])

   c[i][j]=c[i-1][j];

   else

   c[i][j]=c[i][j-1];

   }

   }

   returnc[m][n];

   }

   char*build_lcs(chars[],char*a,char*b)

   {

   inti=strlen(a),j=strlen(b);

   intk=lcs_len(a,b,c);

   s[k]='\0';

   while(k>0)

   {

   if(c[i][j]==c[i-1][j])

   i--;

   elseif(c[i][j]==c[i][j-1])

   j--;

   else

   {

   s[--k]=a[i-1];

   i--;j--;

   }

   }

   returns;

   }

   voidmain()

   {

   printf("Entertwostring(length<%d):

\n",N);

scanf("%s%s",a,b);

printf("LCS=%s\n",build_lcs(str,a,b));

}

2.用动态规划实现导弹拦截

某国为了防御敌国的导弹袭击,开发出一种导弹拦截系统。

但是这种导弹拦截系统有一个缺陷:

虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。

某天,雷达捕捉到敌国的导弹来袭。

由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。

   输入导弹依次飞来的高度(雷达给出的高度数据是不大于30000的正整数),计算这套系统最多能拦截多少导弹,并依次输出被拦截的导弹飞来时候的高度。

   SAMPLEINPUT:

   38920715530029917015865

   SAMPLEOUTPUT:

   6

   38930029917015865

   因为只有一套导弹拦截系统,并且这套系统除了第一发炮弹能到达任意高度外,以后的每一发炮弹都不能高于前一发炮弹的高度;所以,被拦截的导弹应该按飞来的高度组成一个非递增序列。

题目要求我们计算这套系统最多能拦截的导弹数,并依次输出被拦截导弹的高度,实际上就是要求我们在导弹依次飞来的高度序列中寻找一个最长非递增子序列。

   设X={x1,x2,…,xn}为依次飞来的导弹序列,Y={y1,y2,…,yk}为问题的最优解(即X的最长非递增子序列),s为问题的状态(表示导弹拦截系统当前发送炮弹能够到达的最大高度,初值为s=∞——第一发炮弹能够到达任意的高度)。

如果y1=x1,即飞来的第一枚导弹被成功拦截。

那么,根据题意“每一发炮弹都不能高于前一发的高度”,问题的状态将由s=∞变成s≤x1(x1为第一枚导弹的高度);在当前状态下,序列Y1={y2,…,yk}也应该是序列X1={x2,…,xn}的最长非递增子序列(大家用反证法很容易证明)。

也就是说,在当前状态s≤x1下,问题的最优解Y所包含的子问题(序列X1)的解(序列Y1)也是最优的。

这就是拦截导弹问题的最优子结构性质。

   设D(i)为第i枚导弹被拦截之后,这套系统最多还能拦截的导弹数(包含被拦截的第i枚)。

我们可以设想,当系统拦截了第k枚导弹xk,而xk又是序列X={x1,x2,…,xn}中的最小值,即第k枚导弹为所有飞来的导弹中高度最低的,则有D(k)=1;当系统拦截了最后一枚导弹xn,那么,系统最多也只能拦截这一枚导弹了,即D(n)=1;其它情况下,也应该有D(i)≥1。

   假设系统最多能拦截的导弹数为dmax(即问题的最优值),则dmax=max(D(i))

   所以,要计算问题的最优值dmax,需要分别计算出D

(1)、D

(2)、……D(n)的值,然后将它们进行比较,找出其中的最大值。

根据上面分析出来的递归方程,我们完全可以设计一个递归函数,采用自顶向下的方法计算D(i)的值。

然后,对i从1到n分别调用这个递归函数,就可以计算出D

(1)、D

(2)、……D(n)。

但这样将会有大量的子问题被重复计算。

比如在调用递归函数计算D

(1)的时候,可能需要先计算D(5)的值;之后在分别调用递归函数计算D

(2)、D(3)、D(4)的时候,都有可能需要先计算D(5)的值。

如此一来,在整个问题的求解过程中,D(5)可能会被重复计算很多次,从而造成了冗余,降低了程序的效率。

   其实,通过以上分析,我们已经知道:

D(n)=1。

如果将n作为阶段对问题进行划分,根据问题的动态规划递归方程,我们可以采用自底向上的方法依次计算出D(n-1)、D(n-2)、……D

(1)的值。

这样,每个D(i)的值只计算一次,并在计算的同时把计算结果保存下来,从而避免了有些子问题被重复计算的情况发生,提高了程序的效率。

算法代码如下:

   for(i=n-2;i>=0;i--){             //从后往前计算d[i]值

   for(j=i+1;j

   if((array[j]<=array[i])&&(d[i]

   d[i]=d[j]+1;

   }

   }

   }

   dmax=0;

   xh=1;

   for(i=0;i

   if(d[i]>dmax){

   dmax=d[i];

   xh=i;

   }

   }

   cout<

   cout<

   inttemp=xh;

   for(i=xh+1;i

   if(d[i]==d[temp]-1){

   temp=i;

   cout<

   }

   }

   cout<

3.最大化投资回报问题的实现

最大化投资回报问题:

某人有一定的资金用来购买不同面额的债卷,不同面额债卷的年收益是不同的,求给定资金,年限以及债卷面额、收益的情况下怎样购买才能使此人获得最大投资回报。

   程序输入约定:

第一行第一列表示资金(1000的倍数)总量,第二列表示投资年限;第二行表示债卷面额总数;从第三行开始每行表示一种债卷,占用两列,前一列表示债卷面额,后一列表示其年收益,如下输入实例,

   100001

   2

   4000400

   3000250

   程序实现如下,注释几乎说明了一切,所以不再另外分析。

   ///此数组是算法的关键存储结构,用来存储不同阶段各种债卷

   ///组合下对应可获取的最大利息。

   intsaifa[80005];

   ///此函数用于计算当前债卷在不同购买额下的最优利息情况,

   ///注意此时的利息情况是基于上一次债卷的情况下计算得到的,

   ///也就是说当前利息最优是基于上一次利息最优的基础上计算出来的,

   ///这也正好体现了动态规划中“最优化原则”:

不管前面的策略如何,

   ///此后的决策必须是基于当前状态(由上一次决策产生)的最优决策。

   /*

   动态规划的求解过程一般都可以用一个最优决策表来描述,

   对于本程序,以示例输入为例,对于第一年,其最优决策表如下:

   0123  4  5  6  7  8  9  10(*1000) --

(1)

   0000  400400400400800800800       --

(2)

   000250400400500650800900900       --(3)

   

(1)--表示首先选利息为400的债卷在对应资金下的最优利息。

   

(2)--表示可用来购买债卷的资金。

   (3)--表示在已有状态下再选择利息为300的债卷在对应资金下的最优利息。

   注意上面表格,在求购买利息为300的债卷获得的最优收益的时候,

   参考了以前的最优状态,以3行8列的650为例,7(*1000)可以

   在以前购买了0张4000的债卷的基础上再2张3000的,也可以在以前购

   买了1张4000的基础上再买1张3000,经比较取其收益大的,这就是典

   型的动态规划中的当前最优状态计算。

   本程序中把上面的最优决策二维表用一个一维数组表示,值得借鉴。

   */

   voidadd(inta,intb)

   {cout<

   for(inti=0;i<=80000;i++)

   {

   if(i+a>80000)

   {

   break;

   }

   if(saifa[i]+b>saifa[i+a])//累计同时购买多种债卷时的利息

   {

   saifa[i+a]=saifa[i]+b;

   }

   if(i<200)//fordebug

   cout<

   }

   cout<

   }

   intmain(void)

   {

   intn,d,money,year,pay,bond;

   intii,i;

   sca

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 幼儿教育 > 幼儿读物

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1