求2個(gè)字符串的最長公共子序列和最長公共子字符串

一. 最長公共子序列

定義:

一個(gè)數(shù)列S,如果分別是兩個(gè)多個(gè)已知數(shù)列子序列,且是所有符合此條件序列中最長的,則 S 稱為已知序列的最長公共子序列
例如:輸入兩個(gè)字符串BDCABAABCBDAB,字符串 BCBABDAB 都是是它們的最長公共子序列,則輸出它們的長度4,并打印任意一個(gè)子序列. (Note: 不要求連續(xù))

判斷字符串相似度的方法之一 - 最長公共子序列越長,越相似。

思路:

窮舉方法:
窮舉法是解決最長公共子序列問題最容易想到的方法,即對(duì)S的每一個(gè)子序列,檢查是否為T的子序列,從而確定它是否為ST的公共子序列,并且選出最長的公共子序列。

ST的所有子序列都檢查過后即可求出ST的最長公共子序列。S的一個(gè)子序列相應(yīng)于下標(biāo)序列1,2,...,n的一個(gè)子序列。因此,S共有2^n個(gè)子序列。當(dāng)然,T也有2^m個(gè)子序列。

因此,蠻力法的時(shí)間復(fù)雜度為O(2^n * 2^m),這是指數(shù)級(jí)別。

動(dòng)態(tài)規(guī)劃方法:

最優(yōu)子結(jié)構(gòu)性質(zhì):

設(shè)序列 X=<x1, x2, …, xm>Y=<y1, y2, …, yn> 的一個(gè)最長公共子序列 Z=<z1, z2, …, zk>,則:

  1. xm = yn,則 zk = xm = ynZk-1Xm-1Yn-1 的最長公共子序列;

    image.png

  2. xm ≠ yn, 要么ZXm-1Y 的最長公共子序列,要么 ZXYn-1 的最長公共子序列。

  • xm ≠ ynzk≠xm ,則 ZXm-1Y 的最長公共子序列;

  • xm ≠ yn 且 zk ≠yn ,則 ZXYn-1 的最長公共子序列。

綜合一下:就是求二者的大者

image.png

遞歸結(jié)構(gòu)容易看到最長公共子序列問題具有子問題重疊性質(zhì)。例如,在計(jì)算 XY 的最長公共子序列時(shí),可能要計(jì)算出 XYn-1Xm-1Y最長公共子序列。而這兩個(gè)子問題都包含一個(gè)公共子問題,即計(jì)算 Xm-1Yn-1最長公共子序列

image.png

計(jì)算最優(yōu)值:
子問題空間中,總共只有O(m*n)個(gè)不同的子問題,因此,用動(dòng)態(tài)規(guī)劃算法自底向上地計(jì)算最優(yōu)值能提高算法的效率。

長度表C 和 方向變量B:

image.png

實(shí)現(xiàn)代碼:

/**
 找出 兩個(gè) 字符串 的公共 子序列(動(dòng)態(tài)規(guī)劃)
 @param str1 字符串1
 @param str2 字符串2
 */
void maxPublicSubSequence(char *str1, char *str2) {
    assert(str1 != NULL && str2 != NULL);
    
    // 字符串 1 長度
    int str1Length = strlen(str1);
    // 字符串 2 長度
    int str2Length = strlen(str2);
    // 開辟 二維 存儲(chǔ) 數(shù)組 (并初始化 值為:0)
    
    int **postionArray = (int **)malloc(sizeof(int *) * (str1Length + 1));
    
    for (int i = 0; i <= str1Length; i ++) {
        postionArray[i] = (int *)malloc(sizeof(int) * (str2Length + 1));
    }
    
    for (int i = 0; i <= str1Length; i++) {
        for (int j = 0; j <= str2Length; j++) {
            postionArray[i][j] = 0;
        }
    }
    
    
    // 循環(huán) 遍歷
    for(int i = 1; i <= str1Length; i++) {
        for(int j = 1; j <= str2Length; j ++) {
            // 如果 兩個(gè)字符 相同
            if (str1[i - 1] == str2[j - 1]) {
                 postionArray[i][j] = postionArray[i - 1][j - 1] + 1;

            }
            // 如果 兩個(gè) 字符 不同
            else {
               postionArray[i][j] = (postionArray[i - 1][j] > postionArray[i][j - 1]) ? postionArray[i - 1][j] : postionArray[i][j - 1];
            }
            
        }
    }
    
    for (int i = 0; i < str1Length; i++) {
        for (int j = 0; j < str2Length; j++) {
            printf("%d ", postionArray[i][j]);
        }
        printf("\n");
    }
    
    
    
    int i, j ;
    for (i = str1Length, j = str2Length; i >= 1  && j >= 1;) {
        if (str1[i - 1] == str2[j - 1]) {
            printf("%c ", str1[i - 1]);
            i --;
            j --;
        }
        else {
            if (postionArray[i][j - 1] > postionArray[i - 1][j]) {
                j--;
            }
            else {
                i --;
            }
        }
    }
    
    // 釋放開辟數(shù)組
    for (int i = 0; i < str1Length; i++) {
        free(postionArray[i]);
    }
    free(postionArray);
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        maxPublicSubSequenceTwo("ABCBDAB" , "BDCABA");
    }
    return 0;
}

二: 最長公共子串

題目:

