【技術積累】演演算法中的動態規劃【二】

2023-06-09 18:00:41

動態規劃的優缺點是什麼? 

動態規劃的優點是:

  1. 可以解決一些複雜的問題,例如揹包問題、最長公共子序列問題等;
  2. 可以通過記憶化搜尋來避免重複計算,提高效率;
  3. 可以通過狀態轉移方程來簡化問題,使問題更易於理解和解決;
  4. 可以處理連續的問題,例如最大子段和問題。

動態規劃的缺點是:

  1. 對於某些問題,動態規劃的時間和空間複雜度非常高,難以處理大規模的問題;
  2. 動態規劃需要構建狀態轉移方程,需要一定的數學能力和思維能力;
  3. 動態規劃無法處理一些特殊的問題,例如NP完全問題。

動態規劃和遞迴的區別?

動態規劃和遞迴的區別在於它們解決問題的方式。

遞迴是一種自上而下的解決問題的方法,它將問題分解成更小的子問題,並通過遞迴呼叫自身來解決這些子問題。

動態規劃則是一種自下而上的解決問題的方法,它從最小的子問題開始解決,然後通過計運算元問題的解來逐步解決更大的問題。

動態規劃通常會使用一個表格來儲存子問題的解,以避免重複計算。

因此,雖然遞迴和動態規劃都可以解決相同的問題,但它們的解決方式不同,動態規劃通常比遞迴更高效。

什麼是最優子結構?為什麼它對動態規劃很重要?

最優子結構是指問題的最優解包含其子問題的最優解。

也就是說,如果我們能夠通過求解子問題的最優解來得到原問題的最優解,那麼這個問題就具有最優子結構性質。

最優子結構對於動態規劃非常重要,因為它使得我們可以通過解決子問題來求解原問題,從而將原問題分解成更小的子問題。

這種分解問題的方式使得動態規劃可以高效地解決大規模的問題。

同時,最優子結構還可以幫助我們設計狀態轉移方程,以便我們能夠通過子問題的解來計算原問題的解。

因此,最優子結構是動態規劃的一個重要概念,它使得我們能夠高效地解決許多複雜的問題。

什麼是重疊子問題?如何避免它們?

重疊子問題是指在解決一個問題的過程中,需要多次求解同一個子問題。這種重複計算會浪費計算資源,導致演演算法效率降低。

動態規劃通過記憶化搜尋來避免重疊子問題。

在動態規劃中,我們通常會使用一個表格來儲存子問題的解,以便在需要時可以直接查詢,避免重複計算。

具體來說,當我們需要解決一個子問題時,我們首先檢查表格中是否已經有了這個子問題的解,如果有,我們就直接返回表格中的解。

如果沒有,我們就計算這個子問題的解,並將其儲存到表格中。

這樣,當我們需要解決同一個子問題時,就可以直接從表格中查詢,避免重複計算,提高演演算法效率。

什麼是記憶化搜尋?它如何與動態規劃相關?

記憶化搜尋是一種優化搜尋演演算法的方式,它通過記憶已經計算過的結果來避免重複計算。

在記憶化搜尋中,我們通常會使用一個表格來儲存已經計算過的結果,以便在需要時可以直接查詢,避免重複計算。

記憶化搜尋通常用於解決遞迴問題,例如斐波那契數列等。 

動態規劃實際上就是一種記憶化搜尋的方法,它通過使用一個表格來儲存子問題的解,避免了重複計算。

動態規劃通常用於解決具有最優子結構的問題,例如揹包問題、最長公共子序列問題等。

因此,記憶化搜尋和動態規劃在本質上是相同的,都是通過記憶已經計算過的結果來避免重複計算,提高演演算法效率。

什麼是狀態轉移方程?如何構建它們?

