演算法——動態規劃演算法求解字串的編輯距離

Inside_Zhang發表於2015-11-23

當有人讓你用遞迴演算法求解斐波那契數列以及字串的編輯距離時,所設的陷阱都是一致的(遞迴呼叫時的重複計算),解決方案也是一致的(引入備忘錄概念)。觀察樹形的層級呼叫關係,我們可以發現動態規劃隱式地嵌入了一種剪枝機制。

動態規劃版求解菲波那切數列

關於樸素遞迴求解菲波那切數列存在的重複計算的說明:

這裡寫圖片描述

紅色部分均是重複計算項,當遞迴呼叫的層次更多時,重複計算的問題更為嚴重。

unsigned __int64 memo[1000];
unsigned __int64 fib(size_t n)
{
    if (n<=1)
        return n;
    if (memo[n])
        return memo[n];
    return memo[n] = fib(n-1) + fib(n-2);
            // 賦值運算(=)的也存在返回值,返回值是`=`號左側的值
            // 對於第一次計算得到的值進行記錄備份,
            // 下次如果想得到該值,先進性判斷是否已經計算過了
            // 如果是,直接返回,見第二個if
}

動態規劃版求解字串編輯距離

我們來看樸素遞迴來求解字串編輯距離時可能出現的重複計算的問題(5, 5:表示的是,源字串和目標字串各自的長度為5):


這裡寫圖片描述

現在我們應用動態規劃的思想對樸素遞迴演算法進行改造,所謂動態規劃,其核心有二:

  1. 狀態的概念

    為遞迴介面增加狀態標識引數i

    i
    j
    j

    int editDist(char* src, char* dst);

    改造為(引入狀態的概念):

    int editDist(char* src, char* dst, int i, int j);
  2. 備忘錄概念

    通過定義相關的結構體實現

    typedef struct tagMemoRecord
    {
        int dist;
        int refCount;
    }MEMO_RECORD;
    // 作為全域性變數
    vector<vector<MEMO_RECORD>> memo(100, vector<MEMO_RECORD>(100));
int editDist(char* src, char* dst, int i, int j)
{
    if (memo[i][j].refCount)
    {
        ++memo[i][j].refCount;
        return memo[i][j].dist;
    }
    int dist = 0;
    if (strlen(src+i) == 0)
    {
        dist = strlen(dst+j);
    }
    else if(strlen(dst+j) == 0)
    {
        dist = strlen(src+i);
    }
    else
    {
        if (src[i] == dst[j])
        {
            dist = editDist(src, dst, i+1, j+1);
        }
        else
        {
            int editIns = editDist(src, dst, i, j+1) + 1;
            int editDel = editDist(src, dst, i+1, j) + 1;
            int editRep = editDist(src, dst, i+1, j+1) + 1;
            dist = std::min({editIns, editDel, editRep});
        }
    }
    memo[i][j].refCount = 1;
    return memo[i][j].dist = dist;
}

相關文章