給定兩個(gè)字符串,求出它們之間最長的相同子字符串的長度。

思路:

窮舉法:

給定兩個(gè)字符串AB,我們可以通過從A的第一個(gè)字符開始與B對(duì)應(yīng)的每一個(gè)字符進(jìn)行對(duì)比的方式找到最長的公共字串。如果此時(shí)沒有找到匹的字母,則移動(dòng)到A的第二個(gè)字符處,然后從B的第一個(gè)字符處進(jìn)行對(duì)比,以此類推。

實(shí)現(xiàn)代碼:

#import <Foundation/Foundation.h>
/**
 找出 兩個(gè) 字符串 的 最長 公共 子串 (窮舉法)
 @param str1 字符串1
 @param str2 字符串2
 */
void maxPublicSubStringOne(char *str1, char *str2) {
    assert(str1 != NULL && str2 != NULL);
    
    // 起始 位置
    int startPosition = 0;
    // 公共 子串 長度
    int maxStringLength = 0;
    
    // 循環(huán) 遍歷 所有 子字符串
    for (int i = 0; i < strlen(str1); i ++) {
        for (int j = 0; j < strlen(str2); j++) {
            // 如果 兩個(gè) 字符 相等
            if(str1[i] == str2[j]) {
                // 繼續(xù) 比較 后面的字符
                int k = 1;
                while (str1[i + k] == str2[j + k] && str1[i + k] != '\0' && str2[j + k] != '\0') {
                    k ++;
                }
                // 如果 k 大于 最長 字符串
                if (k > maxStringLength) {
                    // 公共 子串 長度
                    maxStringLength = k;
                    // 起始位置
                    startPosition = i;
                }
            }
        }
    }
    
    if(maxStringLength > 0) {
        for (int i = startPosition; i <= maxStringLength; i++) {
            printf("%c ", str1[i]);
        }
    }
}

動(dòng)態(tài)規(guī)劃-空間換時(shí)間

采用一個(gè)二維矩陣來記錄中間結(jié)果,矩陣的橫坐標(biāo)字符串1的各個(gè)字符,矩陣的縱坐標(biāo)字符串2的各個(gè)字符。

舉例說明:假設(shè)兩個(gè)字符串分別為"bab""caba" (當(dāng)然我們現(xiàn)在一眼就可以看出來最長公共子串是"ba""ab")

   b  a  b

c  0  0  0

a  0  1  0

b  1  0  1

a  0  1  0

可以看出,矩陣的斜對(duì)角線最長的那個(gè)就對(duì)應(yīng)著兩個(gè)字符串的最長公共子串

不過在二維矩陣上找最長的由1組成的斜對(duì)角線也是件麻煩費(fèi)時(shí)的事,可以用下面的方法改進(jìn):當(dāng)要在矩陣是填1時(shí)讓它等于其左上角元素加1

   b  a  b

c  0  0  0

a  0  1  0

b  1  0  2

a  0  2  0

這樣矩陣中的最大元素就是最長公共子串的長度。另外,在構(gòu)造這個(gè)二維矩陣的過程中由于得出矩陣的某一行后其上一行就沒用了,所以實(shí)際上在程序中可以用一維數(shù)組來代替這個(gè)矩陣。

實(shí)現(xiàn)代碼:

/**
 找出 兩個(gè) 字符串 的公共 子串(動(dòng)態(tài)規(guī)劃)
 @param str1 字符串1
 @param str2 字符串2
 */
void maxPublicSubStringTwo(char *str1, char *str2) {
    assert(str1 != NULL && str2 != NULL);
    
    // 起始 位置
    int startPosition = 0;
    // 公共 子串 長度
    int maxStringLength = 0;
    // 字符串 1 長度
    int str1Length = strlen(str1);
    // 字符串 2 長度
    int str2Length = strlen(str2);
    // 開辟 二維 存儲(chǔ) 數(shù)組 (并初始化 值為:0)
    
    int **postionArray = (int **)malloc(sizeof(int *) * str1Length);
    
    for (int i = 0; i < str1Length; i ++) {
        postionArray[i] = (int *)malloc(sizeof(int) * str2Length);
    }
    
    for (int i = 0; i < str1Length; i++) {
        for (int j = 0; j < str2Length; j++) {
            postionArray[i][j] = 0;
        }
    }
    
    
    // 循環(huán) 遍歷
    for(int i = 0; i < str1Length; i++) {
        for(int j = 0; j < str2Length; j ++) {
            // 如果 兩個(gè)字符 相同
            if (str1[i] == str2[j]) {
                // 判斷 釋放 是 邊界 情況
                if (i == 0 || j == 0) {
                    // 邊界 情況
                    postionArray[i][j] = 1;
                }
                // 非邊界 情況
                else {
                    postionArray[i][j] = postionArray[i - 1][j - 1] + 1;
                }
                
                if (postionArray[i][j] > maxStringLength) {
                    maxStringLength = postionArray[i][j];
                    startPosition = i - postionArray[i][j] + 1;
                }
            }
            
        }
    }
    
    // 打印 字符串
    if(maxStringLength > 0) {
        for (int i = 0; i <= maxStringLength; i++) {
            printf("%c ", str1[startPosition + i]);
        }
    }
    // 釋放開辟數(shù)組
    for (int i = 0; i < str1Length; i++) {
        free(postionArray[i]);
    }
    free(postionArray);
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容