狀態轉移方程是動態規劃中的一個重要概念,它描述瞭如何通過子問題的解來計算原問題的解。通常情況下,狀態轉移方程可以通過以下步驟來構建:

  1. 定義狀態:首先需要定義狀態,也就是問題的子問題的解。狀態應該儘量簡單,以便於計算。
  2. 定義狀態轉移方程:接下來需要定義狀態轉移方程,也就是描述如何通過子問題的解來計算原問題的解。狀態轉移方程應該儘量簡單,以便於計算,並且應該具有最優子結構性質。
  3. 初始化狀態:在動態規劃中,通常需要初始化一些狀態,以便於計算後續的狀態。初始化狀態應該儘量簡單,以便於計算。
  4. 計算狀態:最後需要計算狀態,也就是根據狀態轉移方程計算出原問題的解。在計算狀態時,應該遵循從小到大的順序,以便於使用已經計算過的狀態來計算後續的狀態。

總之,狀態轉移方程是動態規劃中的一個重要概念,它描述瞭如何通過子問題的解來計算原問題的解。構建狀態轉移方程的關鍵在於定義狀態和狀態轉移方程,以及初始化狀態和計算狀態。

動態規劃解決博弈論問題?

博弈論問題是指在多個參與者之間進行決策的情況下,如何制定最佳策略的問題。

在動態規劃中,我們通常會使用一個表格來儲存子問題的解,以便在需要時可以直接查詢,避免重複計算。

下面我們以經典的石子游戲問題為例,說明如何用動態規劃解決博弈論問題。

有一堆石子,兩個玩家輪流取走石子,每次可以取走1個、2個或3個石子,取走最後一個石子的玩家獲勝。假設兩個玩家都採取最優策略,問先手玩家是否必勝?

  1. 定義狀態:設f[i]表示還剩下i個石子時,先手玩家是否必勝。
  2. 定義狀態轉移方程:當還剩下i個石子時,先手玩家可以取走1個、2個或3個石子,然後變成後手玩家,後手玩家也可以取走1個、2個或3個石子,然後變成先手玩家。
  3. 因此,當先手玩家取走k個石子時,狀態方程為f[i] = !f[i-k](1<=k<=3)。
  4. 也就是說,當先手玩家取走k個石子時,如果後手玩家在剩下的i-k個石子中必敗,則先手玩家必勝,否則先手玩家必敗。
  5. 初始化狀態:f[0] = false,表示沒有石子時先手玩家必敗。
  6. 計算狀態:根據狀態轉移方程,從小到大計算f[i]的值,最終得到f[n]的值,表示還剩下n個石子時,先手玩家是否必勝。
stone_game(n):
    f = [False] * (n+1)
    f[0] = False
    for i in range(1, n+1):
        for k in range(1, 4):
            if i >= k and not f[i-k]:
                f[i] = True
                break
    return f[n]

以上就是用動態規劃解決博弈論問題的一個例子,通過定義狀態、狀態轉移方程、初始化狀態和計算狀態,我們可以解決多種博弈論問題。

動態規劃解決最大二分配問題?

最大二分匹配問題是指在一個二分圖中,找到最大的匹配數,即在圖中找到最多的不相交的邊數。

下面以一個簡單的例子來說明如何用動態規劃解決最大二分匹配問題。

有一個二分圖,其中左側有n個節點,右側有m個節點,二分圖中存在一些邊連線左側和右側的節點,找出最大的匹配數。

  1. 定義狀態:設f[i][j]表示左側的前i個節點和右側的前j個節點之間的最大匹配數。
  2. 定義狀態轉移方程:當考慮節點i和節點j之間的匹配時,有兩種情況:
    • 節點i和節點j不匹配,此時f[i][j] = f[i][j-1],即只考慮左側的前i個節點和右側的前j-1個節點之間的最大匹配數。
    • 節點i和節點j匹配,此時f[i][j] = f[i-1][j-1] + 1,即左側的前i-1個節點和右側的前j-1個節點之間的最大匹配數再加上節點i和節點j之間的一條邊。
  3. 初始化狀態:f[0][0] = 0,表示左側和右側都沒有節點時,匹配數為0。
  4. 計算狀態:根據狀態轉移方程,從小到大計算f[i][j]的值,最終得到f[n][m]的值,表示左側的前n個節點和右側的前m個節點之間的最大匹配數。
