写在前面的话——
动态规划,说是动态,在我看来跟枚举 差不多了,就是把所有结果所有可能都算出来,然后规划一个最优解,不知道这么说不会显得太低端,但是我现在真的就是这么想的,和贪婪的区别就是,贪婪是先选出最优的,然后再计算,只算最优的那一个,动态规划就是先计算,然后再选出最优的,所以所有的都要计算,计算很多次,感觉不就是枚举法么,不知道怎么说啊
第六篇——
这次的题真的想了好久,其实书上都有类似的算法,看看就能写的出来,但是这样似乎没有什么意义,我还是仔细看了看,然后领会思想,把握区别,在三次算法课,分治,贪婪还有这次动态规划的课,这次是最认真的了,呃,我指的是作业,上课的时候觉得老师有一点照本宣科,没有添加一点帮助理解或者是用通俗的语言讲出来,有的时候真的觉得课本的文字真的太书面化了,太不是人话了,文言文!
简单说下三个题,第一个题,有一个图是空白的,其实就是5个箭头,意思就是,人只能走5个方向,就是正上方与左边两个和右边两个,所以要注意,一排有7个数,第一排的最左和最右是不能走的,所以这个题应该增加一点难度,就是把加起来实际最大的数放在最左或最右,看你的程序有没有做出判断;第二个题没什么好说的;第三个题感觉就是很明显了,就是把每一种算法的次数算出来,然后再找出最小的,那不就是枚举么!
1、在一个n*m的放个中,m为奇数,放置有n*m个数,如下所示。方格中间的下方有一个人,此人可按照5个方向前进但不能越出方格。
16 | 4 | 3 | 12 | 6 | 0 | 3 |
4 | -5 | 6 | 7 | 0 | 0 | 2 |
6 | 0 | -1 | -2 | 3 | 6 | 8 |
5 | 3 | 4 | 0 | 0 | -2 | 7 |
-1 | 7 | 4 | 0 | 7 | -5 | 6 |
0 | -1 | 3 | 4 | 12 | 4 | 2 |
人 |
人每走过一个方格必须取此方格中的数。要求找到一条从底到顶的路径,使其数相加之和为最大。输出最大和的值。
算法设计:
可以使用动态规划方法,从顶部开始,将第二排的每个数加上顶部第一排的最优解,将6排的问题缩小为5排的问题,然后重复该操作,直到底部最后一排,此时可以发现人只能选取5个方向,所以排除掉不能选取方向后选取最大值即可
源代码如下:
#include <stdio.h>
#define N 6
#define M 7
#define MID 5
/*
* 用分治法取出一个范围内的最大值以及该值的下标
* */
void getMax(int a[M][4],int left, int right,int *max, int *j)
{
int lmax,rmax,lj,rj,mid;
if(left < 0) {
left= 0;
}
if(right > M - 1) {
right= M - 1;
}
if(left == right) {
*max= a[left][1];
*j= left;
}else if(left == right - 1) {
*max= a[left][1] >= a[right][1] ? a[left][1] : a[right][1];
*j= a[left][1] >= a[right][1] ? left : right;
}else {
mid= (left + right) / 2;
getMax(a,left, mid, &lmax, &lj);
getMax(a,mid + 1, right, &rmax, &rj);
*max= lmax >= rmax ? lmax : rmax;
*j= lmax >= rmax ? lj : rj;
}
}
int main()
{
/*
data[N][M][0]存储原始数据
data[N][M][1]存储使用动态规划方法计算的数据
data[N][M][2]存储2中选中的数的索引
data[N][M][3]存储最终选中的路径
*/
int data[N][M][4];
printf("从底到顶输入每行的数据:\n");
int i, j;
//为方便计算,从底部开始录入,最终输出的时候逆向输出
for(i = 0; i < N; i++) {
for(j = 0; j < M; j++) {
scanf("%d",&data[i][j][0]);
data[i][j][1]= data[i][j][0];
data[i][j][2]= data[i][j][3] = 0;
}
}
printf("正在运算\n");
for(i = N - 2; i >= 0; i--) {
for(j = 0; j < M; j++) {
int maxdata, maxdataj;
getMax(data[i+ 1], j - MID/2, j + MID/2, &maxdata, &maxdataj);//得到每次5个方向的最大的数以及下标
data[i][j][1]+= data[i + 1][maxdataj][1];
data[i][j][2]= maxdataj;//将该位置所加的数的索引,即maxdataj保存起来以便追溯
}
}
int maxdata,maxdataj;
getMax(data[i+ 1], MID - 2, MID + 2, &maxdata, &maxdataj);
printf("最大和为:%d\n",maxdata);
data[0][maxdataj][3]= 1;
for(i = 1; i < N; i++) {
maxdataj= data[i - 1][maxdataj][2];
data[i][maxdataj][3]= 1;
}
//输出路径,按照题目格式输出,并且1表示所走路径
for(i = N - 1; i >= 0; i--) {
for(j = 0; j < M; j++) {
printf("%d\t",data[i][j][3]);
}
printf("\n");
}
for(i = 0; i < N/2; i++) {
printf("\t");
}
printf("人\n");
return 0;
}
算法分析:
以上代码选取出的路径不是唯一路径,打印data[N][M][1],
可以看出路径上的值和不在路径的值,有的是一样的,此时选取另外一条路径也是可以的。在编码过程中,选取最大和十分容易,但是找出最大和之后,该值的相加路径挑选出来,感觉十分不易,最终通过在数组中增加一项存储该位置的值所加的上一个选取的局部最优解的索引,然后循环回溯选取路径解决,但是感觉这样的方法并不是非常理想。
2、某工业生产部门根据国家计划的安排,拟将某种高效率的5台机器,分配给所属的A,B,C3个工厂各工厂载获得这种机器后,可以为国家盈利如下表所示,问:这5台机器如何分配给各工厂,才能使国家盈利最大?
S | A | B | C |
0 | 0 | 0 | 0 |
1 | 3 | 5 | 4 |
2 | 7 | 10 | 6 |
3 | 9 | 11 | 11 |
4 | 12 | 11 | 12 |
5 | 13 | 11 | 12 |
其中,第一列S为机器台数,A、B、C3列为3个工厂在拥有不同台数的机器时的盈利值。
算法设计:
资源分配问题,总体思路应该是先算出前N项和起来的总盈利情况,找出前n项的每加一个资源的最大盈利数,再加上新的一项后的每一个资源的最大盈利数并存储最佳分配方案。本题可以考虑只有一个工厂的情况,此时最佳分配方案就是全部给这一个工厂,然后增加第二个工厂,然后计算分别分给两个工厂的分配之后的总盈利,取出每一个总资源数下的最大盈利数以及分配个新增的第二个工厂的分配额,并且把每一个总资源数下的最大盈利集合成为一个新的数组,再增加第三个工厂,依此计算
源代码如下:
#include <stdio.h>
#define N 3 //项目数
#define M 5 //资源总数
int main()
{
int item[M+1], max[M+1], temp[M+1];
int a[N+1][M+1] = {0};
printf("依次输入第一个工厂的相应的盈利数:\n");
int i,j,k;
for(i = 0; i <= M; i++) {
scanf("%d",&item[i]);
max[i]= item[i];
a[1][i]= i;//在有1个项目的时候,资源总数为i时,对第一个项目的最佳投入为i
}
for(k = 2; k <= N; k++) {//此时新增项目数
printf("依次输入下一个工厂相应的盈利数:\n");
for(i = 0; i <= M; i++) {
temp[i]= item[i];//将前一个备份;
scanf("%d",&item[i]);//输入下一个项目
}
for(j = 0; j <= M; j++) {
for(i = 0; i <= j; i++) {
//选出在总投入为j的时候,投入第k个项目多少,总盈利最大
if(max[j-i] + item[i] > temp[j]) {
temp[j]= max[j-i] + item[i];
a[k][j]= i;
}//如果该判断一次都没有进,则表示这个项目投入为0,因为无论投入多少都不如前几个项目和大
}
}
for(j = 0; j <= M; j++) {
max[j]= temp[j];//max数组始终保持k个项目,投入为j时的最佳盈利数
}
}
int result[N+1] = {0};
int sum = M;
for(i = N; i > 0; i--) {
result[i]= a[i][sum];
sum= sum - result[i];
}
printf("分配结果如下:\n三个工厂分别分配");
for(i = 1; i <= N; i++) {
printf("%d台\t",result[i]);
}
printf("\n总盈利数为%d\n",max[M]);
return 0;
}
3、以下4个矩阵相乘,M=M1 *M2 * M3 * M4,其中M1为5*20的矩阵,M2为20*50的矩阵,M3为50*1的矩阵,M4为1*100的矩阵,多个矩阵连乘运算是满足结合律,写一个算法,求出这4个矩阵连乘时,元素间相乘的次数最少,并写出结合方案。
算法设计:
首先,p*q阶与q*r阶的两个矩阵相乘时,共需作p*q*r次相乘。
其次,记Mi*Mi+1*……*Mj的相乘次数为mij,矩阵的阶数为M1是r1*r2,M2是r2*r3,Mj是rj*rj+1,当计算M1到Mn的矩阵相乘,对于1<=i<=j<=n,mij的递推公式为:
0 当i=j时
ri-1*ri*ri+1 当j=i+1
Min(mi,k+mk+1,j+ri*rk+1*rj+1) 当i<j时
其中,当i<=k<=j时,设mi,k+mk+1,j+ri*rk+1*rj+1为mij的最小值,所以k的分配方案就是整个矩阵相乘的分配方案,由以上的递推公式,可以写出函数,通过递推求出最小分配方案时的k的值,从而求出解
源代码如下:
#include <stdio.h>
#define N 102
int m[N][N];//矩阵相乘的次数
int key[N][N];//矩阵相乘次数最少的关键下标
int r[N];//矩阵的阶数
int course(int i,int j)
{
int u,k,t;
if(m[i][j] != -1) {
return m[i][j];
}
if(i == j) {
return 0;
}else if(i == j - 1) {
key[i][i+1]= i;
m[i][j]= r[i]*r[i+1]*r[i+2];
return m[i][j];
}else {
u= course(i,i) + course(i+1,j) + r[i]*r[i+1]*r[j+1];
key[i][j]= i;
for(k=i+1; k<j; k++) {
t= course(i,k) + course(k+1,j) + r[i]*r[k+1]*r[j+1];
if(t < u) {//如果此时t的划分方法比u的划分方法次数小,则替换掉u的方法并进行下次的比对,直到最终全部比对结束
u= t;
key[i][j]= k;
}
}
m[i][j]= u;
return u;
}
}
void combine(int i,int j)
{
if(i == j) {
return ;
}
printf("在M%d到M%d中分配方案为,",i,j);
if(i == key[i][j] && j == key[i][j]+1) {
printf("M%d和M%d\n",i,j);
}else if(i == key[i][j]) {
printf("M%d和M%d乘到M%d\n",i,key[i][j]+1,j);
}else if(j == key[i][j]+1) {
printf("M%d乘到M%d和 M%d\n",i,key[i][j],j);
}else {
printf("M%d乘到M%d和 M%d乘到M%d\n",i,key[i][j],key[i][j]+1,j);
}
combine(i,key[i][j]);
combine(key[i][j]+1,j);
}
int main()
{
int n,i,j;
printf("输入矩阵的个数(最大值为%d):\n",N-2);
scanf("%d",&n);
printf("分别输入阶数:\n");
for(i = 1; i <= n+1; i++) {
scanf("%d",&r[i]);
}
for(i = 1; i <= n; i++) {
for(j = 1; j <= n; j++) {
key[i][j]= 0;
m[i][j]= -1;
}
}
course(1,n);
printf("元素间的最少相乘次数是%d\n", m[1][n]);
combine(1,n);
return 0;
}