五大經常使用算法之動態規劃算法

Authonr: Lijb算法

Email: lijb1121@163.com數組

五大經常使用算法簡介安全

一、遞歸與分治
遞歸算法:直接或者間接不斷反覆調用自身來達到解決問題的方法。這就要求原始問題能夠分解成相同問題的子問題。
示例:階乘、斐波納契數列、漢諾塔問題
 
斐波納契數列:又稱黃金分割數列,指的是這樣一個數列:一、一、二、三、五、八、1三、2一、……在數學上,斐波納契數列以以下被以遞歸的方法定義:F1=1,F2=1,Fn=F(n-1)+F(n-2)(n>2,n∈N*))。
 
分治算法:待解決複雜的問題可以簡化爲幾個若干個小規模相同的問題,而後逐步劃分,達到易於解決的程度。
一、將原問題分解爲n個規模較小的子問題,各子問題間獨立存在,而且與原問題形式相同
二、遞歸的解決各個子問題
三、將各個子問題的解合併獲得原問題的解
 
示例:棋盤覆蓋、找出僞幣、求最值
 
棋盤覆蓋:在一個(2^k)*(2^k)個方格組成的棋盤上,有一個特殊方格與其餘方格不一樣,稱爲特殊方格,稱這樣的棋盤爲一個特殊棋盤。要求對棋盤的其他部分用L型方塊填滿
 
二、動態規劃
動態規劃與分治法類似,都是組合子問題的解來解決原問題的解,與分治法的不一樣在於:分治法的子問題是相互獨立存在的,而動態規劃應用於子問題重疊的狀況。
動態規劃方法一般用來求解最優化問題,這類問題能夠有不少可行解,每一個解都有一個值,找到具備最優值的解稱爲問題的一個最優解,而不是最優解,可能有多個解都達到最優值。
設計動態規劃算法的步驟:
一、刻畫一個最優解的結構特徵
二、遞歸地定義最優解的值
三、計算最優解的值,一般採用自底向上的方法
四、利用算出的信息構造一個最優解
示例:0-1揹包問題,鋼條切割問題等。
 
三、貪心算法
貪心算法是就問題而言,選擇當下最好的選擇,而不從總體最優考慮,經過局部最優但願致使全局最優。
貪心算法的要素
1)貪心選擇性質:能夠經過局部最優選擇來構造全局最優解。換言之,直接作出在當前問題中看來最優的選擇,而沒必要考慮子問題的解。
2)最優子結構:一個問題的最優解包含其子問題的最優解。
貪心算法的設計步驟:
1)將最優化問題轉換爲這樣的形式:對其作出一次選擇後,只剩下一個子問題須要求解
2)證實作出貪心選擇後,原問題老是存在最優解,即貪心選擇老是安全的
3)證實作出貪心選擇後,剩餘的子問題知足性質:其最優解與貪心選擇組合便可獲得原問題的最優解,這樣就獲得了最優子結構。
示例:揹包問題,均分紙牌,最大整數
 
四、回溯法
回溯法是一種搜索算法,從根節點出發,按照深度優先搜索的策略進行搜索,到達某一節點後 ,探索該節點是否包含該問題的解,若是包含則進入下一個節點進行搜索,如果不包含則回溯到父節點選擇其餘支路進行搜索。
 
回溯法的設計步驟:
1)針對所給的原問題,定義問題的解空間
2)肯定易於搜索的解空間結構
3)以深度優先方式搜索解空間,並在搜索過程當中用剪枝函數除去無效搜索。
示例:0-揹包問題、旅行商問題、八皇后問題
 
五、 分支限界法
和回溯法類似,也是一種搜索算法,但回溯法是找出問題的許多解,而分支限界法是找出原問題的一個解。或是在知足約束條件的解中找出使某一目標函數值達到極大或極小的解,即在某種意義下的最優解
在當前節點(擴展節點)處,先生成其全部的兒子節點(分支),而後再從當前的活節點(當前節點的子節點)表中選擇下一個擴展節點。爲了有效地選擇下一個擴展節點,加速搜索的進程,在每個活節點處,計算一個函數值(限界),並根據函數值,從當前活節點表中選擇一個最有利的節點做爲擴展節點,使搜索朝着解空間上有最優解的分支推動,以便儘快地找出一個最優解。
 
