データ構造 - 動的プログラミングアルゴリズム

1. 動的プログラミングの定義と基本的な考え方

動的プログラミングは、複雑な問題を単純な部分問題に分解する解決方法です。

動的プログラミングの基本的な考え方: 通常、特定の最適な特性を持つ問題を解決するために使用され、各問題を 1 回だけ解決しようとします。与えられた部分問題の解が計算されると、同じ部分問題が計算されるように記憶および保存されます。 -問題は次回必要になるので、問題を解くときは表を直接調べてください。動的プログラミングは、問題をボトムアップで (再帰的ではなく反復的に) 解決し、計算の繰り返しを回避します。

再帰の場合、これらの部分問題が解決され、次に部分問題の解が結合されて元の問題の解が得られます。違いは、これらのサブ問題が重複しており、サブ問題が解決された後、再度解決される可能性があることです。

したがって、これらの部分問題の解を保存することができ、部分問題が後で使用されるかどうかに関係なく、計算される限り、結果がテーブルに入力されます。次回使用するときは、結果を直接使用してください。これが動的計画法の基本的な考え方です。

例:

# 动态规划实现斐波那契数列
def fib(n):
    # 递推公式的边界条件
    if n == 0 or n == 1:
        return n

    dp = [0, 1, 0]

    for i in range(1, n):

        sum = dp[0] + dp[1]
        dp[0] = dp[1]
        dp[1] = sum

    return dp[1]
print(fib(10))

# 时间复杂度:O(n)
# 空间复杂度:O(1)

# 递归思路
def fib(n):
    if n == 0 or n == 1:
        return n
    return fib(n-1) + fib(n-2)
print(fib(10))

# 时间复杂度:O(2^n)
# 空间复杂度:O(n)

2. 動的計画法の基本ステップ

1.再帰または再帰式の結果と添え字の意味を保存できる配列を決定します。一般的には 1 次元配列 (簡単な質問) または 2 次元配列 (少し難しい) を使用して保存しますが、ここでは常に dp[] で表します。 [i] (インデックス位置に対応する配列)。要素)、2 次元配列 dp[i][j] (2 次元配列において、平たく言えば、行の対応するインデックス位置にある要素です)および列);

2. dp 配列の初期化。1 次元配列と 2 次元配列の場合、初期値は、次回結果を重ね合わせて表示するときに役立ちます。

例: フィボナッチ dp[0] = 0、dp[1] = 1、次に dp[2] = dp[0] + dp[1]。

3. 状態遷移方程式(漸化式)を求める。状態遷移とは、前のステージの状態を元に、このステージの状態(つまり、異なるステージ間の接続)を導出することであり、実際には、以前に使用した結果がこのステージでも適用できるかどうかがここにあります。

例: フィボナッチの fib(4) は 2 回再帰的に計算されますが、どちらも同じ計算方法 fib(4) = fib(3) + fib(2) を使用するため、この計算方法は再帰式です。動的正規化では、最初の計算結果を保存し、次回その結果を直接使用します。

4. 走査順序を決定します。例: フィボナッチ走査は前から後ろへ走査する順序を決定すると、より速く結果を得ることができます。

5. dp 配列を例に挙げます。上記の手順を完了した後、いくつかの例を示して推測するか、コードを完成させた後、コード エディターを使用できます。コードのデバッグには vscode と pycharm を使用します。

ここでは、LeetCode がこれら 5 つのステップを説明します

ステップ 1: 配列 dp を決定し、各計算のオペランドを格納するための m+1 行と n+1 列の配列を作成します。word1 と word2 は関連する演算を実行するために存在するため、2 次元配列を使用して word1 を word2 に変換し、使用する演算の最小数を節約できます。

