一、例题要求及理论分析
声明:理论指导《算法设计与分析 第四版》
因为这个地方用到了三维数组,感觉很有意思就故意挑出来分享给大家(三维数组可以看成很多页二维数组)
4.5.1认识动态规划
数塔问题:
如图4-12所示的一个数塔,从顶层到底层或从底层到顶层,在每一结点可以选择向左走或是向右走,要求找出一条路径,使路径上的数值和最大。
问题分析:
(1)不难理解,这个问题用贪婪算法有可能会找不到真正的最大和。以图4-12为例就是如此。采用贪婪策略,无论是自上面下,还是自下而上,每次向下都选择较大的一个数移动,则路径和分别为:
数塔图
9+15+8+9+10=51(自上而下),19+2+10+12+9=52(自下而上)
都得不到最优解,真正的最和是:
9+12+10+18+10=59
(2)要找到最大和的前提条件是,要能看到数塔的全貌,下面的算法设计都是以此为前提的。
在知道数塔全貌的前提下,可以用枚举法或第5章将学习的搜索算法来解决问题。但从图中可以看出,在数塔层数为 n 时,要枚举的路径为2^(n-1)条。在 n 稍大的情况下,需要列举出的路径条数是一个非常庞大的数目。所以枚举法也不是一个适合此问题的算法策略。
(3)这个问题的原始数据是一个三角形的二维图形,而且问题的答案与各层数据间关系复杂,不适合用分治算法分解为与原问题相似的子问题。
下面就学习用动态规划解决此问题。
算法设计:动态规划设计过程如下。
1.阶段划分
从数塔问题的特点来看,不难发现解决问题的阶段划分,应该是自下而上逐层决策。不同于贪婪策略的是做出的不是唯一决策,第一步对于第五层的8个数据,做如下4次决策:
对经过第四层2的路径,在第五层的19,7中选择19;
对经过第四层18的路径,在第五层的7,10中选择10;
对经过第四层9的路径,在第五层的10,4中也选择10;
对经过第四层5的路径,在第五层的4,16中选择16。
这是一次决策过程,也是一次递推过程和降阶过程。因为以上的决策结果将5阶数塔题变为4阶子问题,递推出第四层与第五层的和为:
21(2+19),28(18+10),19(9+10),21(5+16)
用同样的方法还可以将4阶数塔问题变为3阶数塔问题……最后得到的1阶数问题,就是整个问题的最优解。
2、存储、求解
1)原始信息存储
原始信息有层数和数塔中的数据,层数用一个整型变量 n 存储,数塔中的数据用二维数组 data ,存储成如下的下三角阵:
9
12 15
10 6 8
2 18 9 5
19 7 10 4 16
2)动态规划过程存储
由于早期阶段动态规划决策的结果是一组数据,且本次的决策结果是下次决策的唯一依据(无后效性),所以必须在存储每一次决策的结果,若仅仅是求最优解,用一个一维数组存储最新的决策结果即可;但若要同时找出最优解的构成或路径,则必须用二维数组 d 存储各阶段的决策结果。根据上面的算法设计,二维数组 d 的存储内容如下:
d [ n ][j]= data [ n ][j] j=1,2,……,n;
i = n -1, n -2,…,1, j =1,2,…, i 时
d [ i ][ j ]= max ( d [ i +1][j], d [ i +1][j+1])+ data [ i ][j]
最后 d [1][1]存储的就是问题的结果。
二、代码
#include<stdio.h>
int main()
{
int a [50][50][3], i , j , n ;
printf (" please input the number of rows :\n");
scanf("%d",&n);
for ( i =1; i <= n ; i = i +1)
for ( j =1; j <= i ; j = j +1)
{
scanf("%d",&a [ i ][ j ][1]);
a [ i ][ j ][2]= a [ i ][ j ][1];
a [ i ][ j ][3]=0;
}
for ( i = n -1; i >=1; i = i -1)
for ( j =1; j <= i ; j = j +1)
if ( a [ i +1][ j ][2]> a [ i +1][ j +1][2])
{
a [ i ][ j ][2]= a [ i ][ j ][2]+ a [ i +1][ j ][2];
a [ i ][ j ][3]=0;
}
else
{
a [ i ][ j ][2]= a [ i ][ j ][2]+ a [ i +1][ j +1][2]; a [ i ][ j ][3]=1;
}
printf (" max =%d\n", a [1][1][2]);
j =1;
for ( i =1; i <= n -1; i = i +1)
{ printf ( "%d->",a [ i ][ j ][1]);
j = j + a [ i ][ j ][3];
}
printf ("%d", a [ n ][ i ][1]);
return 0;
}