要求:利用动态规划实现最短路径和。
Example:从起点到终点,只能向右或向上,要求找到最短的路径。(下图为了方面大家思考所以标明了行列和每段距离,实际情况可能会输入)
动态规划思想:
把原始问题分解为一系列子问题
求解每个子问题仅一次,并将结果保存下来,以后用到时直接取,不重复计算,与此同时,算法效率提高
自底向上计算
适用:对于一类优化问题,可以分为多个相关子问题,子问题的解被重复使用
(可参考:斐波那契数列,爬楼梯问题)
首先我们标好每个坐标(中间的就不标了,太乱了看着)
我们的目的是从(1,1)到(4,5)
我们可以倒着想,如果我要从(4,5)往回走,发现只能走(4,4)或者(3,5)
(4,4)到(4,5)最短路径就是1,因为没别的可选
(3,5)到(4,5)最短路径也是1
我们给(4,4)这里保存的值是1,给(3,5)这里保存的值是1
然后考虑(3,4)到终点,从(3,4)到(4,5)只有两条路:
(3,4)->(4,4)->(4,5)
(3,4)->(3,5)->(4,5)
(3,4)->(4,4)->(4,5)的路径是3+1=4
(3,4)->(3,5)->(4,5)的路径是8+1=9
所以从(3,4)到(4,5)最短的路径就是走(3,4)->(4,4)->(4,5),也就是距离为4的是最短路径和,所以这里给(3,4)保存的值是4
相信大家明白啥意思了,我可以定义一个二维数组,其值存储从该点到终点最短的路径和。
就像我从(1,1)算最短路径和,我只需要知道,(2,1)和(1,2)这两个分别到终点的路径和。(因为我们倒着计算,完全能够确定(2,1)和(1,2)存储的值是多少,因为他们本身存的值就是最短路径和)
我怎么确定从(1,1)走哪条呢?
只需要确定 (1,1)到(2,1)的距离+(2,1)本身存储的值。(这里的话也就是3+a[2][1])
以及(1,1)到(1,2)的距离+(1,2)本身存储的值。(这里的话也就是5+a[2][1])
我比较这两个哪个小,我就在(1,1)存储上计算结果。
即把原始问题分解为一系列子问题,求解每个子问题仅一次,并将结果保存下来,以后用到时直接取,不重复计算。
如果没明白,我再举个栗子
我们之前算了a[4][4] =1,a[3][5]=1,a[3][4]=4;
那a[2][5] = ?
因为最右边比较特殊,只能向上走(同理最上边只能向右)。即(2,5)->(3,5)->(4,5),然后求一下距离之和。a[2][5]=2+1=3。
懂了吗?那我们再算一个a[2][4]应该存储的最短路径和。
a[2][4]第一步只可以去a[3][4]或a[2][5]。
这时我们不用去考虑从(3,4)或(2,5)之后该怎么走,因为我们a[3][4]和a[2][5]已经都计算好了怎么走路径最短,以及已经存储好了最短路径和。
so: 可以有两个计算方法:
a[2][4] = 2 + a[3][4] = 2+4 = 6;
a[2][4] =4 + a[2][5] = 4+3 = 7;
选最小的,所以,a[2][4] = min(6,7) = 6;
有同学问为什么不考虑(2,4)->(3,4)->(3,5)->(4,5)
是这样的:我们从(3,5)到终点的时候我们在计算a[3][5]的时候已经确定了走哪条是最短的,即(3,4)->(4,4)->(4,5)=4的。而非(3,4)->(3,5)->(4,5)的路径是8+1=9的。
本题的最短路径和:12
如果大家懂了就快去用代码实现一下。
好啦,上代码啦(首先是C的):
# include <stdio.h>
struct S{
int up; //该坐标向上的距离
int right; //该坐标向右的距离
int v; //本身的值
};
int main(void)
{
//m向上 n向右 从(1,1)点到(m,n)
//m、n 可以后期再定义为输入,这里指定一下,但以下程序都是用m、m来表示,不会涉及到具体的数。
int m=4,n=5;
struct S a[m+1][n+1];
int i,j;
for(i=1;i<=m;i++)
{
for(j=1;j<=n;j++)
{
//输入的时候,如果没有向上或向右的,我们手动输入 0
scanf("%d",&(a[i][j].up));
scanf("%d",&(a[i][j].right));
}
}
//compute 算法部分,重点看一下哦!!!
a[m][n].v = 0;
for(i=m;i>0;i--)
{
for(j=n;j>0;j--)
{
if( m==i && j!=n) //当m==i时,可以发现会筛选出最上边的一行 至于j==n时,算的是终点,终点之前已经赋值了,再计算会混乱
{
a[i][j].v = a[i][j].right + a[i][j+1].v;
}
else if( n==j && i!=m) //当n==j时,可以发现会筛选出最右边的一行 至于i==m时,算的是终点,终点之前已经赋值了,再计算会混乱
{
a[i][j].v = a[i][j].up + a[i+1][j].v;
}
else if(i!=m && j!=n) //当既不是最上边也不是最右边,也不是终点时
{
int n1 = a[i][j].up+a[i+1][j].v;
int n2 = a[i][j].right+a[i][j+1].v;
//要求的是最短路径 看一下n1和n2哪个小,就存储哪个
a[i][j].v = n1>n2?n2:n1;
}
}
}
printf("%d",a[1][1].v);
return 0;
}
/*
供测试数据:
3 5
1 6
3 7
1 8
3 0
2 1
2 2
2 3
2 4
2 0
1 5
3 6
1 7
3 8
1 0
0 4
0 3
0 2
0 1
0 0
得出最后结果a[1][1].v=12;
*/
再附上一个JS代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>WebHomework05</title>
<script type="text/javascript">
//动态规划实现最小路径和
//这里的m和n可以手动输入,利用prompt语句。这里指定一下值,但下面的程序用的都是m和n,如果需要修改m和n的值只需要修改定义这里的就行。
var m = 4;
var n = 5;
/*不太会定义结构体,所以定义了三个二维数组,基本算法思想是一样的*/
//up数组存储每个坐标的向上的路径,right存储每个坐标向下的路径,v数组利用动态规划存储该坐标的值到右上终点的最小路径和
var up = new Array();
for(var i=1;i<=m;i++){
up[i] = new Array();
for(var j=1;j<=n;j++){
up[i][j] = 0;
}
}
var right = new Array();
for(var i=1;i<=m;i++){
right[i] = new Array();
for(var j=1;j<=n;j++){
right[i][j] = 0;
}
}
var v = new Array();
for(var i=1;i<=m;i++){
v[i] = new Array();
for(var j=1;j<=n;j++){
v[i][j] = Number(0);
}
}
//从[1,1]到[m,n]依次输入所在坐标的up和right值
for(var i=1;i<=m;i++){
for(var j=1;j<=n;j++){
up[i][j] = prompt("Please input 'up' a["+i+"]["+j+"]");
right[i][j] = prompt("Please input 'right' a["+i+"]["+j+"]");
}
}
//compute
v[m][n] = Number(0);
for(i=m;i>0;i--)
{
for(j=n;j>0;j--)
{
if( m==i && j!=n)
{
v[i][j] = Number(right[i][j]) + Number(v[i][j+1]);
}
else if( n==j && i!=m)
{
v[i][j] = Number(up[i][j]) + Number(v[i+1][j]);
}
else if(i!=m && j!=n)
{
var n1 = Number(up[i][j]) + Number(v[i+1][j]);
var n2 = Number(right[i][j])+Number(v[i][j+1]);
//要求的是最短路径,所以三元表达式如下:
v[i][j] = Number(n1>n2?n2:n1);
}
}
}
/*
for(var i=1;i<=m;i++){
for(var j=1;j<=n;j++){
console.log("i="+i+" j="+j+" up="+up[i][j]+" right="+right[i][j]+" v="+v[i][j]);
}
}
//由此循环可以观察出 js将prompt语句所赋的值当成字符串来处理了,
//所以我们可以在之前的循环赋值语句中调用String对象的Number()方法,将字符串转化为数值型
*/
console.log("the min add = "+Number(v[1][1]));
//或者是alert("the min add = "+Number(v[1][1]));
/*供测试的数据:
3 5
1 6
3 7
1 8
3 0
2 1
2 2
2 3
2 4
2 0
1 5
3 6
1 7
3 8
1 0
0 4
0 3
0 2
0 1
0 0
最短路径和是12,正确,从左下到右上走的分别是3->1->2->2->1->2->1
*/
</script>
</head>
<body>
</body>
</html>
感谢朋友们能看到这里。
如果程序不懂记得先看懂流程控制,在看懂每个语句的功能,最后试数。
加油呀!