矩阵的路径最小和 python实现

矩阵的路径最小和 python实现

数据的算法与结构是每个程序员必须熬过的基础之一,相信大家在解析问题的时候总会迸发灵感想出自己的算法。下面结合算法中的经典问题:矩阵路径和最小,提出问题不同的求解方法,方便初学者更好的理解算法结构以及分析问题的思路。

问题:给定一个矩阵m,从左上角开始每次只能向右或者向下走,最后到达右下角的位置,路径上所有的数字累加起来就是路径和,返回所有路径和中最小的路径和。

方法1:递归寻找所有路径和 (左、上两个方向)
对于这种重复进行操作判断的问题,我们最容易想到的就是递归了,暴力递归总是我们最好的朋友。

import numpy as np
def f(v, i, j):
	#到达原点终止
    if i == 0 and j == 0:
        return v[0][0]
     #到达最上端,不再执行上走
    elif i == 0:
        return f(v, i, j - 1) + v[i][j] #j-1表示向左走
     #到达最左端,不再执行左走
    elif j == 0:
        return f(v, i - 1, j) + v[i][j] #i-1表示向上走;
     #以上为终止条件,else中的是递归的链条  
    else:
    	#比较左走还是上走的产生的路径和小,进行累加
        return min(f(v, i - 1, j), f(v, i, j - 1)) + v[i][j]

v = np.array([[1, 3, 5, 9],
              [8, 1, 3, 4],
              [5, 0, 6, 1],
              [8, 8, 4, 0]])
print(f(v, 3, 3))

既然是从左上角(A点)抵达右下角(B点)的路径和,那么我们就可以巧妙地利用逆向的思维,将问题转化未由右下角退回至左上角,可以减少我们定义变量的数量,以及分析问题的复杂程度。
问题就变为 :f(v, i, j)=f(v, i, j)+v [i] [j]
这里的f(v, i, j)就是我们存储的上一步的路径和;v [i] [j]是当前步骤的数字

因此递归的要素:
终止条件:到达A点(i=0,j=0)
链条:判断左走的路径和小,还是右走的路径和小,累加数字

我们再对问题进行一次回顾,每走一步即判断下一步是左走还是上走比较好,由于递归的层层嵌套,这里已经将所有路径的可能都进行了比较取优,时间复杂度Θ(nlgn),这怎么能够满足我们对知识的探索呢?那怎么减少计算的次数呢?
因此再提出另一种方法:动态规划。

'''方法2:动态规划(下、右两个方向)'''
def minSum(list1):
    l1=len(list1)
    if l1<2:
        return list1
    list2=list1
    for i in range(1,l1):                       #设定了一个初始值矩阵,指定0行0列数字为第一个数字进行向右或是向下产生的路径和,
        list2[0][i]=list1[0][i-1]+list1[0][i]
        list2[i][0]=list1[i-1][0]+list1[i][0]
    for i in range(1,l1):
        for j in range(1,l1):
            sum1=list2[i][j-1]+list1[i][j]      #对于矩阵中的每一个元素进行一个判别,从第上一个元素到达该位置,是执行向下走 j-1 的数值小还是向右走 i-1 的路径和数值小
            sum2=list2[i-1][j]+list1[i][j]
            if sum1<sum2:
                list2[i][j]=sum1                 #判别成功后,将路径和小的那个路径,写在最后的元素的位置上替代该元素。之后进行循环判别各个路径加上哪个新元素的路径和小
            else:
                list2[i][j]=sum2
    return list2[l1-1][l1-1]

print(minSum(v))

动态规划的思路是,为路径和再生成一个矩阵list2,该矩阵的维度与原矩阵相同,矩阵的每个位置保存从原点到达该位置的路径和(该路径和需判断是否向右走的产生小,还是向下走产生的小),即每走一步更新一次矩阵,当到达终点是,矩阵的最后一个元素即为最小路径和。
动态规划的步骤更为清晰,因此直接遍历元素即可达到效果。因此时间复杂度O(M×N)远小于递归。
这里我们进行一个简单的验证:
两个函数循环运行1000次

import datetime
import time
t1 = datetime.datetime.now().microsecond
t3 = time.mktime(datetime.datetime.now().timetuple())

for m in range(1000):
    f(v, 3, 3)

t2 = datetime.datetime.now().microsecond
t4 = time.mktime(datetime.datetime.now().timetuple())
strTime = '递归 use:%dms' % ((t4 - t3) * 1000 + (t2 - t1) / 1000)
print(strTime)

''''''
t1 = datetime.datetime.now().microsecond
t3 = time.mktime(datetime.datetime.now().timetuple())

for m in range(1000):
    minSum(v)

t2 = datetime.datetime.now().microsecond
t4 = time.mktime(datetime.datetime.now().timetuple())
strTime = '动态规划 use:%dms' % ((t4 - t3) * 1000 + (t2 - t1) / 1000)
print(strTime)

输出结果为:

12
12
递归 use:34ms
动态规划 use:21ms
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值