参考出处:http://blog.csdn.net/ctu_85/archive/2008/05/11/2432736.aspx
一、什么是法
法是一pd解决问题的清晰指令,也就是说Q能够对一定规范的输入Q在有限旉内获得所要求的输出。算法常常含有重复的步骤和一些比较或逻辑判断。如果一个算法有~陷Q或不适合于某个问题,执行q个法不会解册个问题。不同的法可能用不同的旉、空间或效率来完成同Ld。一个算法的优劣可以用空间复杂度与时间复杂度来衡量?br />
法的时间复杂度是指法需要消耗的旉资源。一般来_计算机算法是问题规模n 的函数f(n)Q算法执行的旉的增长率与f(n) 的增长率正相养IUC渐进旉复杂度(Asymptotic Time ComplexityQ。时间复杂度?#8220;OQ数量Q?#8221;来表C,UCؓ“?#8221;。常见的旉复杂度有Q?OQ?Q常数阶QOQlog2nQ对数阶QOQnQ线性阶QOQn2Q^斚w?br />
法的空间复杂度是指法需要消耗的I间资源。其计算和表C方法与旉复杂度类|一般都用复杂度的渐q性来表示。同旉复杂度相比,I间复杂度的分析要简单得多?br />
二、算法设计的Ҏ
1.递推?br />
递推法是利用问题本n所h的一U递推关系求问题解的一U方法。设要求问题规模为N的解Q当N=1Ӟ解或为已知,或能非常方便地得到解。能采用递推法构造算法的问题有重要的递推性质Q即当得到问题规模ؓi-1的解后,由问题的递推性质Q能从已求得的规模ؓ1Q?Q?#8230;Qi-1的一pd解,构造出问题规模为I的解。这PE序可从i=0或i=1出发Q重复地Q由已知至i-1规模的解Q通过递推Q获得规模ؓi的解Q直臛_到规模ؓN的解?
【问题?阶乘计算
问题描述Q编写程序,对给定的nQn?00Q,计算q输出k的阶乘kQ(k=1Q?Q?#8230;QnQ的全部有效数字?
׃要求的整数可能大大超Z般整数的位数Q程序用一l数l存储长整数Q存储长整数数组的每个元素只存储长整数的一位数字。如有m位成整数N用数la[ ]存储Q?
N=a[m]×10m-1+a[m-1]×10m-2+ … +a[2]×101+a[1]×100
q用a[0]存储长整数N的位数mQ即a[0]=m。按上述U定Q数l的每个元素存储k的阶乘kQ的一位数字,q从低位到高位依ơ存于数l的W二个元素、第三个元素……。例如,5Q?120Q在数组中的存储形式为:
3 0 2 1 ……
首元?表示长整数是一?位数Q接着是低位到高位依次???Q表C成整数120?
计算阶乘kQ可采用对已求得的阶?k-1)Q连l篏加k-1ơ后求得。例如,已知4Q?24Q计?Q,可对原来?4累加4?4后得?20。细节见以下E序?
# include <stdio.h>
# include <malloc.h>
......
2.递归
递归是设计和描述法的一U有力的工具Q由于它在复杂算法的描述中被l常采用Qؓ此在q一步介l其他算法设计方法之前先讨论它?
能采用递归描述的算法通常有这L特征Qؓ求解规模为N的问题,设法它分解成规模较的问题Q然后从q些问题的解方便地构造出大问题的解,q且q些规模较小的问题也能采用同L分解和综合方法,分解成规模更的问题Qƈ从这些更问题的解构造出规模较大问题的解。特别地Q当规模N=1Ӟ能直接得解?
【问题?~写计算斐L那契QFibonacciQ数列的Wn函数fibQnQ?
斐L那契数列为:0?????#8230;…Q即Q?
fib(0)=0;
fib(1)=1;
fib(n)=fib(n-1)+fib(n-2) Q当n>1Ӟ?
写成递归函数有:
int fib(int n)
{ if (n==0) return 0;
if (n==1) return 1;
if (n>1) return fib(n-1)+fib(n-2);
}
递归法的执行过E分递推和回归两个阶Dc在递推阶段Q把较复杂的问题Q规模ؓnQ的求解推到比原问题单一些的问题Q规模小于nQ的求解。例如上例中Q求解fib(n)Q把它推到求解fib(n-1)和fib(n-2)。也是_fib(n)Q必d计算fib(n-1)和fib(n-2)Q而计fib(n-1)和fib(n-2)Q又必须先计fib(n-3)和fib(n-4)。依ơ类推,直至计算fib(1)和fib(0)Q分别能立即得到l果1?。在递推阶段Q必要有终止递归的情c例如在函数fib中,当n??的情c?
在回归阶D,当获得最单情늚解后Q逐q回Q依ơ得到稍复杂问题的解Q例如得到fib(1)和fib(0)后,q回得到fib(2)的结果,……Q在得到了fib(n-1)和fib(n-2)的结果后Q返回得到fib(n)的结果?
在编写递归函数时要注意Q函C的局部变量和参数知识局限于当前调用层,当递推q入“单问?#8221;层时Q原来层ơ上的参数和局部变量便被隐蔽v来。在一pd“单问?#8221;层,它们各有自己的参数和局部变量?
׃递归引v一pd的函数调用,q且可能会有一pd的重复计,递归法的执行效率相对较低。当某个递归法能较方便地{换成递推法Ӟ通常按递推法~写E序。例如上例计斐波那契数列的Wn的函数fib(n)应采用递推法Q即从斐波那契数列的前两出发,逐次由前两项计算Z一,直至计算求的WnV?
【问题?l合问题
问题描述Q找Z自然???#8230;…、n中Q取r个数的所有组合。例如n=5Qr=3的所有组合ؓQ?Q?Q??? Q?Q??? Q?Q???
Q?Q??? Q?Q??? Q?Q???
Q?Q??? Q?Q??? Q?Q???
Q?0Q???
分析所列的10个组合,可以采用q样的递归思想来考虑求组合函数的法。设函数为void comb(int m,int k)为找Z自然???#8230;…、m中Q取k个数的所有组合。当l合的第一个数字选定Ӟ其后的数字是从余下的m-1个数中取k-1数的l合。这将求m个数中取k个数的组合问题{化成求m-1个数中取k-1个数的组合问题。设函数引入工作数组a[ ]存放求出的组合的数字Q约定函数将定的k个数字组合的W一个数字放在a[k]中,当一个组合求出后Q才a[ ]中的一个组合输出。第一个数可以是m、m-1?#8230;…、kQ函数将定l合的第一个数字放入数l后Q有两种可能的选择Q因q未去顶l合的其余元素,l箋递归ȝ定;或因已确定了l合的全部元素,输出q个l合。细节见以下E序中的函数comb?
【程序?
# include <stdio.h>
# define MAXN 100
int a[MAXN];
void comb(int m,int k)
{ int i,j;
for (i=m;i>=k;i--)
{ a[k]=i;
if (k>1)
comb(i-1,k-1);
else
{ for (j=a[0];j>0;j--)
printf(“%4d”,a[j]);
printf(“\n”);
}
}
}
void main()
{ a[0]=3;
comb(5,3);
}
3.回溯?br />
回溯法也UCؓ试探法,该方法首先暂时放弃关于问题规模大的限制Qƈ问题的候选解按某U顺序逐一枚D和检验。当发现当前候选解不可能是解时Q就选择下一个候选解Q倘若当前候选解除了q不满问题规模要求外,满所有其他要求时Ql扩大当前候选解的规模,ql试探。如果当前候选解满包括问题规模在内的所有要求时Q该候选解是问题的一个解。在回溯法中Q放弃当前候选解Q寻找下一个候选解的过E称为回溯。扩大当前候选解的规模,以l试探的q程UCؓ向前试探?
【问题?l合问题
问题描述Q找Z自然?Q?Q?#8230;Qn中Q取r个数的所有组合?
采用回溯法找问题的解Q将扑ֈ的组合以从小到大序存于a[0]Qa[1]Q?#8230;Qa[r-1]中,l合的元素满以下性质Q?
Q?Q?a[i+1]>aQ后一个数字比前一个大Q?
Q?Q?a-i<=n-r+1?
按回溯法的思想Q找解过E可以叙q如下:
首先攑ּl合CCؓr的条Ӟ候选组合从只有一个数?开始。因该候选解满除问题规模之外的全部条gQ扩大其规模Qƈ使其满上述条gQ?Q,候选组合改?Q?。l这一q程Q得到候选组?Q?Q?。该候选解满包括问题规模在内的全部条Ӟ因而是一个解。在该解的基上,选下一个候选解Q因a[2]上的3调整?Q以及以后调整ؓ5都满问题的全部要求Q得到解1Q?Q??Q?Q?。由于对5不能再作调整Q就要从a[2]回溯到a[1]Q这Ӟa[1]=2Q可以调整ؓ3Qƈ向前试探Q得到解1Q?Q?。重复上q向前试探和向后回溯Q直臌从a[0]再回溯时Q说明已l找完问题的全部解。按上述思想写成E序如下Q?
【程序?
# define MAXN 100
int a[MAXN];
void comb(int m,int r)
{ int i,j;
i=0;
a=1;
do {
if (a-i<=m-r+1
{ if (i==r-1)
{ for (j=0;j<r;j++)
printf(“%4d”,a[j]);
printf(“\n”);
}
a++;
continue;
}
else
{ if (i==0)
return;
a[--i]++;
}
} while (1)
}
main()
{ comb(5,3);
}
4.贪婪?br />
贪婪法是一U不q求最优解Q只希望得到较ؓ满意解的Ҏ。贪婪法一般可以快速得到满意的解,因ؓ它省M为找最优解要穷所有可能而必耗费的大量时间。贪婪法总当前情况为基作最优选择Q而不考虑各种可能的整体情况,所以贪婪法不要回溯?
例如qx购物NӞZ扑֛的零q币数最,不考虑Nq所有各U发表方案,而是从最大面值的币种开始,按递减的顺序考虑各币U,先尽量用大面值的币种Q当不大面值币U的金额时才去考虑下一U较面值的币种。这是在用贪婪法。这U方法在q里L最优,是因为银行对其发行的币U类和硬币面值的巧妙安排。如只有面值分别ؓ1??1单位的硬币,而希望找回总额?5单位的硬币。按贪婪法Q应??1单位面值的币??单位面值的币Q共扑֛5个硬币。但最优的解应??单位面值的币?
【问题?装箱问题
问题描述Q装问题可q如下:设有~号???#8230;、n-1的nU物品,体积分别为v0、v1?#8230;、vn-1。将qnU物品装到容量都为V的若q箱子里。约定这nU物品的体积均不过VQ即对于0≤iQnQ有0Qvi≤V。不同的装箱Ҏ所需要的子数目可能不同。装问题要求装尽qnU物品的子数要?
若考察nU物品的集合分划成n个或于n个物品的所有子集,最优解可以找到。但所有可能划分的L太大。对适当大的nQ找出所有可能的划分要花费的旉是无法承受的。ؓ此,对装问题采用非常简单的q似法Q即贪婪法。该法依次物品放到它W一个能放进ȝ子中,该算法虽不能保证扑ֈ最优解Q但q是能找到非常好的解。不׃般性,设n件物品的体积是按从大到小排好序的Q即有v0≥v1≥…≥vn-1。如不满上q要求,只要先对qn件物品按它们的体U从大到排序,然后按排序结果对物品重新~号卛_。装q法简单描q如下:
{ 输入子的容U;
输入物品U数nQ?
按体U从大到顺序,输入各物品的体积Q?
预置已用子链ؓI;
预置已用子计数器box_count?Q?
for (i=0;i<n;i++)
{ 从已用的W一只箱子开始顺序寻找能攑օ物品i 的箱子jQ?
if Q已用箱子都不能再放物品iQ?
{ 另用一个箱子,q将物品i攑օ该箱子;
box_count++Q?
}
else
物品i攑օ子jQ?
}
}
上述法能求出需要的子数box_countQƈ能求出各子所装物品。下面的例子说明该算法不一定能扑ֈ最优解Q设?U物品,它们的体U分别ؓQ?0?5?5?0?0?0单位体积Q箱子的容积?00个单位体U。按上述法计算Q需三只子Q各子所装物品分别ؓQ第一只箱子装物品1?Q第二只子装物???Q第三只子装物?。而最优解Z只箱子,分别装物???????
若每只箱子所装物品用链表来表C,链表首结Ҏ针存于一个结构中Q结构记录尚剩余的空间量和该子所装物品链表的首指针。另全部箱子的信息也构成链表。以下是按以上算法编写的E序?
}
5.分治?br />
M一个可以用计算机求解的问题所需的计时间都与其规模N有关。问题的规模小Q越Ҏ直接求解Q解题所需的计时间也少。例如,对于n个元素的排序问题Q当n=1Ӟ不需M计算Qn=2Ӟ只要作一ơ比较即可排好序Qn=3时只要作3ơ比较即可,…。而当n较大Ӟ问题׃那么Ҏ处理了。要想直接解决一个规模较大的问题Q有时是相当困难的?
分治法的设计思想是,一个难以直接解决的大问题,分割成一些规模较的相同问题Q以便各个击_分而治之?
如果原问题可分割成k个子问题Q?<k≤nQ,且这些子问题都可解,q可利用q些子问题的解求出原问题的解Q那么这U分L是可行的。由分治法生的子问题往往是原问题的较模式,q就Z用递归技术提供了方便。在q种情况下,反复应用分治手段Q可以子问题与原问题类型一致而其规模却不断羃,最l子问题羃到很容易直接求出其解。这自然D递归q程的生。分M递归像一对孪生兄弟,l常同时应用在算法设计之中,q由此生许多高效算法?
分治法所能解决的问题一般具有以下几个特征:
Q?Q该问题的规模羃到一定的E度可以容易地解决Q?
Q?Q该问题可以分解q个规模较小的相同问题,卌问题h最优子l构性质Q?
Q?Q利用该问题分解出的子问题的解可以合qؓ该问题的解;
Q?Q该问题所分解出的各个子问题是怺独立的,卛_问题之间不包含公q子子问题?
上述的第一条特征是l大多数问题都可以满的Q因为问题的计算复杂性一般是随着问题规模的增加而增加;W二条特征是应用分治法的前提Q它也是大多数问题可以满的Q此特征反映了递归思想的应用;W三条特征是关键Q能否利用分L完全取决于问题是否具有第三条特征Q如果具备了W一条和W二条特征,而不具备W三条特征,则可以考虑贪心法或动态规划法。第四条特征涉及到分L的效率,如果各子问题是不独立的,则分L要做许多不必要的工作Q重复地解公q子问题,此时虽然可用分治法,但一般用动态规划法较好?
分治法在每一层递归上都有三个步骤:
Q?Q分解:原问题分解q个规模较小Q相互独立,与原问题形式相同的子问题Q?
Q?Q解冻I若子问题规模较小而容易被解决则直接解Q否则递归地解各个子问题;
Q?Q合qӞ各个子问题的解合ƈ为原问题的解?
6.动态规划法
l常会遇到复杂问题不能简单地分解成几个子问题Q而会分解Zpd的子问题。简单地采用把大问题分解成子问题Qƈl合子问题的解导出大问题的解的方法,问题求解耗时会按问题规模呈幂U数增加?
Z节约重复求相同子问题的时_引入一个数l,不管它们是否Ҏl解有用Q把所有子问题的解存于该数l中Q这是动态规划法所采用的基本方法。以下先用实例说明动态规划方法的使用?
【问题?求两字符序列的最长公共字W子序列
问题描述Q字W序列的子序列是指从l定字符序列中随意地Q不一定连l)L若干个字W(可能一个也不去掉)后所形成的字W序列。ol定的字W序列X=“x0Qx1Q?#8230;Qxm-1”Q序列Y=“y0Qy1Q?#8230;Qyk-1”是X的子序列Q存在X的一个严格递增下标序列<i0Qi1Q?#8230;Qik-1>Q得对所有的j=0Q?Q?#8230;Qk-1Q有xij=yj。例如,X=“ABCBDAB”QY=“BCDB”是X的一个子序列?
考虑最长公共子序列问题如何分解成子问题Q设A=“a0Qa1Q?#8230;Qam-1”QB=“b0Qb1Q?#8230;Qbm-1”QƈZ=“z0Qz1Q?#8230;Qzk-1”为它们的最长公共子序列。不难证明有以下性质Q?
Q?Q?如果am-1=bn-1Q则zk-1=am-1=bn-1Q且“z0Qz1Q?#8230;Qzk-2”?#8220;a0Qa1Q?#8230;Qam-2”?#8220;b0Qb1Q?#8230;Qbn-2”的一个最长公共子序列Q?
Q?Q?如果am-1!=bn-1Q则若zk-1!=am-1Q蕴?#8220;z0Qz1Q?#8230;Qzk-1”?#8220;a0Qa1Q?#8230;Qam-2”?#8220;b0Qb1Q?#8230;Qbn-1”的一个最长公共子序列Q?
Q?Q?如果am-1!=bn-1Q则若zk-1!=bn-1Q蕴?#8220;z0Qz1Q?#8230;Qzk-1”?#8220;a0Qa1Q?#8230;Qam-1”?#8220;b0Qb1Q?#8230;Qbn-2”的一个最长公共子序列?
q样Q在找A和B的公共子序列Ӟ如有am-1=bn-1Q则q一步解决一个子问题Q找“a0Qa1Q?#8230;Qam-2”?#8220;b0Qb1Q?#8230;Qbm-2”的一个最长公共子序列Q如果am-1!=bn-1Q则要解决两个子问题Q找?#8220;a0Qa1Q?#8230;Qam-2”?#8220;b0Qb1Q?#8230;Qbn-1”的一个最长公共子序列和找?#8220;a0Qa1Q?#8230;Qam-1”?#8220;b0Qb1Q?#8230;Qbn-2”的一个最长公共子序列Q再取两者中较长者作为A和B的最长公共子序列?
代码如下Q?br />
# include <stdio.h>
# include <string.h>
# define N 100
char a[N],b[N],str[N];
int lcs_len(char *a, char *b, int c[ ][ N])
{ int m=strlen(a), n=strlen(b), i,j;
for (i=0;i<=m;i++) c[0]=0;
for (i=0;i<=n;i++) c[0]=0;
for (i=1;i<=m;i++)
for (j=1;j<=m;j++)
if (a[i-1]==b[j-1])
c[j]=c[i-1][j-1]+1;
else if (c[i-1][j]>=c[j-1])
c[j]=c[i-1][j];
else
c[j]=c[j-1];
return c[m][n];
}
char *buile_lcs(char s[ ],char *a, char *b)
{ int k, i=strlen(a), j=strlen(b);
k=lcs_len(a,b,c);
s[k]=’’;
while (k>0)
if (c[j]==c[i-1][j]) i--;
else if (c[j]==c[j-1]) j--;
else { s[--k]=a[i-1];
i--; j--;
}
return s;
}
void main()
{ printf (“Enter two stringQ?lt;%dQ?\n”,N);
scanf(“%s%s”,a,b);
printf(“LCS=%s\n”,build_lcs(str,a,b));
}
7.q代?br />
q代法是用于求方E或方程l近似根的一U常用的法设计Ҏ。设方程为f(x)=0Q用某种数学Ҏ导出{h的Ş式x=g(x)Q然后按以下步骤执行Q?
Q?Q?选一个方E的q似根,赋给变量x0Q?
Q?Q?x0的g存于变量x1Q然后计g(x1)Qƈ结果存于变量x0Q?
Q?Q?当x0与x1的差的绝对D于指定的精度要求时Q重复步骤(2Q的计算?
若方E有根,q且用上q方法计出来的q似根序列收敛,则按上述Ҏ求得的x0p为是方程的根。上q算法用CE序的Ş式表CZؓQ?
E序如下Q?
【算法】P代法求方E组的根
{ for (i=0;i<n;i++)
x=初始q似?
do {
for (i=0;i<n;i++)
y = x;
for (i=0;i<n;i++)
x = gi(X);
for (delta=0.0,i=0;i<n;i++)
if (fabs(y-x)>delta) delta=fabs(y-x)Q?} while (delta>Epsilon)Q?
for (i=0;i<n;i++)
printf(“变量x[%d]的近似根?%f”QIQx)Q?
printf(“\n”)Q?
} 具体使用q代法求Ҏ应注意以下两U可能发生的情况Q?
Q?Q如果方E无解,法求出的近似根序列׃会收敛,q代q程会变成死循环Q因此在使用q代法前应先考察方程是否有解Qƈ在程序中对P代的ơ数l予限制Q?
Q?Q方E虽然有解,但P代公式选择不当Q或q代的初始近似根选择不合理,也会Dq代p|?br />
8.ID搜烦?br />
ID搜烦法是对可能是解的众多候选解按某U顺序进行逐一枚D和检验,q从众找出那些符合要求的候选解作ؓ问题的解?
【问题?A、B、C、D、E、Fq六个变量排成如图所C的三角形,q六个变量分别取[1Q?]上的整数Q且均不相同。求使三角Ş三条边上的变量之和相{的全部解。如囑ְ是一个解?
E序引入变量a、b、c、d、e、fQƈ让它们分别顺序取1?的整敎ͼ在它们互不相同的条g下,试由它们排成的如图所C的三角形三条边上的变量之和是否相等Q如相等即ؓ一U满求的排列Q把它们输出。当q些变量取尽所有的l合后,E序可得到全部可能的解。程序如下:
按穷举法~写的程序通常不能适应变化的情c如问题Ҏ?个变量排成三角ŞQ每条边?个变量的情况Q程序的循环重数p相应改变?/div>