(一)分治法
概念:将一个难以直接解决的大问题,分割成一些规模较小的几个相似问题,以便各个击破,分而治之。
一般算法设计模式:
Divide-and-Conquer(n) //n为问题规模
{
if (n<=n0) //n0为可解子问题的规模
{解子问题;
return(子问题的解);}
for(i=1;i<=k;i++) //分解为较小的k个子问题
{分解原问题为更小的子问题Pi;
yi=Divide-and-Conquer(|Pi|);} //递归解决Pi
T=MERGE(y1,y2,…,yk); //合并子问题
return(T);
}
注:二分查找,二叉排序树查找等算法无须合并子问题
例:(金块问题)老板有一袋金块,最优秀的雇员得到其中最重的一块,最差的雇员得到其中最轻的一块,利用分治法找出最重和最轻的金块。
#include <stdio.h>
#define N 5
//用分治法求最大最小数(二分法+递归)
int maxmin(int i, int j, float *max, float *min, float a[])
{
int mid;
float lmax,lmin,rmax,rmin;
if(i==j) //组内有一个元素
{
*max=a[i];
*min=a[i];
}
else if(i==j-1) //组内有2个元素
if(a[i]<a[j])
{
*max=a[j];
*min=a[i];
}
else
{
*max=a[i];
*min=a[j];
}
else
{
mid=(i+j)/2;
maxmin(i,mid,&lmax,&lmin,a);
maxmin(mid+1,j,&rmax,&rmin,a); //递归分解
if(lmax>rmax)
*max=lmax;
else
*max=rmax;
if(lmin>rmin)
*min=rmin;
else
*min=lmin; //溯洄合并
}
return 0;
}
int main() {
int i;
float max,min,a[N];
printf("array a=?");
for(i=0;i<N;i++)
scanf("%f",&a[i]);
maxmin(0,N-1,&max,&min,a);
printf("max=%.1f,min=%.1f\n",max,min);
return 0;
}
(二)贪婪法
概念:又名登山法,根本思想是逐步获得最优解,以逐步的局部最优,达到最终的全局最优。要求问题具有无后向性,即某状态以后的过程不会影响以前的状态,只与当前状态有关。
算法框架:
从问题的某一初始解出发;
while (能朝给定总目标前进一步)
利用可行的决策,求出可行解的一个解元素;
由所有解元素组合成问题的一个可行解;
例:把一个真分数表示为最少的埃及分数之和(埃及分数:分子为1)
数学模型:
将一个真分数F表示为A/B:做B/A的整除运算,商为D,余数为
K(0<Κ<Α),他们之间的关系及导出如下:
B=A*D+K
B/A=D+K/A < D+1
A/B > 1/(D+1)
记C=D+1,这样就找到了分数F所包含的“最大的”埃及分数就是1/C。进一步计算,A/B-1/C=(A*C-B)/(B*C),也就是继续要解决的是关于分子为A=A*C-B,分母为B=B*C的子问题(A < B)
#include <stdio.h>
int main() {
long a,b,c;
printf("a,b=?");
scanf("%ld%ld",&a,&b);
if(a>=b)
printf("input error");
else if(b%a==0)
printf("%ld/%ld=1/%ld",a,b,b/a);
else
{
printf("%ld/%ld=",a,b);
do{
c=b/a+1;
a=a*c-b;
b*=c;
printf("1/%ld+",c);
if(b%a==0)
{
printf("1/%ld",b/a);
a=1;
}
}while(a!=1);
}
printf("\n");
return 0;
}
(三)动态规划
概念:多阶段最优化决策问题的过程。
动态规划=贪婪策略+递推(降阶)+存储递推结果
使用问题特征:
1. 最优化原理:问题的最优解所包含的子问题的解也是最优的
2. 无后向性:某阶段状态一旦确定,就不受这个状态以后决策的影响
3. 子问题重叠:子问题之间是不独立的,一个子问题在下阶段决策中可能被多次使用到。对有分解过程的问题还表现在自顶向下分解问题时,每次产生的子问题并不总是新问题,有些子问题会反复出现多次。
基本思想:
把求解的问题分成许多阶段或子问题,然后按顺序求解各子问题。前一子问题的解,为后一子问题的求解提供了有用的信息。在求解任一子问题时,列出各种可能的局部解,通过决策保留那些有可能达到最优的局部解,丢弃掉其他局部解,依次解决各子问题,最后一个子问题就是初始问题的解。
基本步骤:
1. 划分阶段
2. 选择状态
3. 确定决策并写出状态转移方程
例:(数塔问题)一个数塔,从顶部出发,在每一个结点可以选择向左或向右走,一直走到底层,要求找出一条路径,使数值和最大
9
12 15
10 6 8
2 18 9 5
19 7 10 4 16
自下而上逐层决策,第一次决策递推出第四层和第五层的和(选择两种路径中数字和较大的结果,储存在第四层相应位置),以此类推。(若只考虑一层为贪婪法,在本题中失效)
动态规划过程:
59
50 49
38 34 29
21 28 19 21
19 7 10 4 16
#include <stdio.h>
int main() {
int i,j,k,n,flag,b,a[50][50],d[50][50],t[50][50]; //t数组存储求解方向
printf("n=?");
scanf("%d",&n);//数塔行数
printf("array a=?");
for(i=1;i<=n;i++)
for(j=1;j<=i;j++)
{
scanf("%d",&a[i][j]);
d[i][j]=a[i][j];
t[i][j]=0;
}
for(i=n-1;i>=1;i--)
for(j=1;j<=i;j++) //从最低两层开始使用贪婪法
{
if(d[i+1][j]>d[i+1][j+1])
{
d[i][j]+=d[i+1][j];
t[i+1][j]=1;
}
else
{
d[i][j]+=d[i+1][j+1];
t[i+1][j+1]=1;
}
}
printf("max=%d\n",d[1][1]);
printf("%d",a[1][1]);
for(k=1,i=2;i<=n;i++)
{
j=k;
if(t[i][j]==1)
{
printf("->%d",a[i][j]);
k=j;
}
else
{
printf("->%d",a[i][j+1]);
k=j+1;
}
}
printf("\n");
return 0;
}
另解:
#include <stdio.h>
//结构体二维数组a存储数塔数据,递推结果,求解方向
int main() {
int i,j,n;
struct {
int c1,c2,c3;
}a[50][50];
printf("n=?");
scanf("%d",&n);//数塔行数
printf("array a=?");
for(i=1;i<=n;i++)
for(j=1;j<=i;j++)
{
scanf("%d",&a[i][j].c1);
a[i][j].c2=a[i][j].c1;
}
for(i=n-1;i>=1;i--)
for(j=1;j<=i;j++) //从最低两层开始使用贪婪法
{
if(a[i+1][j].c2>a[i+1][j+1].c2)
{
a[i][j].c2+=a[i+1][j].c2;
a[i][j].c3=0;
}
else
{
a[i][j].c2+=a[i+1][j+1].c2;
a[i][j].c3=1;
}
}
printf("max=%d\n",a[1][1].c2);
for(j=i=1;i<=n-1;i++)
{
printf("%d->",a[i][j].c1);
j+=a[i][j].c3;
}
printf("%d\n",a[n][j].c1);
return 0;
}