算法八大类算法原理与例子.docx
《算法八大类算法原理与例子.docx》由会员分享,可在线阅读,更多相关《算法八大类算法原理与例子.docx(37页珍藏版)》请在冰豆网上搜索。
算法八大类算法原理与例子
目录
算法设计与基础1
目录2
一、蛮力法3
原理3
例子平面最近的点对3
二、动态规划5
原理5
例子:
最长公共子序列6
三、分治算法9
原理9
例子棋盘覆盖9
四、贪心算法11
原理11
例子背包问题11
五、回溯法14
原理14
例子八皇后问题14
六、分支界限法16
原理16
例子装载问题16
七、时空权衡17
原理17
例子KMP字符串匹配算法18
八、变治法25
原理25
例子堆排序25
一、蛮力法
原理
顾名思义,蛮力法即是顺序往下寻找方法,直到问题的解决。
它所依赖的技术是扫描技术,关键是依次处理所有元素。
蛮力法的思想非常简单,没有很多条件的限制,比如动态划法,
必须满足最有性原理才可以使用。
它的方法上也没有缺陷,对于分治法,一旦子问题的规模不同,便不能在使用。
而蛮力法则没有这个要求。
因此,简单,易上手,是蛮力法的
基本原理。
蛮力法是我们算法中最常使用的算法,虽然巧妙和高效的算法很少来自于蛮力法,但是蛮力法依然是一种重要的算法设计技术。
在实际理论上,蛮力法可以解决可计算领域的各种问题,只是效率的高低不同而已。
因此蛮力法经常用来解决一些较小规模的问题。
蛮力法对于一些重要的问题可以产生一些合理的算法,他们具备一些实用价值,而且不受问题规模的限制。
蛮力法也可以作为某类问题时间性能的底限,来衡量同样问题的更高效算法。
例子平面最近的点对
问题:
设p1=(x1,y1),p2=(x2,y2),…,pn=(xn,yn)是平面上n个点构成的集合S,设计算法找出集合S中距离最近的点对。
蛮力算法描述:
intClosestPoints(intn,intx[],inty[]){
minDist=Double.POSITIVE_INFINITY;;
for(i=1;ifor(j=i+1;j<=n;j++)
{
d=(x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]);
if(dminDist=d;
index1=i;
index2=j;
}
}
returnminDist;
}
程序:
importjava.util.*;
publicclassClosestPair1{
publicstaticvoidmain(String[]args)
{
/**
*输入需要比较的点的对数存在变量n中
*/
Scannerin=newScanner(System.in);
System.out.println("Howmanypairsofpointstocompare?
(有多少对点需要比较?
)");
intn=in.nextInt();
int[]x=newint[n];
int[]y=newint[n];
/**
*输入这些点的横坐标和纵坐标分别存储在x[n]和y[n]
*/
System.out.println("Pleaseenterthesepoints,X-coordinate(请输入这些点,横坐标):
");
for(inti=0;i{
x[i]=in.nextInt();
}
System.out.println("Pleaseenterthesepoints,Y-coordinate(请输入这些点,纵坐标):
");
for(inti=0;i{
y[i]=in.nextInt();
}
doubleminDist=Double.POSITIVE_INFINITY;
doubled;
intindexI=0;
intindexJ=0;
/**
*求解最近对距离存于minDist中
*/
doublestartTime=System.currentTimeMillis();//startTime
for(inti=0;i{
for(intj=i+1;j{
d=Math.sqrt((x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]));
if(d{
minDist=d;
indexI=i;
indexJ=j;
}
}
}
doubleendTime=System.currentTimeMillis();//endTime
/**
*打印输出最后求出的结果,最近的是哪两个点,以及最近距离和程序用的时间
*/
System.out.println("Theclosestpairis:
("+x[indexI]+","+y[indexI]+")and("+x[indexJ]+","+y[indexJ]+")");
System.out.println("Theclosestdistanceis"+minDist);
System.out.println("BasicStatementstake(基本语句用时)"+(endTime-startTime)+"milliseconds!
");
}
}
运行:
二、动态规划
原理
动态规划算法通常用于求解具有某种最优性质的问题。
在这类问题中,可能会有许多可行解。
每一个解都对应于一个值,我们希望找到具有最优值的解。
动态规划算法与分治法类似,其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。
与分治法不同的是,适合于用动态规划求解的问题,经分解得到子问题往往不是互相独立的。
若用分治法来解这类问题,则分解得到的子问题数目太多,有些子问题被重复计算了很多次。
如果我们能够保存已解决的子问题的答案,而在需要时再找出已求得的答案,这样就可以避免大量的重复计算,节省时间。
我们可以用一个表来记录所有已解的子问题的答案。
不管该子问题以后是否被用到,只要它被计算过,就将其结果填入表中。
这就是动态规划法的基本思路。
具体的动态规划算法多种多样,但它们具有相同的填表格式。
例子:
最长公共子序列
什么是最长公共子序列呢?
举个简单的例子吧,一个数列S,若分别是两个或多个已知序列的子序列,且是所有符合条件序列中最长的,则S称为已知序列的最长公共子序列。
举例如下,如:
有两个随机数列,123456和34589,则它们的最长公共子序列便是:
345。
1、序列str1和序列str2
·长度分别为m和n;
·创建1个二维数组L[m.n];
·初始化L数组内容为0
·m和n分别从0开始,m++,n++循环:
-如果str1[m]==str2[n],则L[m,n]=L[m-1,n-1]+1;
-如果str1[m]!
=str2[n],则L[m,n]=max{L[m,n-1],L[m-1,n]}
·最后从L[m,n]中的数字一定是最大的,且这个数字就是最长公共子序列的长度
·从数组L中找出一个最长的公共子序列
2、从数组L中查找一个最长的公共子序列
i和j分别从m,n开始,递减循环直到i=0,j=0。
其中,m和n分别为两个串的长度。
·如果str1[i]==str2[j],则将str[i]字符插入到子序列内,i--,j--;
·如果str1[i]!
=str[j],则比较L[i,j-1]与L[i-1,j],L[i,j-1]大,则j--,否则i--;(如果相等,则任选一个)
图1效果演示图
根据上图,我们可以得到其中公共子串:
BCBA和BDAB。
#include
#include
usingnamespacestd;
intmain(intargc,char**argv)
{
stringstr1="ABCBDAB";
stringstr2="BDCABA";
intx_len=str1.length();
inty_len=str2.length();
intarr[50][50]={{0,0}};
inti=0;
intj=0;
for(i=1;i<=x_len;i++)
{
for(j=1;j<=y_len;j++)
{
if(str1[i-1]==str2[j-1])
{
arr[i][j]=arr[i-1][j-1]+1;
}
else
{
if(arr[i][j-1]>=arr[i-1][j])
{
arr[i][j]=arr[i][j-1];
}
else
{
arr[i][j]=arr[i-1][j];
}
}
}
}
for(i=0;i<=x_len;i++)
{
for(j=0;j<=y_len;j++)
{
cout< }
cout< }
for(i=x_len,j=y_len;i>=1&&j>=1;)
{
if(str1[i-1]==str2[j-1])
{
cout< i--;
j--;
}
else
{
// if(arr[i][j-1]>=arr[i-1][j])//打印:
BADB
if(arr[i][j-1]>arr[i-1][j])//打印:
ABCB
{
j--;
}
else
{
i--;
}
}
}
cout< return0;
}
三、分治算法
原理
基本思想
当我们求解某些问题时,由于这些问题要处理的数据相当多,或求解过程相当复杂,使得直接求解法在时间上相当长,或者根本无法直接求出。
对于这类问题,我们往往先把它分解成几个子问题,找到求出这几个子问题的解法后,再找到合适的方法,把它们组合成求整个问题的解法。
如果这些子问题还较大,难以解决,可以再把它们分成几个更小的子问题,以此类推,直至可以直接求出解为止。
这就是分治策略的基本思想。
二分法
利用分治策略求解时,所需时间取决于分解后子问题的个数、子问题的规模大小等因素,而二分法,由于其划分的简单和均匀的特点,是经常采用的一种有效的方法,例如二分法检索。
解题步骤
分治算法
解题的一般步骤:
(1)分解,将要解决的问题划分成若干规模较小的同类问题;
(2)求解,当子问题划分得足够小时,用较简单的方法解决;
(3)合并,按原问题的要求,将子问题的解逐层合并构成原问题的解。
例子棋盘覆盖
题目:
在一个(2^k)*(2^k)个方格组成的棋盘上,有一个特殊方格与其他方格不同,称为特殊方格,称这样的棋盘为一个特殊棋盘。
现在要求对棋盘的其余部分用L型方块填满(注:
L型方块由3个单元格组成。
即围棋中比较忌讳的愚形三角,方向随意),且任何两个L型方块不能重叠覆盖。
L型方块的形态如下:
题目的解法使用分治法,即子问题和整体问题具有相同的形式。
我们对棋盘做一个分割,切割一次后的棋盘如图1所示,我们可以看到棋盘被切成4个一样大小的子棋盘,特殊方块必定位于四个子棋盘中的一个。
假设如图1所示,特殊方格位于右上角,我们把一个L型方块(灰色填充)放到图中位置。
这样对于每个子棋盘又各有一个“特殊方块”,我们对每个子棋盘继续这样分割,知道子棋盘的大小为1为止。
用到的L型方块需要(4^k-1)/3个,算法的时间是O(4^k),是渐进最优解法。
本题目的C语言的完整代码如下(TC2.0下调试),运行时,先输入k的大小,(1<=k<=6),然后分别输入特殊方格所在的位置(x,y),0<=x,y<=(2^k-1)。
#include
inttitle=1;
intboard[64][64];
voidchessBoard(inttr,inttc,intdr,intdc,intsize){
ints,t;
if(size==1)return;
t=title++;s=size/2;
if(dr
chessBoard(tr,tc,dr,dc,s);
else
{board[tr+s-1][tc+s-1]=t;
chessBoard(tr,tc,tr+s-1,tc+s-1,s);
}
if(dr
|
=tc+s)chessBoard(tr,tc+s,dr,dc,s);
else{board[tr+s-1][tc+s]=t;
chessBoard(tr,tc+s,tr+s-1,tc+s,s);}
if(dr>=tr+s&&dcchessBoard(tr+s,tc,dr,dc,s);
else
{board[tr+s][tc+s-1]=t;
chessBoard(tr+s,tc,tr+s,tc+s-1,s);}
if(dr>=tr+s&&dc>=tc+s)
chessBoard(tr+s,tc+s,dr,dc,s);
else{board[tr+s][tc+s]=t;
chessBoard(tr+s,tc+s,tr+s,tc+s,s);}}
voidmain(){intdr=0,dc=0,s=1,i=0,j=0;
printf("printinthesizeofchess:
\n");
scanf("%d",&s);
printf("printinspecalpointx,y:
\n");
scanf("%d%d",&dr,&dc);
if(dr
{chessBoard(0,0,dr,dc,s);
for(i=0;i
{for(j=0;j
printf("%4d",board[i][j]);}
printf("\n");
}
}else
printf("thewrongspecalpoint!
!
\n");
getch();}
四、贪心算法
原理
贪婪算法(Greedyalgorithm)是一种对某些求最优解问题的更简单、更迅速的设计技术。
用贪婪法设计算法的特点是一步一步地进行,常以当前情况为基础根据某个优化测度作最优选择,而不考虑各种可能的整体情况,它省去了为找最优解要穷尽所有可能而必须耗费的大量时间,它采用自顶向下,以迭代的方法做出相继的贪心选择,每做一次贪心选择就将所求问题简化为一个规模更小的子问题,通过每一步贪心选择,可得到问题的一个最优解,虽然每一步上都要保证能获得局部最优解,但由此产生的全局解有时不一定是最优的,所以贪婪法不要回溯。
贪婪算法是一种改进了的分级处理方法。
其核心是根据题意选取一种量度标准。
然后将这多个输入排成这种量度标准所要求的顺序,按这种顺序一次输入一个量。
如果这个输入和当前已构成在这种量度意义下的部分最佳解加在一起不能产生一个可行解,则不把此输入加到这部分解中。
这种能够得到某种量度意义下最优解的分级处理方法称为贪婪算法。
对于一个给定的问题,往往可能有好几种量度标准。
初看起来,这些量度标准似乎都是可取的,但实际上,用其中的大多数量度标准作贪婪处理所得到该量度意义下的最优解并不是问题的最优解,而是次优解。
因此,选择能产生问题最优解的最优量度标准是使用贪婪算法的核心。
一般情况下,要选出最优量度标准并不是一件容易的事,但对某问题能选择出最优量度标准后,用贪婪算法求解则特别有效。
最优解可以通过一系列局部最优的选择即贪婪选择来达到,根据当前状态做出在当前看来是最好的选择,即局部最优解选择,然后再去解做出这个选择后产生的相应的子问题。
每做一次贪婪选择就将所求问题简化为一个规模更小的子问题,最终可得到问题的一个整体最优解。
例子背包问题
有N件物品和一个容量为V的背包。
第i件物品的重量是c[i],价值是w[i]。
求解将哪些物品装入背包可使这些物品的重量总和不超过背包容量,且价值总和最大。
基本思路
这是最基础的背包问题,特点是:
每种物品仅有一件,可以选择放或不放。
用子问题定义状态:
即f[i][v]表示前i件物品恰放入一个容量为v的背包可以获得的最大价值。
则其状态转移方程便是:
f[i][v]=max{f[i-1][v],f[i-1][v-c[i]]+w[i]}。
可以压缩空间,f[v]=max{f[v],f[v-c[i]]+w[i]}
这个方程非常重要,基本上所有跟背包相关的问题的方程都是由它衍生出来的。
所以有必要将它详细解释一下:
“将前i件物品放入容量为v的背包中”这个子问题,若只考虑第i件物品的策略(放或不放),那么就可以转化为一个只牵扯前i-1件物品的问题。
如果不放第i件物品,那么问题就转化为“前i-1件物品放入容量为v的背包中”,价值为f[i-1][v];如果放第i件物品,那么问题就转化为“前i-1件物品放入剩下的容量为v-c[i]的背包中”,此时能获得的最大价值就是f[i-1][v-c[i]]再加上通过放入第i件物品获得的价值w[i]。
注意f[v]有意义当且仅当存在一个前i件物品的子集,其费用总和为v。
所以按照这个方程递推完毕后,最终的答案并不一定是f[N][V],而是f[N][0..V]的最大值。
如果将状态的定义中的“恰”字去掉,在转移方程中就要再加入一项f[v-1],这样就可以保证f[N][V]就是最后的答案。
至于为什么这样就可以,由你自己来体会了。
空间复杂
以上方法的时间和空间复杂度均为O(N*V),其中时间复杂度基本已经不能再优化了,但空间复杂度却可以优化到O(N)。
先考虑上面讲的基本思路如何实现,肯定是有一个主循环i=1..N,每次算出来二维数组f[i][0..V]的所有值。
那么,如果只用一个数组f[0..V],能不能保证第i次循环结束后f[v]中表示的就是我们定义的状态f[i][v]呢?
f[i][v]是由f[i-1][v]和f[i-1][v-c[i]]两个子问题递推而来,能否保证在推f[v]时(也即在第i次主循环中推f[v]时)能够得到f[v]和f[v-c[i]]的值呢?
事实上,这要求在每次主循环中我们以v=V..0的顺序推f[v],这样才能保证推f[v]时f[v-c[i]]保存的是状态f[i-1][v-c[i]]的值。
伪代码如下:
fori=1..N
forv=V..0
f[v]=max{f[v],f[v-c[i]]+w[i]};
其中的f[v]=max{f[v],f[v-c[i]]}一句恰就相当于我们的转移方程f[i][v]=max{f[i-1][v],f[i-1][v-c[i]]},因为的
f[v-c[i]]就相当于原来的f[i-1][v-c[i]]。
如果将v的循环顺序从上面的逆序改成顺序的话,那么则成了f[i][v]由f[i][v-c[i]]推知,与本题意不符,但它却是另一个重要的背包问题P02最简捷的解决方案,故学习只用一维数组解01背包问题是十分必要的。
示例程序
#include"stdafx.h"
#include
usingnamespacestd;
#defineMAXSIZE1000
intf[MAXSIZE+1],c[MAXSIZE+1],w[MAXSIZE+1];
int_tmain(intargc,_TCHAR*argv[])
{
intN,V;
cin>>N>>V;
inti=1;
for(;i<=N;++i)
{
cin>>c[i]>>w[i];
}
for(i=1;i<=N;++i)
{
for(intv=V;v>=c[i];--v)//c[i]可优化为bound,bound=max{V-sumc[i,...n],c[i]}
{
f[v]=(f[v]>f[v-c[i]]+w[i]?
f[v]:
f[v-c[i]]+w[i]);
}
}
//当i=N时,可以跳出循环单独计算F[V]
cout<system("pause");
return0;
}
递归实现
//现在设A[i][v]表示在剩余空间为v时选取当前物品i的最大值,B[i][v]表示不选取当前物品i的最大值,所以总的最大值必然是max(A[n][v],B[n][v]),详细程序见如下:
//BYwanda1416
#include
#includusingnamespacestd;
#defineMAXSIZE1000
intA[MAXSIZE+1][MAXSIZE+1],B[MAXSIZE+1][MAXSIZE+1];
intc[MAXSIZE+1],w[MAXSIZE+1];
intF(intn,intv){
if(n==0)return0;
if(!
A[n][v]&&v>=c[n])
A[n][v]=F(n-1,v-c[n])+w[n];
if(!
B[n][v])B[n][v]=F(n-1,v);
returnA[n][v]>B[n][v]?
A[n][v]:
B[n][v];
}
intmain(intargc,char*argv[])
{
intn,v;
memset(A,0,sizeof(A));
memset(B,0,sizeof(B));
ifstreamin("in.txt");
o
展开阅读全文
相关搜索