動態規划之最長子串匹配問題


1.問題引出

最長字段匹配問題歸結如下:

假設存在兩個字符段:

A={'a', 'b', 'c', 'b', 'd', 'a', 'b'};

B= {'b', 'd', 'c', 'a', 'b', 'a'};

則A、B兩個字段的匹配即為A、B的公共子序列,如{‘a’}、{‘b’}、{‘b、‘d’}、{‘b’、’c’、’a’、’b’}均為該兩個字符段的公共子序列,而最長公共子序列則為以上子序列中長度最長的。如{‘b’、’c’、’a’、’b’},長度為4。

2.問題求解

2.1動態規划的簡述

動態規划:把多階段過程轉化為一系列單階段問題,利用各階段之間的關系,逐個求解。通俗的解釋就是將一個很大的問題轉化為一個一個很小的相關聯且可求解的子問題,每個子問題都可以被解決,並將結果存儲,下一個子問題通過查詢上一個子問題的結果進行求解,以此,只到最終問題被解決,這是一種以空間換取執行效率的方法。舉例:如計算2+3,如果用動態規划的思想的話可將其分割為求1+1,1+2,2+3三個子問題,求出1+1的結果存儲,求出1+2的結果存儲,通過查詢利用前兩個子問題的結果得到2+3,算出最終結果。

2.2最長公共子序列的動態規划描述

對於上面兩個子序列

A={'a', 'b', 'c', 'b', 'd', 'a', 'b'};

B= {'b', 'd', 'c', 'a', 'b', 'a'};

設兩字符段的最長公共子序列result[i][j] (i為字符段A的長度,j為字符段B的長度),

①當A[i]=B[j]時,表示該問題可規划為先找到A[i-1]與B[j-1]公共子序列值result[i-1][j-1],result[i][j] = result[i-1][j-1]+1. 定義序列類型trace[i][j] = 1,便於后期追蹤。

 ②當result[i-1][j] >= result[i][j-1]時,表示長度為i-1字段A與長度為j的字段 B的公共子序列,大於等於長度為i字段A與長度為j-1的字段 B,因此result[i][j] = result[i-1][j]; trace[i][j] = 2。

③當result[i-1][j] < result[i][j-1]時,表示長度為i-1字段A與長度為j的字段 B的公共子序列,小於長度為i字段A與長度為j-1的字段 B,此時result[i][j] = result[i][j-1];trace[i][j] = 3。

2.3代碼實現

#include <stdio.h>
#include <stdlib.h>
#include <iostream.h>

/**
* 最長公共子序列求解算法
* 
* @param lengthS1 第一個序列的長度
* @param lengthS2 第二個序列的長度
* @param s1 第一個序列頭指針
* @param s2 第二個序列頭指針
* @param result 最長公共子序列遞推求解結果矩陣,m+1行,n+1列
* @param trace 最長公共子序列追蹤矩陣,m+1行,n+1列
*/
int LCSLength(int lengthS1, int lengthS2, char* s1, char* s2, int** result, int** trace){
    int i;
    trace[0][0] = 0;  //trace[i][0]與trace[0][j]不用,可以用作標志位

    for(i=0; i<=lengthS1; i++) result[i][0] = 0;
    for(int j=1; j<=lengthS2; j++) result[0][j] = 0;

    for(i=1; i<=lengthS1; i++){
        for(int j=1; j<=lengthS2; j++){
            if(s1[i-1] == s2[j-1]) { //兩個序列尾部匹配
                result[i][j] = result[i-1][j-1]+1; 
                trace[i][j] = 1;//1表示繼續向左上[i-1][j-1]追蹤
            } 
            else if(result[i-1][j] >= result[i][j-1]){ //當前最長序列由s1尾部前移一位與s2構成
                result[i][j] = result[i-1][j];
                trace[i][j] = 2; //2表示繼續向上[i-1][j]追蹤
            }
            else {  //當前最長序列由s2尾部前移一位與s1構成
                result[i][j] = result[i][j-1];
                trace[i][j] = 3;  //3表示繼續向上[i][j-1]追蹤
            }
        }
    }
    return result[lengthS1][lengthS2];
}

/**
* 最長公共子序列追蹤構造算法
* @param tailS1 第一個序列尾部位置
* @param tailS2 第二個序列的尾部位置
* @param s1 第一個序列頭指針,根據追蹤矩陣在其中搜索公共子序列中字符
* @param trace 追蹤矩陣
*/
void LCSTraceback(int tailS1, int tailS2, char* s1, int** trace){
    
    if  (tailS1 == 0 || tailS2 == 0) return;
    
    if  (trace[tailS1][tailS2] == 1) {  //向左上追蹤[i-1][j-1]
        LCSTraceback(tailS1-1, tailS2-1, s1, trace);
        cout << s1[tailS1-1] << " ";
    }
    else if (trace[tailS1][tailS2] == 2) {  //繼續向上追蹤[i-1][j]
        LCSTraceback(tailS1-1, tailS2, s1, trace);
    }
    else LCSTraceback(tailS1, tailS2-1, s1, trace);
}

int main(){

    const int lengthS1 = 7, lengthS2 = 6;
    int i;
    char sequence1[lengthS1] = {'a', 'b', 'c', 'b', 'd', 'a', 'b'};
    char sequence2[lengthS2] = {'b', 'd', 'c', 'a', 'b', 'a'};

    char* s1 = sequence1;
    char* s2 = sequence2;

    cout << "s1: " << *s1;
    for(i=1; i<lengthS1; i++)
        cout << " " << *(s1 + i);
    cout<< endl;

    cout << "s2: " << *s2;
    for(i=1; i<lengthS2; i++)
        cout << " " << *(s2 + i);
    cout << endl;

    int** resultMatrix = new int* [lengthS1+1];//m+1行n+1列迭代結果矩陣
    for(i=0; i<lengthS1+1; i++)
        *(resultMatrix+i) = new int [lengthS2+1];

    int** traceMatrix = new int* [lengthS1+1];//m+1行n+1列追蹤矩陣
    for(i=0; i<lengthS1+1; i++)
        *(traceMatrix+i) = new int [lengthS2+1];

    cout << "The length of the longest common sequence is: " << LCSLength(lengthS1, lengthS2, s1, s2, resultMatrix, traceMatrix) << endl;

    cout << "The longest common sequece is: " << endl;
    LCSTraceback(lengthS1, lengthS2, s1, traceMatrix);
    return 0;
}
View Code

 

 

 


免責聲明!

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



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