dp = [[0] * (n+1) for i in range(m+1)

ここで、添え字 dp[i][j]の意味を理解する必要があります。つまり、word1 の最初の i 文字 (添え字 i-1) と word2 の最初の j 文字 (添え字 j-1) の間の最小編集距離です。例: word1 = 'op'、word2 = 'app'、次に dp[2][2]=X、これは 'op' と 'ap' の間の最小編集距離、つまり変換に必要な最小量を表します。 「op」から「ap」までが動作します。

ステップ 2: 配列 dp の初期値を決定します。テーブルに初期化を実装するには、dp[i-1][j-1]、つまり dp[i][j] の関係を理解する必要があります。

例: 演算対象の word1 と word2 に対応する最初の文字 dp[i][j] を見つけたい場合は、前のステップの最小オペランド、つまり dp[i-1][j-1] を見つける必要があります。ただし、対応する要素は実際には空であるため、初期値を決定するときは、dp[i-1][j-1] = 0 の状況を考慮する必要があります。

これで、初期値の1行目の要素が追加操作後にword2となる空文字のオペランド、1列目の要素が削除操作後にword1が空文字となるオペランドであると判断できます。 。

行: word1

列:word2

 「」

ヌル

ある AP アプリ アプリ りんご

「」

ヌル

0 1 2 3 4 5
ああ 1
オプ 2
オップ 3
オッパ 4
dp[0][0] = 0        # 第一个元素赋值为0
    for i in range(m+1):     # 对word1里的元素全部做删除操作
        dp[i][0] = i
    for j in range(n+1):     # 对word2里的元素进行添加操作
        dp[0][j] = j

3 番目のステップは、漸化式を決定することです。この問題の最も難しい部分は漸化式です。漸化式を理解する前に、まず考えられる状況を判断します。

1. 変換中に word1[i-1] == word2[j-1] の場合、演算を実行する必要はなく、添字 dp[i-1][j-1] の結果を直接保持できます。つまり、dp[i][j] == dp[i-1][j-1]となります。

2. word1[i-1] != word2[j-1] の場合、演算が発生する 3 つの方法を考慮する必要があります。

つまり: word1 は word1[i-1] を置き換えます: dp[i][j] = dp[i-1][j-1] + 1    

       word2 は要素を削除します: dp[i][j] = dp[i][j-1] + 1

       Word1 は要素を削除します: dp[i][j] = dp[i-1][j] + 1。これは word2 が要素を追加するのと同じです。

dp[i-1][j-1] dp[i-1][j]
dp[i][j-1] dp[i][j]

なぜここに + 1 を付けるのでしょうか?

これら 3 つの操作では、最後の結果が dp 配列に保持されており、dp[i][j] の結果を解決する必要があるためです。最後に、オペランドの最小値(オペランドの最小数)を求め、+1を求める。

if word1[i-1] == word2[j-1]:
    dp[i][j] = dp[i-1][j-1]
else:
    dp[i][j] = min(dp[i-1][j-1],dp[i][j-1],dp[i-1][j])+1

4 番目のステップは、配列の走査順序を決定することです。当然、4 つの再帰式を組み合わせる必要があり、走査順序が左から右、上から下であることがわかります。

ステップ 5: 例を通じて検証し、推測する

ここでは、導出に word1='oppa' と 'apple' を使用します。

行: word1

列:word2

 「」

ヌル

ある AP アプリ アプリ りんご

「」

ヌル

0 1 2 3 4 5
ああ 1 1 2 3 4 5
オプ 2 2 1 2 3 4
オップ 3 3 2 1 2 3
オッパ 4 3 3 2 2 3

動的計画法アルゴリズムの問​​題のほとんどはこの 5 つのステップに基づいていると言えるため、5 つのステップの意味に注意する必要があります。

動的計画法の考え方をよりよく理解するには、さらに多くの質問を研究する必要があります。

Pythonコードの実装:

def minDistance(word1, word2):

   m = len(word1)
   n = len(word2)
   # 当word1为空的时候,word2的长度就是需要编辑的距离
   if m == 0:
       return n
   
   # 当word2为空的时候,word1的长度就是需要编辑的距离
   if n == 0:
       return m
        
   # 创建一个m+1行n+1列的数组
   dp = [[0]*(n+1) for i in range(m+1)]

   dp[0][0] = 0
   
   # 从 word1[:i] 变成 空串(word2[:0]), 操作即**删除** i 次直到将 word1[:i] 删光到 空串
   for i in range(1,m+1):
       dp[i][0] = i
   
# 从 空串(word1[:0]) 变成 word2[:j], 操作即**插入** j 次直到将 空串 变成 word2[:j]
   for j in range(1,n+1):
       dp[0][j] = j

   for i in range(1, m+1):
       for j in range(1, n+1):
            # 考虑字符是否相等
            if word1[i-1] == word2[j-1]:
               # 如果相等就是上一步操作的步数  
               dp[i][j] = dp[i-1][j-1]
            else:
               # 如果不相等就是附近三个最小值+1
               dp[i][j] = 1+min(dp[i-1][j-1], dp[i-1][j], dp[i][j-1]) 

    return dp[m][n]

s1 = "oppa"
s2 = "apple"
print(minDistance(s1,s2))

ここで、閲覧していただいた皆様に感謝するとともに、ダイナミック プランニングについて理解するきっかけとなった情報を提供してくださった優秀な専門家の皆様に感謝いたします。

同一の記事がございましたら、著者にご連絡の上、リンクを貼っていただければ、記事内の該当箇所にリンクを貼らせていただきますので、よろしくお願いいたします。

おすすめ

転載: blog.csdn.net/m0_71145378/article/details/126837568