經典算法——動態規劃入門實例

經典算法——動態規劃入門實例

神馬是動態規劃

  • 專業定義:

    動態規劃的本質,是對問題狀態的定義狀態轉移方程的定義。通過拆分問題,定義問題狀態和狀態之間的關係,使得問題能夠以遞推(或者說分治)的方式去解決。

    看完之後或許你一個字也沒記住,這都是什麼鬼?

    這裏寫圖片描述

  • 關鍵點

    ok,讓我們摒棄這些專業的條條框框,直奔主題。其實動態規劃的核心就是拆解子問題——把一個大的問題拆解成逐步逐步的小問題,而這些小問題都可以直接由之前更小的問題得到。那麼怎麼拆解這些小問題呢?

    靠的就是狀態的定義狀態轉移方程的定義

    當然,這樣還是很抽象的。不如舉個栗子:

    這裏寫圖片描述

求解最長公共子串

  • 神馬是最長公共子串?

    假定現在有兩個字符串,最長公共子串就是它們之間公有的連續的最長子串,就像下圖這樣

    這裏寫圖片描述

    橙色部分就是最長的公有子串,記住這裏一定要連續。那麼問題來了?怎麼求出任意兩個字符串的最長公有子串呢?看似很簡單,不過細思又感覺有點點複雜的感覺。

  • 暴力求解

    簡單粗暴的方法——先求出str1的所有子串,然後在求出str2的所有子串,然後再一個個比較,找到相同且最大的那個不就好了。ok,我們先來看看要多少次:

    str1的可能情況:

    n+(n-1)+(n-1)+.......+2+1 = ((n+1)*n)/2

    同理str2也是一樣:

    m+(m-1)+(m-1)+.......+2+1 = ((m+1)*m)/2

    然後在通過兩個for循環一個一個比較就搞定了嘛;

    for i in range(((n+1)*n)/2):
      for i in range(((m + 1) * m) / 2):
          dosomthing()

    ok,這樣不就搞定了,so easy,不過感覺有點細思極恐,這也太暴力了,萬一字符串很大,這得算多久呀!心疼計算機3s。正所謂暴力膜法不可取,所以這時候我們的動態規劃就登場啦。

  • 最開始我們說過,用動態規劃算法時最重要的就是要拆解子問題,那麼我們如何將這個問題拆解爲更小的子問題呢?根據動態規劃的思想,我們首先要分析出如何根據已有的結果算出我們需要的結果,舉慄來說:

    這裏寫圖片描述

    ​ 假如我們已經求好了截至到str1[i-1]與str2[j-1]處兩個字符串的最大公有子串,是否可以幫助我們求出截止到str1[i]與str[j]處兩個字符串的最大公有子串呢?思考一下:

    ​ 爲了方便,我們將截止到(這裏的截止到要包含str1[i]與str2[j])str[i]與str[j]處的最大字符串長度記作lcs(i,j),假如:str1[i] == str2[j],那麼我們直接在lcs(i-1,j-1)後面加1不就是lcs(i,j)了嘛,如果str1[i] !=str2[j],最大公有子串到這裏就結束了,所以經過這兩個點沒有最大公有子串,直接記作0就好了。

    用數學公式就是:

    f(m,n)=0        str1[m] != str2[n] ;
    f(m,n)=f(m-1,n-1) + 1      str[m]==str2[n];

    轉化爲代碼就是:

    if str1[i]==str2[j]:
      lcs[i][j] = lcs[i-1][j-1] + 1
    else:
      lcs[i][j] = 0

    用一張圖片描述整體流程就是:

    這裏寫圖片描述

    ok,既然弄清楚了計算的流程,就可以寫出代碼,完整代碼如下:

    def lcs(mes1, mes2):
      if len(mes1) > len(mes2):
          mes1, mes2 = mes2, mes1
      data = [[0 for i in range(len(mes2) + 1)] for i in range(len(mes1) + 1)]
      mes1 = "$" + mes1
      mes2 = "$" + mes2
      max_lenth = 0
      end = 0
      for i in range(1, len(mes1)):
          for j in range(1, len(mes2)):
              if mes1[i] == mes2[j]:
                  data[i][j] = data[i - 1][j - 1] + 1
                  if data[i][j] > max_lenth:
                      max_lenth = data[i][j]
                      end = i
    
      if max != 0:
          result = mes1[end - max_lenth + 1:end + 1]
          print(result)
    
    
    if __name__ == '__main__':
      while True:
          try:
              mes1 = input()
              mes2 = input()
              lcs(mes1, mes2)
          except:
              break
    

這裏申請的二維數組比兩個字符串都要大1,原因爲我們呢將i,j爲0的邊都賦值爲了0(當然,這裏爲了方便直接把數組都初始化了爲0),有點像爲我們的動態規劃棋盤撒了半圈水雷一樣。參考上面的流程圖。這是一道經典的動態規劃題目,在華爲的筆試題裏面曾經出現過,上面代碼就是之前根據題目寫的,已經通過了測試。原題如下:

這裏寫圖片描述

求解最大公有子序列

  • 神馬是最大公有子序列

    乍一看,和最大公共子串就兩字之差,其實這兩個差的還是挺遠的。還是上面那兩個字符串:

    這裏寫圖片描述

    橙色部分就是他們的最大公有子序列。和子串最大的區別就是這裏的子串可以不用連續了,所以叫最大子序列了。這種算法有什麼用呢?

    比如:有次毛概老師讓你寫一篇5000字的論文,可是要交的前一天晚上你纔想起來怎麼辦?當然是網上隨便找一篇,但是你又怕老師發現是抄襲的,所以就會在文章開頭改點東西,文章中間改點,文章結尾改點,當你以爲萬事大吉的時候,如果老師剛好學過這個算法,那麼可以通過對比最大子序列找到文章的相似度了,然後就。。。。

  • 暴力求解

    。。。。。。。。。。。。這個子集比之前的高多了,有興趣朋友可以計算試試。。。。。。。

  • 其實仔細想想,這個和最大子串其實是一樣一樣的,就是遞推公式有一丟丟不同,希望大家稍微思考下,應該很快就寫出來了。完整代碼如下,建議對比之前代碼就會發現差別了:

    def lcs(mes1, mes2):
      if len(mes1) > len(mes2):
          mes1, mes2 = mes2, mes1
      data = [[0 for i in range(len(mes2) + 1)] for i in range(len(mes1) + 1)]
      mes1 = "$" + mes1
      mes2 = "$" + mes2
      max_lenth = 0
      end = 0
      for i in range(1, len(mes1)):
          for j in range(1, len(mes2)):
              if mes1[i] == mes2[j]:
                  data[i][j] = data[i - 1][j - 1] + 1
              else:
                  data[i][j] = max(data[i - 1][j], data[i][j - 1])
      i = len(mes1) - 1
      j = len(mes2) - 1
      str = ""
      while i != 0 and j != 0:
          if mes1[i] == mes2[j]:
              str += mes1[i]
              i -= 1
              j -= 1
          else:
              if data[i][j - 1] > data[i - 1][j]:
                  j -= 1
              else:
                  i -= 1
      print(str[::-1])
    
    
    if __name__ == '__main__':
      lcs("abcdacgw", "acdacvgwc")

    其實就是拷貝的前面代碼,改了幾行代碼和輸出而已。

總結

  • 算法是一門很理論通過也很實踐的學問,建議先搞清流程,再寫代碼加深理解。本人算法菜鳥,如果錯誤,歡迎一起探討。共同學習進步。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章