分支限界法:
1)FIFO分支限界法
3)優先隊列分支限界法:按照優先隊列中規定的優先級選取優先級最高的節點成爲當前擴展節點。
 
示例:裝載問題,旅行售貨員問題

動態規劃算法函數

編輯距離問題優化

什麼是兩個字符串的編輯距離(edit distance)?給定字符串s1和s2,以及在s1上的以下操做:.net

  • 插入(Insert)一個字符
  • 移除(Remove)一個字符
  • 替換(Replace)一個字符

試問最小須要多少次這樣的操做才能使得s1轉換爲s2?設計

好比,單詞「cat」和「hat」,這樣的操做最少須要一次,只須要把「cat」中的「c」替換爲「h」便可。單詞「recall」和「call」,這樣的操做最少須要兩次,只須要把「recall」中的「r」和「e」去掉便可。單詞「Sunday」和「Saturday」,這樣的操做最少須要3次,在「Sunday」的「S」和「u」中插入「a」和「t」,再把「n」替換成「r」便可。code

那麼,是否存在一種高效的算法,可以快速、準確地計算出兩個字符串的編輯距離呢?遞歸

動態規劃算法隊列

咱們使用動態規劃算法(Dynamic Programming)來計算出兩個字符串的編輯距離。 咱們從兩個字符串s1和s2的最末端向前遍從來考慮。假設s1的長度爲m,s2的長度爲n,算法以下:

  1. 若是兩個字符串的最後一個字符同樣,那麼,咱們就能夠遞歸地計算長度爲m-1和n-1的兩個字符串的情形;
  2. 若是兩個字符串的最後一個字符不同,那麼,進入如下三種情形:
    • 插入: 遞歸地計算長度爲m和n-1的兩個字符串的情形,這是由於在s1中的末端插入了一個s2的最後一個字符,這樣s1和s2的末端字符同樣,就是1中情形;
    • 刪除: 遞歸地計算長度爲m-1和n的兩個字符串的情形,這是在s1中的末端刪除了一個字符;
    • 替換: 遞歸地計算長度爲m-1和n-1的兩個字符串的情形,這是由於把s1中末端字符替換成了s2的最後一個字符,這樣s1和s2的末端字符同樣,就是1中情形;

這樣,咱們就有了子結構問題。對於動態規劃算法,咱們還須要一個初始化的過程,而後中間維護一張二維表便可。初始化的過程以下: 若是m爲0,則至少須要操做n次,即在s1中逐個添加s2的字符,一共是n次;若是n爲0,則至少須要操做m次,即把s1的字符逐個刪除便可,一共是m次。

Python實現

利用DP算法解決兩個字符串的編輯距離的Python代碼以下:

'''
[@author](https://my.oschina.net/arthor) lijb
[@company](https://my.oschina.net/u/3478402) 東網科技
[@date](https://my.oschina.net/u/2504391) 2019/3/22  10:06
'''

#使用動態規劃算法計算字符串的編輯距離
#傳入兩個字符串類型的參數
def edit_distance(s1, s2):

   m, n = len(s1), len(s2)
   #建立數組
   dp = [[0 for _ in range(n+1)] for _ in range(m+1)]

   # 使用動態規劃算法
   for i in range(m + 1):
       for j in range(n + 1):
           if i == 0:
               dp[i][j] = j
           elif j == 0:
               dp[i][j] = i
           elif s1[i-1] == s2[j-1]:
               dp[i][j] = dp[i-1][j-1]
           else:
               dp[i][j] = 1 + min(dp[i][j-1],  # Insert
                                  dp[i-1][j],  # Remove
                                  dp[i-1][j-1])  # Replace

   return dp[m][n]


