问题:给出一个字符数组或者整型数组,要求找出最长的重复子串或重复子数组。
条件:存在两种情况,要么要求重复子串不能够出现交叉重叠,要么就是允许交叉重叠。
思路:目前我用的方法是,后缀数组。根据已知数据构建后缀数组,为了节省空间,尽量使用指针而不复制原有的数据。
代码一:字符串中寻找最长重复子串(允许重叠)。
#include <stdio.h> #include <stdlib.h> #include <string.h> #define N 100 char c[N], *a[N]; int getmaxlen( char *p, char *q ){ int i = 0; while( *p && (*p++ == *q++) ) ++i; return i; } int pstrcmp( const void *p1, const void *p2 ){ return strcmp( *(char* const *)p1, *(char* const*)p2 ); } int main(){ char ch; int n=0; int i, temp; int maxlen=0, index=0; printf("Please input your string:\n"); while( (ch=getchar())!='\n' ){ a[n]=&c[n]; c[n++]=ch; } c[n]='\0'; //for(i=0;i<n;i++) // printf("%s\n", a[i]); //printf("\n"); qsort( a, n, sizeof(char*), pstrcmp ); //for(i=0;i<n;i++) // printf("%s\n", a[i]); //printf("\n"); for(i=0; i<n-1; ++i ){ temp=getmaxlen( a[i], a[i+1] ); if( temp>maxlen ){ maxlen=temp; index=i; } } printf("%.*s\n",maxlen, a[index]); return 0; }
1,c[]是作为输入的字符串。a[]这个指针数组中的每个元素都是指向输入字符串的一个后缀子串的指针。后缀子串就是从输入字符串的某一个字符开始到字符串结尾所构成的子串。对于一个长度为n的字符串,就会有n个后缀子串(输入串本身也算是一个)。
2,将这n个后缀子串排序。按照字母表顺序排序后,含有重复子串的后缀子串就会挨在一起。逐次比较相邻的两个后缀子串进行匹配,看他们的匹配长度。选出匹配长度最长的那一对后缀子串的匹配。
3,qsort是c标准库里提供的排序函数。第四个参数负责提供比较规则。
4,printf("%.*s\n",maxlen, a[index]); 中的星号 * 是利用参数来控制输出格式。
代码二:整型数组中寻找最长重复子数组(不允许重叠)。
不允许重叠的时候,就需要我在后缀数组中找最大长度时候对重叠与否做出判断。
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <set> using namespace std; #define N 300 //两个全局变量 int data[N]; int num; //为set自定义的比较函数 struct mycmp { // x < y ? true : false bool operator()(const int &x, const int &y) const { int idx1 = x; int idx2 = y; int *p = data; while(idx1 < num && idx2 < num) { if(p[idx1] > p[idx2]) return false; else if(p[idx1] < p[idx2]) return true; idx1++; idx2++; } if(idx2 == num) return false; if(idx1 == num) return true; return 0; // 没用,因为后缀数组不可能有相等的。 } }; void show(int p) { int idx = p; int *a = data; printf("Suffix: "); for(;idx< num;idx++) printf("%d ", a[idx]); printf("\n"); } // 寻找相邻两个后缀子串中的最大相同长度(不记录重叠部分) int getmax(const int &p1, const int &p2) { int idx1 = p1; int idx2 = p2; int *p = data; while(idx1 < num && idx2 < num) { if(p[idx1] == p[idx2]) { ++idx1; ++idx2; } else break; } if(p1 > p2 && idx2 > p1) //说明有重叠,处理重叠 { int mid = (idx2 + p1)/2; return mid - p2; } if(p2 > p1 && idx1 > p2) { int mid = (idx1 + p2)/2; return mid - p1; } return idx1 - p1; //无重叠 } int main(){ set<int, mycmp> myset; while(scanf("%d", &num) != EOF) { myset.clear(); memset(data, 0, sizeof(data)); for(int i=0;i<num;i++) scanf("%d", &data[i]); //读完所有数据之后才往set里插入数据 for(int i=0;i<num;i++) { myset.insert(i); } int maxlen = 0; set<int, mycmp>::iterator it, it2; it = myset.begin(); for(;it!= myset.end();) { printf("%d ",*it); show(*it); it2 = it; ++it; if(it == myset.end()) break; int tmp = getmax(*it, *it2); //排序后相邻的两个后缀数组 if(tmp > maxlen) maxlen = tmp; } printf("%d\n", maxlen); } return 0; }
1、代码中为了比较两个子数组方便,为set自定义了比较函数。该函数返回true则表明左参数较大,返回false表明左参数较小。
2、在getmax()函数中比较排序之后的相邻后缀数组,并且处理子数组重叠的情况。