程式設計珠璣: 15章 字串 15.2尋找字串中的最長重複子串 -------解題總結
阿新 • • 發佈:2019-01-10
#include <iostream> #include <stdio.h> #include <sstream> #include <stdlib.h>//qsort using namespace std; /* 問題:給定一個文字檔案作為輸入,查詢其中最長的重複子字串。例如,“Ask not what your country can do for you, but what you can do for your country”中 最長的重複字串是"can do for you",第二長的是“your country”。 分析:這個是尋找最長重複字串,字首樹trie適用於進行字首和字串搜尋,字尾樹適合於查詢子串在字串中出現位置,這兩種輸入結構需要制定輸入的查詢字串, 但這道題目不會給出輸入的查詢字串,因此這兩種方法不行。 最長公共子序列求的是兩個字串公共部分,也不適用該方法。 最短摘要生成方法,是需要給定搜尋的字串陣列,不適用該題。 暴力破解方法:羅列子串的起始位置,設句子長度為n,羅列的時間複雜度為O(N^2),然後對每個子字串採用字尾樹來做,尋找出字尾樹中至少包含兩個起始位置的子串 作為結果返回,然後從中選擇出長度最長的。 字尾樹建樹時間:O(N^2),查詢時間O(N),總的時間複雜度:O(N^3) 書上解法: 1 簡單解法:遍歷兩個字串的起始位置,分別p,q,對p,q查詢公共字串的長度,查詢時間為O(N),遍歷兩個子串的起始位置是O(N),總的時間複雜度為O(N^3) 2 字尾陣列:遍歷字串str的起始位置i(i從0到字串長度length-1),產生字尾陣列str[0...length-1],str[1...length-1],..str[length-1...length-1], 對字串陣列進行排序,使得字尾相近的子串集中在一起,對排序後的子串掃描比較相鄰遠古三,找出最長重複的字串。 時間複雜度為O(NLogN * N),最後面的n是字串比較的時間複雜度 輸入: abcdabcd 輸出: abcd 關鍵: 1 1】 簡單解法:遍歷兩個字串的起始位置,分別p,q,對p,q查詢公共字串的長度,查詢時間為O(N),遍歷兩個子串的起始位置是O(N),總的時間複雜度為O(N^3) 2】字尾陣列:遍歷字串str的起始位置i(i從0到字串長度length-1),產生字尾陣列str[0...length-1],str[1...length-1],..str[length-1...length-1], 對字串陣列進行排序,使得字尾相近的子串集中在一起,對排序後的子串掃描比較相鄰遠古三,找出最長重複的字串。 時間複雜度為O(NLogN * N),最後面的n是字串比較的時間複雜度 2 //字串比較,這裡發現傳入的是字串的首字元地址後,發現沒有排序成功 int myStrcmp(const void* p1 , const void* p2) { char* ptr1 = * ( (char**)p1 );//由於是傳入的字元地址: char* 是字元的地址 , char* *:是指向字元地址的地址,即字串的地址,前面為什麼又加 * char* ptr2 = * ( (char**)p2 ); return strcmp(ptr1 , ptr2); } 3 qsort(suffixArr , length , sizeof(char*) , myStrcmp);//第三個是待排序陣列中每個元素大小,由於每個元素是char* ,所以是sizeof(char*) */ const int MAXSIZE = 1024; int commonLength(char* str1 , char* str2) { if(NULL == str1 || NULL == str2) { return 0; } int length = 0; while( *(str1++) == *(str2++) ) { length++; }; return length; } string findMaxRepeatedSubString(char* str) { if(NULL == str) { return ""; } int size = strlen(str); int max = INT_MIN; int maxIndex = 0; for(int i = 0 ; i < size ; i++) { for(int j = i + 1 ; j < size ; j++) { int length = commonLength(&str[i] , &str[j]); if(length > max) { max = length; //需要記錄後面開始的字串的起始位置 maxIndex = j; } } } stringstream stream; //獲取最大重複子串 for(int i = maxIndex ; i < maxIndex + max ; i++) { stream << str[i]; } return stream.str(); } //字串比較,這裡發現傳入的是字串的首字元地址後,發現沒有排序成功 int myStrcmp(const void* p1 , const void* p2) { /* char* ptr1 = (char*)p1; char* ptr2 = (char*)p2; */ char* ptr1 = * ( (char**)p1 );//由於是傳入的字元地址: char* 是字元的地址 , char* *:是指向字元地址的地址,即字串的地址,前面為什麼又加 * char* ptr2 = * ( (char**)p2 ); return strcmp(ptr1 , ptr2); } //採用字尾陣列,查詢最長重複子串, string findMaxRepeatedSubString_suffixArray(char* str) { if(NULL == str) { return 0; } int length = strlen(str); if(length > MAXSIZE) { cout << "string length is " << length << ",it must less than " << MAXSIZE; return 0; } char* suffixArr[MAXSIZE];//字尾陣列,每個元素是一個指向字尾陣列的指標 for(int i = 0 ; i < length; i++) { suffixArr[i] = &str[i]; } //對字尾陣列排序 qsort(suffixArr , length , sizeof(char*) , myStrcmp);//第三個是待排序陣列中每個元素大小,由於每個元素是char* ,所以是sizeof(char*) //接下來比較字尾陣列相鄰元素,查詢最大長度 int max = INT_MIN; char* maxPtr; for(int i = 1 ; i < length; i++) { int length = commonLength(suffixArr[i-1] , suffixArr[i]); if(length > max) { max = length; maxPtr = suffixArr[i-1];//由於字尾陣列從小到大排的,前面的才是最大重複子串 } } stringstream stream; //獲取最大重複子串 for(char* s = maxPtr ; s < maxPtr + max ; s++) { stream << (*s); } return stream.str(); } void process() { char str[MAXSIZE]; while(scanf("%s" , str)) { //string result = findMaxRepeatedSubString(str); string result = findMaxRepeatedSubString_suffixArray(str); cout << result << endl; } } int main(int argc , char* argv[]) { process(); getchar(); return 0; }