1. 題目
給定一個文本作爲輸入,查找其中最長的重複子字符串。
2. 示例
字符串爲:“Ask not what your country can do for you, but what you can do for your country”。
最長的重複字符串爲:“can do for you”。
3. 解法-構造後綴數組
使用稱爲“後綴數組”的簡單數據結構。這個結構是一個字符指針數組,記爲pStr,每個元素都指向字符串中某個位置開始的子字符串。pStr[0]指向整個字符串,pStr[1]指向第二個字符開始的數組後綴。
/* 構造後綴數組 */
vector<const char*> pStr;
const char *str = s.c_str();
for (int i = 0; i < s.size(); i++)
{
pStr.push_back(str);
str++;
}
例如:輸入字符串爲:“banana”,則:
- pStr[0]: banana
- pStr[1]: anana
- pStr[2]: nana
- pStr[3]: ana
- pStr[4]: na
- pStr[5]: a
4. 解法-後綴數組排序
後綴數組中的每個元素都指向一個字符串,如果某個長字符串在數組中串行兩次,那麼它將出現在量不同的後綴中,依次可以對數組進行排序以找到相同的後綴。
因爲數組的每個元素都相當於一個字符串,因此按照字符串的方式進行比較排序,如下所示:
比較函數如下所示:
bool StringCompare(const char *pa, const char *pb)
{
while (*pa != '\0' && *pb != '\0' && *pa == *pb)
{
pa++;
pb++;
}
if (*pa == '\0' && *pb != '\0') /* 小於 */
return true;
else if (*pb == '\0') /* 大於等於 */
return false;
else
return *pa < *pb;
}
調用方式如下所示:
/* 對後綴數組進行排序 */
sort(pStr.begin(), pStr.end(), StringCompare);
對“banana”的後綴數組進行排序後如下所示:
- pStr[0]: a
- pStr[1]: ana
- pStr[2]: anana
- pStr[3]: banana
- pStr[4]: na
- pStr[5]: nana
通過比較相鄰元素,可以找到最長的重複子字符串:“ana”
5. 解法-獲取最長重複子串
對後綴數組中的相鄰的元素進行比較,找到最長的重複字符串。
int LengthOfCommon(const char *p1, const char *p2)
{
int length = 0;
while (p1[length] != '\0' && p2[length] != '\0' && p1[length] == p2[length])
length++;
return length;
}
調用方式如下所示:
/* 依次比較後綴數組的兩個相鄰子字符串 */
int currentMaxLength = 0;
int currentIndex = 1;
for (int i = 1; i < pStr.size(); i++)
{
int length = LengthOfCommon(pStr[i-1], pStr[i]);
if (length > currentMaxLength)
{
currentMaxLength = length;
currentIndex = i;
}
}
/* 提取公共子字符串 */
string result;
for (int i = 0; i < currentMaxLength; i++)
{
result += pStr[currentIndex][i];
}
6. 流程
- 構造後綴數組
- 對後綴數組進行排序
- 對後綴數組相鄰元素進行比較,查找最長重複子串
- 提取最長重複子串
7. 複雜度分析
假設輸入字符串的長度爲n,則:
空間複雜度
空間複雜度爲O(n),因爲後綴數組保存的並不是n個子字符串,而是指向這n個字符串的指針。時間複雜度:
- 構建後綴數組的時間複雜度爲O(n)
- 假設使用快速排序,時間複雜度爲O(knlgn),其中k爲字符串的平均比較次數,一般重複子字符串的長度不會特別大,因此k較小。
- 查找最長重複子字符串的時間複雜度爲O(kn)
綜上:空間複雜度爲O(n),時間複雜度爲O(nlgn)
8. 實驗
以英文版的《簡愛》作爲輸入字符串,總共1059723個字符,最長的重複子串爲:
“I am disposed to be gregarious and communicative to-night”。
運行時間爲:0.358s
以英文版的《霧都孤兒》作爲輸入字符串,總共935369個字符,最長的重複子串爲:
“Is a very short one, and may appear of no great importance in its place; but it should be read notwithstanding, as a sequel to the last, and a key to one that will follow when its time arrives.”
運行時間爲:0.325s
以英文版的《霧都孤兒》作爲輸入字符串,總共790103個字符,最長的重複子串爲:
“I am the resurrection and the life, saith the Lord: he that believeth in me, though he were dead, yet shall he live: and whosoever liveth and believeth in me, shall never die.”
運行時間爲:0.278s
9. 其他
參考《編程珠璣》第15章
代碼:GitHub