1. 程式人生 > >程式設計珠璣: 15章 字串 15.2尋找字串中的最長重複子串 -------解題總結

程式設計珠璣: 15章 字串 15.2尋找字串中的最長重複子串 -------解題總結

#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;
}