哈工大(威海)算法实验二:曼哈顿游客问题(动态规划)
题目
设想有个观光团在纽约曼哈顿区,一群游客决定从位于第59街与第8大道的拐角处步行到第42街与Lexington大道交叉处的克莱斯勒汽车大厦(Chrysler Building)。途中会有许多景点,我们假设游客想游览尽可能多的景点。游客们可以向东或向南步行,但即使如此,也有许多条路线可供他们选择。
说人话 :在加权网格中寻找一条最长的路线。
把曼哈顿表示为具有加权路的图
输入
有起点和终点的加权网格G。
输出
G中从起点到终点的一条最长的路线。
样例
输入:题目中的网格图
输出:
题解
解决动态规划问题的两个子条件是优化子结构和重叠子问题,这两个条件曼哈顿问题都符合。
证明
由于在图上只能向下走或者向右走,因此到达某一点的距离等于,到达该点上面那一点的距离加上上面那一点到这一点的距离,和到达该店左边那一点的距离加上左边那一点到这一点的距离的最大值。存在优化子结构。
设C[i, j]存储到点(i, j)的最长距离,每计算出一个C[i, j]的值,可以利用它来计算它右边的点或者下面的点(C[i+1, j]或C[i, j+1])。存在重叠子问题。
状态转移方程及边界条件
(C[i, j]存储到点(i, j)的最长距离,w_up表示从上方的点到该点的距离,w_left表示从左侧的点到该点的距离)
C[i,j] = max(C[i-1,j]+w_up, C[i,j-1]+w_left) (i != 0 and j != 0)
C[i,j] = 0 (i = 0 and j = 0)
C[i,j] = C[i-1,j] + w_up (i != 0 and j = 0)
C[i,j] = C[i,j-1] + w_left (i = 0 and j != 0)
数据结构
定义一个结构体类型Distance来存储网格图中的点与点的距离信息,拥有两个int属性up和left,分别存储从上方的点到此点的距离和从左侧的点到此点的距离。使用这个结构题类型声明一个二维数组W[][],W[i][j].up表示点(i-1, j)到点(i, j)的距离,W[i][j].left表示点(i, j-1)到点(i, j)的距离。
代码
#include <stdio.h>
struct Distance{ // up存储从上方的点移动到此点的距离,left存储从左侧的点移动到此点的距离
int up;
int left;
};
void findLongestWay(int D[][100], int i, int j){ // 递归打印路径(从终点倒着搜索)
if (i == 0 && j == 0){ // 找到了起点,搜索结束,开始回溯打印
printf("(%d, %d)\n", i, j);
return;
}
if (D[i][j] == 0){
findLongestWay(D, i-1, j);
printf("(%d, %d)\n", i, j);
}else{
findLongestWay(D, i, j-1);
printf("(%d, %d)\n", i, j);
}
}
int main(int argc, const char * argv[]) {
struct Distance W[100][100]; // 存储网格图中点之间的距离
int C[100][100], D[100][100]; // C[][]存储到点的最大距离,D[][]存储是从上方还是左侧到某点的,0表示从上方,1表示从左侧
int n, m;
printf("输入网格的行数:");
scanf("%d", &n);
printf("输入网格的列数:");
scanf("%d", &m);
for (int i = 0; i < n; i++){
W[i][0].left = 0;
}
for (int i = 0; i < m; i++){
W[0][i].up = 0;
}
for (int i = 1; i < n; i++){
for (int j = 0; j < m; j++){
printf("请输入点(%d, %d)到点(%d, %d)的距离:", i-1, j, i, j);
scanf("%d", &W[i][j].up);
}
}
for (int i = 0; i < n; i++){
for (int j = 1; j < m; j++){
printf("请输入点(%d, %d)到点(%d, %d)的距离:", i, j-1, i, j);
scanf("%d", &W[i][j].left);
}
}
for (int i = 0; i < n; i++){ // 初始化c[][]和D[][]
for (int j = 0; j < m; j++){
C[i][j] = 0;
D[i][j] = -1;
}
}
for (int i = 1; i < n; i++){
C[i][0] = C[i - 1][0] + W[i][0].up;
D[i][0] = 0; //从上方过来,设为0
}
for (int i = 1; i < m; i++){
C[0][i] = C[0][i - 1] + W[0][i].left;
D[0][i] = 1; //从左边过来,设为1
}
for (int i = 1; i < n; i++){
for (int j = 1; j < m; j++){
if (C[i][j - 1] + W[i][j].left >= C[i - 1][j] + W[i][j].up){ // 到达某一个点有两个选择,从上方的点或者从左侧的点,选择距离大的方案,更新C[][]和D[][]的值
C[i][j] = C[i][j - 1] + W[i][j].left;
D[i][j] = 1;
}else{
C[i][j] = C[i - 1][j] + W[i][j].up;
D[i][j] = 0;
}
}
}
printf("最长路径:\n");
findLongestWay(D, n-1, m-1);
printf("最长路径的长度:%d\n", C[n-1][m-1]);
return 0;
}