美文网首页算法算法与数据结构
浅层理解动态规划及利用动态规划解决最长公共子串等问题

浅层理解动态规划及利用动态规划解决最长公共子串等问题

作者: 不会编程的程序猿甲 | 来源:发表于2019-09-29 14:37 被阅读0次

    动态规划基本思想


    动态规划的工作原理是先解决子问题,再逐步解决大问题。


    用动态规划解决旅游规划问题


    目前面对的问题是,有A、B、C、D、E五个地点想要去参观,每个地点的评分不同,花费的时间也不同,假设你有两天的时间,怎么样能够在两天内参观达到评分最多的参观路线呢?各地点的花费时间以及评分如下:

    名胜 时间/天数 评分
    A 0.5 7
    B 0.5 6
    C 1 9
    D 2 9
    E 0.5 8

    动态规划可以看做是一个建立网格并且填写网格的过程,表格填好后问题也可解决。建立一个空白的网格如下:

    0.5 1 1.5 2
    A
    B
    C
    D
    E
      1. 填写A行
        这一行中,每一个网格只需要决定要不要去A地,填写当前网格可能的最高评分值,牢记目的是使评分最高,0.5代表只有0.5天时间,1代表有1天时间,以此类推,因此A行的网络填写如下,每个网格都选择去A地:
    A行网格评分.png
    • 2.填写B行
      B行填写时每个网格可选A.B两地,目的同样是选择评分更高的选项,准则如下,首先确定是否选择B,如果选择B,计算选B之后的空闲时间,并与空闲时间列的上一行最高评分值相加,作为这一网格的最高评分,若和大于上一行同列的评分则作为这一网格最终的评分,否则不选择B,继承上一行的评分。


      B行网格评分.png

      如B行第一列,假设选择B,则正好用完0.5天得到评分6,与上一行的7相比较小,因此,不选择B,继承上行的结果;第二列则为选择B后剩余0.5天,查找0.5天列的上一行,评分最高为7,故该网格的最高评分为7+6=13,超过上一行7,最终确定填写为13。

    • 3.填写剩余行
      每多一行,可选择的地点就多一个,填网格的法则如上,不在赘述,最终的网格如下:
      由图可知最终选择ACE三地参观可获得最高的评分。


      最终网格.png

    动态规划的注意事项:


    1.动态规划的结果不会随网格的行顺序而改变;
    2.在动态规划时每个网格只有两种选择,是or否,没有选择商品的一部分这种选项;
    3.动态规划的每个子问题都必须是离散独立的,不能依赖于其他子问题。


    动态规划查找最长公共子串


    动态规划要将某个指标最大化,最长子串指的是两个单词中相同字母最多的字符串,例如red和reg的最长公共子串是re,利用动态规划解决该问题单元格的值为共同字符串长度值,横纵坐标分别为两个单词,最终的结果为单元格中的最大值而不是最后一个单元格的值。

    • 计算公式:
      1.如果两个字母不同,值为0;
      2.如果两个字母相同,值为左上角邻居的值加1;

    举例,fish和fosh的最长公共子串的最长公共子串长度为2,是sh


    最长公共子串.png
    • python 代码实现
    #动态规划解决最长公共子串问题
    def findCommenstr(str1,str2):
        cell = [[0 for x in range(len(str2))] for y in range(len(str1))]
        max_ = 0
        max_id = -1
        for i in range(len(str1)):
            for j in range(len(str2)):
                if str1[i]==str2[i] and (i < 1 or j < 1): #确定第一行或者第一列的值
                    cell[i][j]=1
                elif str1[i]==str2[j]:   #如果相同,加上左上角的值
                    cell[i][j]=cell[i-1][j-1]+1  
                if cell[i][j] > max_:
                    max_ = cell[i][j]    #获取最大值
                    max_id = i           #获取行号
    
        sub_str = []          
        for i in range(max_id-max_+1,max_id + 1):
            sub_str.append(str1[i])
        return sub_str,max_
    
    a='fish'
    b='fosh'
    sub,str_len = findCommenstr(a,b)
    print(''.join(sub),str_len)
    

    动态规划查找最长公共子序列


    最长公共子序列:两个读单词都有的序列包含的字母数,即将各个公共子串的长度相加。子串要求在原字符串中是连续的,而子序列则只需保持相对顺序一致,并不要求连续
    举例fish和fosh的最长公共子序列是fsh,长度为3。

    最长公共子序列.png
    • 计算公式
      1.如果两个字母不同,就选择上方和左方邻居中较大的;
      2.如果两个字母相同,就将当前单元格的值设置为左上方单元格的值加1。

    • 得出公共子序列
      通常的学习资料中只有如何计算出最长公共子序列长度,没有将子序列具体串得出,笔者通过自己的研究,r如有问题,还请指教,笔者认为得出子序列输出的步骤如下:
      1.得到网格最大值的位置,将最大值对应的字符添加到子序列集合,
      当该位置不是第一行或者第一列时,将该值与左上方的结果比较:
      如果左上邻居小于当前值,将左上的邻居加入子序列集合;
      否则,先将当前值pop出集合,再将左上邻居添加到子序列集合;
      依次向网格左上方移动,直到遍历完网格的位置。

    python实现代码如下:

    # chapter 9 动态规划解决两个单词最长公共子序列问题 
    def findCommenstr(str1,str2):
        cell = [[0 for x in range(len(str2))] for y in range(len(str1))]
        max_ = 0
        max_id = -1
        flag = []
        max_id_x =-1
        max_id_y =-1
        for i in range(len(str1)):
            for j in range(len(str2)):
                if str1[i]==str2[j] and (i<1 or j < 1): #确定第一行第一列的值
                    cell[i][j]=1
                elif str1[i]==str2[j]:                  #如果相同,加上左上角的值
                    cell[i][j]=cell[i-1][j-1]+1
                else:
                    cell[i][j]=max(cell[i-1][j],cell[i][j-1]) if i>0 else cell[i][j-1]
                if cell[i][j] > max_:
                    max_ = cell[i][j]    #获取最大值
                    max_id_x = i         #获取行号
                    max_id_y = j
        
        if max_id_x !=-1:                        #如果没有公共子序列则flag为[]
            flag.append(str1[max_id_x])                     
        while (max_id_x > 0 and max_id_y > 0):   #如果有公共子序列,回溯确定公共子串 
            if cell[max_id_x-1][max_id_y-1] < cell[max_id_x][max_id_y] and cell[max_id_x-1][max_id_y-1]>0:
                flag.append(str1[max_id_x-1])
            elif cell[max_id_x-1][max_id_y-1] == cell[max_id_x][max_id_y] and cell[max_id_x-1][max_id_y-1] > 0:
                flag.pop(-1)
                flag.append('*-*')                  #分隔子串的标记
                flag.append(str1[max_id_x-1])      #如果相等先把当前的拿出然后再添加左上角值
            else:                                 #如果当前表格已经为0,则结束
                flag.reverse()
                return flag,max_            
            max_id_x-=1
            max_id_y-=1
                    
        flag.reverse()
        return flag,max_
    
    a='sish'
    b='wosh'
    s,str_len = findCommenstr(a,b)
    print('Str1 is',a)
    print('Str2 is',b)
    print('The common string is ',s, ', And the length is ',str_len)    
    

    代码测试结果:


    最长子序列代码测试结果.png

    相关文章

      网友评论

        本文标题:浅层理解动态规划及利用动态规划解决最长公共子串等问题

        本文链接:https://www.haomeiwen.com/subject/ckcnuctx.html