利用动态规划实现最短路径和(适合小白看,看不懂你打我,附JS代码和C代码实现)

要求:利用动态规划实现最短路径和。
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>

感谢朋友们能看到这里。
如果程序不懂记得先看懂流程控制,在看懂每个语句的功能,最后试数。
加油呀!

  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值