# Driver program
str1 = "ljb"
str2 = "lijb"
edit_dist = edit_distance(str1, str2)
print("The Edit Distance of '%s' and '%s' is %d."%(str1, str2, edit_dist))

輸出結果以下:

The Edit Distance of 'ljb' and 'lijb' is 1.

Java實現

利用DP算法解決兩個字符串的編輯距離的Java代碼以下:

package com.neunn;

/**
 * [@author](https://my.oschina.net/arthor) lijb
 * [@company](https://my.oschina.net/u/3478402) 東網科技
 * @date 2019/3/22  10:06
 */
public class Dynamic_Programming {

    public static void main(String[] args) {
        String str1 = "lijb";
        String str2 = "ljb";
        int edit_dist = edit_distance(str1, str2);
        System.out.println("The Edit Distance of" + str1 + " and " + str2 + " is: " + edit_dist);
    }

    /**
     * 計算兩個字符串的編輯距離(Edit Distance)
     * @param str1                字符串參數1
     * @param str2                字符串參數2
     * @return                     編輯距離
     */
    public static int edit_distance(String str1, String str2) {
        int m = str1.length();
        int n = str2.length();
        // 初始化表格,用於維護子問題的解
        int[][] dp = new int[m + 1][n + 1];
        for (int i = 0; i <= m; i++)
            for (int j = 0; j <= n; j++)
                dp[i][j] = 0;

        /**
         * 使用動態規劃算法
          */
        for (int i = 0; i <= m; i++) {
            for (int j = 0; j <= n; j++) {
                if (i == 0) {
                    dp[i][j] = j;
                } else if (j == 0) {
                    dp[i][j] = i;
                } else if (str1.charAt(i - 1) == str2.charAt(j - 1)) {
                    dp[i][j] = dp[i - 1][j - 1];
                } else {
                    /*
                     * dp[i][j-1]: Insert
                     * dp[i-1][j]: Remove
                     * dp[i-1][j-1]: Replace
                     */
                    dp[i][j] = 1 + min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]);
                }
            }
        }
        return dp[m][n];
    }

    public static int min(int i, int j) {
        return (i <= j) ? i : j;
    }
}

輸出結果以下:

The Edit Distance of ljb and lijb is: 1

Scala實現

利用DP算法解決兩個字符串的編輯距離的Scala代碼以下:

package com.neunn

/**
  * @author lijb
  * @company 東網科技
  * @date 2019/3/22  10:19
  */
class Dynamic_Programming {

}

object Dynamic_Programming {
  def main(args: Array[String]): Unit = {
    val str1 = "ljb";
    val str2 = "lijb";
    val edit_dist = edit_distance(str1, str2)
    println("The Edit Distance of " + str1 + " and " + str2 + " is: " + edit_dist);
  }

  def edit_distance(str1: String, str2: String): Int = {
    val m = str1.length
    val n = str2.length
    /**
      * 建立數組並初始化
      */
    val dp = Array.ofDim[Int](m + 1, n + 1)
    for (i <- 0 to m) {
      for (j <- 0 to n) {
        dp(i)(j) = 0
      }
    }

    /**
      * 使用動態規劃算法
      */
    for (i <- 0 to m) {
      for (j <- 0 to n) {
        if (i == 0) {
          dp(i)(j) = j
        } else if (j == 0) {
          dp(i)(j) = i
        } else if (str1.charAt(i - 1) == str2.charAt(j - 1)) {
          dp(i)(j) = dp(i - 1)(j - 1)
        } else {
          dp(i)(j) = 1 + min(min(dp(i)(j - 1), dp(i - 1)(j)), dp(i - 1)(j - 1))
        }
      }
    }
    dp(m)(n)
  }

  def min(i: Int, j: Int): Int = if (i <= j) i
  else j

}

輸出結果以下:

The Edit Distance of ljb and lijb is: 1
相關文章
相關標籤/搜索