题目要求:
编写一个算法,在非负矩阵中,从左上角走到右下角,每次只能向左或向下移动一格,输出走过的路径节点坐标和最小权值。
方法:动态规划法
状态转移方程stat[i][j] = min{stat[i-1][j], stat[i][j-1]}。
逻辑:目的节点是从其上边节点或左边节点下来的,判断上边节点和左边节点哪个小,就是从哪个节点下来的,并用一个direct矩阵记录所来方向,若该节点是从上边节点下来的,就将direct矩阵该节点定为1,否则定为0。
非负矩阵名字定义为matrix,这个matrix矩阵有两条特殊路径,即第一行和第一列,因为第一行的所有节点只能从它左边节点走过来的,而第一列的所有节点都是从它上一个节点走过来的。这样,就能得到第一行每个节点到左上角节点的权值和第一列每个节点到左上角节点的权值。
比如:
(0, 0)(0,1)(0, 2)
(1, 0)(1,1)(1,2)
(2,0)(2, 1)(2,2)
对应的权值为:
1 2 3
4 5 6
2 3 5
节点(0,0)为起始点,节点(2,2)为终止点,从起始点到节点(0,1)的总权值为(0,0)的权值加上(0,1)的权值,从起始点到(0,2)的总权值为(0,0)的权值加上(0,1)的权值加上(0,2)的权值。同理可得(1,0)到起始点的总权值,(2,0)到起始点的总权值。
得到的stat表为
1 3 6
5
7
而(1,1)节点到起始点有两个方向,一个是它的上边,即(0,1)点,一个是它的左边(1,0)点,通过stat表可知,(0,1)点的总权值为3,(1,0)点的总权值为5,因为要获得最短路径,所以我们选最小的权值3在加上当前节点的权值就是当前节点到启始节点的总权值,即为8。为了记录当前权值是从它上边走过来的还是从它左边走过来的,我们还第一了一个direct矩阵,初始化后的direct矩阵为:
0 1 1
0
0
其中,1表示该节点是从它的左边走过来的,0表示该节点是从它上边节点走过来的,在修改stat矩阵时,也应该修改direct矩阵。经过两层循环,就能填充stat矩阵和direct矩阵,填充满后的stat矩阵和direct矩阵为:
stat矩阵:
1 3 6
5 8 12
7 10 15
direct矩阵:
0 1 1
0 0 0
0 1 1
stat矩阵的终止顶点中的权值就是最短路径的权值。
根据direct矩阵,我们能够得到目的顶点(2, 2)是从它左边顶点(2,1)走过来的,因为当前direct值为1(表示从当前顶点左边走过来),而(2,1)是从它左边顶点(2,0)走过来的,而(2,0)是从它上边顶点(1,0)走过来的,而(1,0)又是从它上边(0,0)点走过来的,而(0,0)点就是启始顶点。
由此就得到了最短路径和最短路径权值,代码如下:
# include
# include
using namespace std;
void getPath(vector<vector<int> > &matrix, int &value, vector<int> &path_i, vector<int> &path_j)
{
if(matrix.empty())
return;
int rows = matrix.size();
int cols = matrix[0].size();
vector<vector<int> > stat(rows, vector<int> (cols)); // 用来存放已经过节点的总权值
vector<vector<int> > direct(rows, vector<int> (cols)); // 用来存放该节点是从哪个方向来的(1表示从上边,0表示从左边))
int sum = 0;
for (int j = 0; j < cols; ++j) // 初始化状态表第一行
{
sum += matrix[0][j];
stat[0][j] = sum;
direct[0][j] = 1;
}
sum = 0;
for (int i = 0; i < rows; ++i) // 初始化状态表第一列
{
sum += matrix[i][0];
stat[i][0] = sum;
direct[i][0] = 0;
}
for (int i = 1; i < rows; ++i) // 填充状态表和方向表
{
for (int j = 1; j < cols; ++j)
{
if (stat[i - 1][j] < stat[i][j - 1])
{
stat[i][j] = stat[i - 1][j] + matrix[i][j];
direct[i][j] = 0;
}
else
{
stat[i][j] = stat[i][j - 1] + matrix[i][j];
direct[i][j] = 1;
}
}
}
value = stat[rows - 1][cols - 1]; // 获取最短路径权值
for (int i = rows -1, j = cols - 1; i >= 0 && j >= 0;) // 获得最短路径
{
path_i.push_back(i);
path_j.push_back(j);
if (direct[i][j] == 1)
j--;
else
i--;
}
}
void printPath(vector<int> path_i, vector<int> path_j) // 打印路径
{
for (int i = path_i.size() - 1; i >= 0; --i)
{
cout << '(' << path_i[i] << ", " << path_j[i] << ')' << endl;
}
}
int main(void)
{
int matrix_temp[6][7] = {{0, 2, 8, 3, 7, 9, 6},
{5, 3, 7, 9, 6, 8, 1},
{3, 9, 2, 9, 8, 7, 6},
{8, 8, 1, 6, 9, 6, 8},
{3, 2, 9, 8, 7, 7, 1},
{4, 2, 9, 6, 5, 3, 9}};
vector<vector<int> > matrix;
int rows = 6;
int cols = 7;
for (int i = 0; i < rows; ++i)
{
vector<int> temp;
for (int j = 0; j < cols; ++j)
{
temp.push_back(matrix_temp[i][j]);
}
matrix.push_back(temp);
temp.clear();
}
vector<int> path_i; // 用来存放经过的节点的行号
vector<int> path_j; // 用来存放经过的节点的列号
int value; // 用来存放最短路径的权值
getPath(matrix, value, path_i, path_j);
cout << "value is " << value << endl;
cout << "path: " << endl;
printPath(path_i, path_j);
}
若有不对之处,敬请指正。