编程珠玑: 15章 字符串 15.2寻找字符串中的最长重复子串 -------解题总结

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

你可能感兴趣的:(编程珠玑)