max_bipartite_matching(n, m, edges):
    f = [[0] * (m+1) for _ in range(n+1)]
    for i in range(1, n+1):
        for j in range(1, m+1):
            if (i-1, j-1) in edges:
                f[i][j] = f[i-1][j-1] + 1
            else:
                f[i][j] = max(f[i][j-1], f[i-1][j])
    return f[n][m]


以上就是用動態規劃解決最大二分匹配問題的一個例子,通過定義狀態、狀態轉移方程、初始化狀態和計算狀態,我們可以解決多種最大二分匹配問題。

動態規劃解決字串匹配問題?

字串匹配問題是指在一個字串中查詢另一個字串的位置。

下面以一個簡單的例子來說明如何用動態規劃解決字串匹配問題。

給定兩個字串s和t,判斷s中是否包含t。

  1. 定義狀態:設f[i][j]表示字串s的前i個字元和字串t的前j個字元是否匹配。
  2. 定義狀態轉移方程:當考慮字元s[i]和字元t[j]時,有兩種情況:
    • 字元s[i]和字元t[j]不匹配,此時f[i][j] = False。
    • 字元s[i]和字元t[j]匹配,此時f[i][j] = f[i-1][j-1],即字串s的前i-1個字元和字串t的前j-1個字元是否匹配。
  3. 初始化狀態:f[0][0] = True,表示兩個空字串是匹配的。
  4. 計算狀態:根據狀態轉移方程,從小到大計算f[i][j]的值,最終得到f[m][n]的值,表示字串s中是否包含字串t。
string_matching(s, t):
    m, n = len(s), len(t)
    f = [[False] * (n+1) for _ in range(m+1)]
    f[0][0] = True
    for i in range(1, m+1):
        for j in range(1, n+1):
            if s[i-1] == t[j-1]:
                f[i][j] = f[i-1][j-1]
            else:
                f[i][j] = False
    return f[m][n]


以上就是用動態規劃解決字串匹配問題的一個例子,通過定義狀態、狀態轉移方程、初始化狀態和計算狀態,我們可以解決多種字串匹配問題。

動態規劃解決序列模式匹配問題?

最大獨立集問題是指在一個無向圖中,找到一個最大的獨立頂點集合,使得這些頂點之間沒有邊相連。動態規劃是解決最大獨立集問題的一種常用方法。下面

給定一個無向圖G=(V,E),求出最大的獨立頂點集合。

  1. 定義狀態:設f[i]表示以頂點i為結尾的最大獨立集大小。
  2. 定義狀態轉移方程:當考慮頂點i時,有兩種情況:
    • 頂點i不在最大獨立集中,此時f[i] = f[i-1]
    • 頂點i在最大獨立集中,此時f[i] = f[i-2] + w[i],其中w[i]表示頂點i的權值。
  3. 因為如果頂點i在最大獨立集中,那麼頂點i-1一定不在最大獨立集中,所以f[i] = f[i-2] + w[i]。
  4. 初始化狀態:f[0] = 0,f[1] = w[1]。
  5. 計算狀態:根據狀態轉移方程,從小到大計算f[i]的值,最終得到最大的獨立集大小為max(f[i])。
max_independent_set(w):
    n = len(w)
    f = [0] * (n+1)
    f[1] = w[0]
    for i in range(2, n+1):
        f[i] = max(f[i-1], f[i-2] + w[i-1])
    return f[n]

以上就是用動態規劃解決最大獨立集問題的一個例子,通過定義狀態、狀態轉移方程、初始化狀態和計算狀態,我們可以解決多種最大獨立集問題。