【问题】如下图(一个5层数塔)所示的一个数塔,从数塔的顶层出发,在每一个结点可以选择向左走或向右走,一直走到最底层,要求找出一条路径,使得路径上的数值和最大。例如,下图所示数塔的最大数值和是 8+15+9+10+18=60。
【想法】观察上图(一个5层数塔)所示数塔不难发现,从 5 层数塔的顶层(设顶层为第 1 层)出发,下一层选择向左走还是向右走取决于两个 4 层数塔的最大数值和,如下图(数塔问题的子问题具有重叠关系)所示,显然,子问题具有重叠的特征。
如何找到子问题满足的动态规划函数呢?显然,动态规划的求解需要从底层开始进行决策,图(一个5层数塔)所示数塔问题的决策过程如下图(数塔问题的决策过程)所示,具体过程如下。
求解初始子问题:底层的每个数字可以看作 1 层数塔,则最大数值和就是其自身,填写图(数塔问题的决策过程)最下一行。
再求解下一阶段的子问题:第 4 层的决策是在底层决策的基础上进行求解,可以看作 4 个 2 层数塔,如下图(数塔问题的动态规划求解过程)(a) 所示,对每个数塔进行求解,填写图(数塔问题的决策过程)的第 4 行。
再求解下一阶段的子问题:第 3 层的决策是在第 4 层决策的基础上进行求解,可以看作 3 个 2 层的数塔,如如下图(数塔问题的动态规划求解过程)(b) 所示,对每个数塔进行求解,填写图(数塔问题的决策过程)的第 3 行。
依此类推,直到最后一个阶段:第 1 层的决策结果就是数塔问题的整体最优解。
由上述填表过程,可以设计数塔问题的存储结构。将给定的数塔存储为如下图(数塔的存储)所示
下三角矩阵 d[ n ][ n ],设二维数组 maxAdd[ n ][ n ] 存储动态规划每一步的决策结果,最后maxAdd[ 0 ][ 0 ]存储的就是数塔问题的最优解,则得到如下动态规划函数:
为了求得最大数值和的路径,设数组 path[ n ][ n ] 保存每一次决策所选择的数字在数组 d[ n ][ n ]中的列下标,例如,path[ i ][ j ] 的值表示在第 i 层第 j 个数塔的决策时选择的路径,path[ i ][ j ] 的值定义如下:
【算法】设函数 DataTower 完成 n 层数塔问题并输出对应的路径,算法用伪代码描述如下。
算法:数塔问题 DataTower
输入:二维数组 d[ n ][ n ]
输出:数塔的最大数值和及其路径
1. 初始化数组 maxAdd 的最后一行为数塔的底层数据:
for(j=0;j<n; j++)
maxAdd[ n - 1 ][ j ] = d[ n - 1 ][ j ];
2.从第 n-1 层开始直到第 1 层对下三角元素 maxAdd[ i ][ j ]执行下述操作:
2.1 maxAdd[ i ][ j ] = d[ i ][ j ] + max{maxAdd[ i + 1 ][ j ],maxAdd[ i + 1 ][ j + 1 ]};
2.2 如果选择下标 j 的元素,则 path[ i ][ j ] = j,否则 path[ i ][ j ] = j + 1;
3.输出最大数值和 maxAdd[0][0];
4.根据 path 数组确定每一层决策的列下标,输出路径信息。
【算法分析】在上面算法中,步骤 1 的时间代价是;步骤 2 进行填表工作,需填写 n - 1 行,由于数组 maxAdd 是下三角矩阵,第 i 行只需填写 i 个元素,因此,步骤 2 的时间代价是;由于数组 path 已经记载每个决策的列下标,步骤 4 只需输出每行的决策结果,因此,步骤 4 的时间代价是。算法的时间复杂性是。
【算法实现】设函数DataTower返回数塔的最大数值和,同时输出对应的路径,算法用JAVA语言描述如下:
public class DataTower {
int n=5;
public static void main(String[] args) {
int n=5;
DataTower dataTorwer = new DataTower();
int [][] d = new int[n][n];
d[0][0]=8;
d[1][0]=12;d[1][1]=6;
d[2][0]=3;d[2][1]=9;d[2][2]=4;
d[3][0]=6;d[3][1]=5;d[3][2]=7;d[3][3]=8;
d[4][0]=1;d[4][1]=2;d[4][2]=3;d[4][3]=4;d[4][4]=5;
int max=dataTorwer.DataTower(d);
System.out.println(max);
}
int DataTower( int d[][] ) //求解数塔问题,数塔存储在数组d[n][n]中
{
int [][] maxAdd = new int[n][n];
int [][] path = new int[n][n]; //初始化
int i, j;
for (j = 0; j < n; j++) //初始化底层决策结果
maxAdd[n-1][j] = d[n-1][j];
for (i = n-2; i >= 0; i--) //进行第i层的决策
for (j = 0; j <= i; j++) //填写addMax[i][j],只填写下三角
if (maxAdd[i + 1][j]>maxAdd[i + 1][j + 1])
{
maxAdd[i][j] = d[i][j] + maxAdd[i + 1][j];
path[i][j] = j; //本次决策选择下标j的元素
}
else
{
maxAdd[i][j] = d[i][j] + maxAdd[i + 1][j + 1];
path[i][j] = j + 1; //本次决策选择下标j+1的元素
}
System.out.println("路径为(顶层数字):"+d[0][0]); //输出最顶层数字
j = path[0][0]; //顶层决策是选择下一层列下标为path[0][0]的元素
for (i = 1; i < n; i++)
{
System.out.println("下一层的元素为:");
System.out.println(String.valueOf(d[i][j]));
j = path[i][j]; //本层决策是选择下一层列下标为path[i][j]的元素
}
System.out.println("最大数值和为:");
return maxAdd[0][0]; //返回最大数值和,即最终的决策结果
}
}
运行结果如下:
from:算法设计与分析(第2版)——王红梅 胡明 编著——清华大学出版社