LCS問題動態規划方法的改進:時間復雜度O(mn*(min(m,n))),空間復雜度O(1)


LCS問題,即求兩個字符串的最長公共子序列的問題。該問題常用的解法有普通遞歸法和動態規划法。

  • 普通遞歸法方法采用了減而治之和分而治之的思想。但該算法存在大量子問題的重復計算,其時間復雜度為指數時間復雜度。
  • DP方法使用一個二維數組記錄每個子問題的結果,從而避免了子問題的重復計算,只需要根據一定的次序,如從底向上,從只有一個字符出發,一次填滿該數組,最后的DP[m][n]即為該問題的結果,同時可以根據DP數組將一個解結果還原。其時間復雜度和空間復雜度都為 \(\Theta(mn)\)

在對DP數組觀察的基礎上,筆者對動態規划法進行了改進。現一個具體實例來講解如何改進動態規划法。
兩個字符串分別為"EDUCATIONAL"和"ADVANTAGE",求這兩個字符串的最長公共子序列,其DP數組如下圖所示:
image
可以觀察到最長公共子序列的一個解為DATA,對應的相同的字母,以A為例,是A在第一個字符串和第二個字符串的位置和最小的這個字母,對於剩下的字母也是如此,因此我們可以采用這樣的策略來找到個一個LSC的解,即尋找兩個字符串中相同的字符,記錄下該字符在兩個字符串中的位置,當這兩個位置和最小時,該字符就是最長公共子序列的第一個字符,記錄下這個字符的坐標,用ir和jr表示,第二次循環從ir+1和jr+1處開始搜索,得到最長公共子序列的第二個字符,依次類推。
編寫程序的過程中,需要考慮到重復的字母,這里使用了一個函數來統計該字母在LSC解中,出現的次數,來查找該字母在兩個字符串中的位置。
循環結束的條件是兩個坐標之和有一個初始值minpos,當該minpos的初始值不改變時,說明尋找不到新的滿足條件的字母,則循環終止。其時間復雜度為\(\O(mn*min(n,n))\),空間復雜度為\(\O(1)\),程序如下:

// LCS_DP_improve.cpp -- 優化LCS的DP解法,使空間復雜度為常數復雜度,同時得到LCS的組成
// 課后作業3
// 空間復雜度O(1)時間復雜度O(mn*min(m,n))
#include <iostream>
#include <algorithm>
#include <string>
#include <cstring>
using std::string;

int timeschar(string s, char ch);               // 計算ch在s中出現的次數
int searchpos(string s, char ch, int n);        // 計算第n個ch出現的位置
int LCS_DP_improve(string A, string B, string & C);

int main()
{
    using namespace std;
    string str1 = "immaculate";
    string str2 = "computer";
    string str;
    int len = LCS_DP_improve(str1, str2, str);
    cout << str << endl;
    cout << "Length: " << len << endl;
    return 0;
}
int timeschar(string s, char ch)
{
    int len = s.size();
    int times = 0;
    for (int i = 0; i < len; i++)
        if (s[i] == ch)
            times++;
    return times;
}
int searchpos(string s, char ch, int n)
{
    int len = s.size();
    int timesch = 0;
    for (int i = 0; i < len; i++)
        if (s[i] == ch)
        {
            timesch++;
            if (timesch == n)
                return i;
        }
    return -1;          // 沒有找到,本問題不存在
}
int LCS_DP_improve(string A, string B, string & C)
{
    int lenA = A.size();
    int lenB = B.size();
    int minlen = lenA > lenB ? lenB : lenA; // 計算A和B的最小長度
    int ir = -1;     // 每次喜歡開始的位置
    int jr = -1;     // 同ir
    string tempA;    // 記錄每次尋找的位置
    for (int k = 0; k < minlen; k++)
    {
        int minpos = lenA + lenB;         // 記錄相等時最小的x,y的坐標,初始為最大值
        char ch;    //記錄找到的字母
        for (int i = ir + 1; i < lenA; i++)
            for (int j = jr + 1; j < lenB; j++)
                if(A[i] == B[j])
                {
                    if (minpos > i + j)
                    {
                        minpos = i + j;
                        ch = A[i];
                    }
                }
        if (minpos == lenA + lenB)      // 終止循環的條件
            break;
        C.push_back(ch);
        int timech = timeschar(C, ch);
        ir = searchpos(A, ch, timech);
        jr = minpos - ir;
    }
    return C.size();
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM