第十五章的第二部分涉及到“短语”,其书中提出的一个问题是:给定一个文本文件作为输入,查找其中最长的重复子字符串。例如,"Ask not what your country can do for you, but what you can do for your country"中最长的重复字符串是“can do for you”,第二长的是"your country"。如何解决这个问题呢?
文中利用两种方法来解决这个问题,第一种方法进行全文的扫描,其时间复杂度为O(n^2),这种方法将字符串作为一个整体(包括空格符)存储在一个字符数组char c[]中,因此利用的输入函数为C中的getchar(),算法比较字符串中的每个子串,并利用全局变量存储最长长度和始末数组下标。
// Program to recognize the longest duplicated string in the text
// 简易方法,时间复杂度O(n^2)
#include <stdio.h>
#include <stdlib.h>
#define MAX 5000000
char buf[MAX];
int comlen(char *p, char *q)
{
int count=0;
/*
if(*p != *q)
return count;
if(*p && *p == *q)
{
++count;
p++;
q++;
}
*/
while(*p++ == *q++)
++count;
return count;
}
int main()
{
int i, j, n=0, maxlen=0, maxi, maxj, thislen;
char c;
//while(scanf("%s", buf) != EOF)
// ;
// IN order to calculate the length n, we use getchar() instead of scanf()
while((c=getchar()) != EOF)
buf[n++]=c;
//buf[n]='\0';
for(i=0;i<n;i++)
{
for(j=i+1;j<n;j++) // j=i+1
{
//if((len=comlen(i,j))>maxlen)
if((thislen=comlen(&buf[i], &buf[j]))>maxlen) // pay attention to the (a=b)>c expression
{
maxlen=thislen;
maxi=i;
maxj=j;
}
}
}
printf("The longest length is:%d\n",maxlen);
printf("The longest string is: ");
for(i=maxi;i<maxi+maxlen;i++) // i<
putchar(buf[i]);
return 0;
}
需要注意的问题是怎样将comlen()函数中的字符指针和字符数组的下标值联系起来,其实就是用取地址符嘛!--&。
文中的第二种方法用到了一个新颖的数据结构---后缀数组。其实在本例中就是一个字符指针数组,元素a[0]指向整个字符串,下一个元素指向从第二个字符开始的数组后缀,等等。其数组的大小为字符串的大小。而算法的思想是这样的:利用额外的空间来换取时间,首先读入输入的字符串,构建后缀数组。然后利用C库函数中的qsort()对后缀数组排序,这样排序后相邻的数据就是有最多重复字符的数据。最后比较排序后数组中的每个相邻的数据,找出最长的一个重复子串。在下面的程序中更灵活地加入了一个宏变量M,其代表的意义是这个重复子串出现的次数,也就是将解法扩展为解决“打印重复M次的最长子串”,这个问题只要在比较数组中数据时比较a[i]和a[i+M]即可。本例中对于n个字符的输入文本,后缀数组使用文本自身和额外的n个指针来表示每个子串,时间复杂度为O(nlogn)。
// Program to recognize the longest duplicated string in the text (打印最长重复子串)
// 后缀数组,时间复杂度 O(nlogn)
// Print longest string duplicated M times (打印重复M次的最长子串)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define M 1 //
#define MAX 5000000
char buf[MAX];
char *a[MAX]; //后缀数组
int comlen(char *p, char *q)
{
int count=0;
while(*p && (*p++ == *q++))
++count;
return count;
}
int pstrcmp(char **p, char **q) //
{
return strcmp(*p, *q);
}
int main()
{
int i, n=0, maxlen=0, thislen, j;
char c;
while((c=getchar()) != EOF)
{
/*
buf[n]=c;
a[n]=&buf[n];
n++;
*/
a[n]=&buf[n];
buf[n++]=c;
}
buf[n]='\0';
//qsort(a, n, sizeof(char *), strcmp); //
qsort(a, n, sizeof(char *), pstrcmp); // 指向指针的指针
/*
for(i=0;i<n;i++)
printf("%s\n",a[i]);
*/
//for(i=0;i<(n-1);i++) // i<n-1 不要越界
for(i=0;i<(n-M);i++)
{
//if((thislen=comlen(a[i], a[i+1]))>maxlen)
if((thislen=comlen(a[i], a[i+M]))>maxlen)
{
maxlen=thislen;
j=i;
}
}
printf("The longest length is:%d\n", maxlen);
//printf("The longest duplicated string is:%s",a[j]);
//*精度输出字符串中的maxlen个字符
printf("The longest duplicated string is:%.*s\n",maxlen, a[j]);
return 0;
}
需要注意的问题:1,qsort()函数中的各个参数代表的含义,其中自己重新构建的比较函数pstrcmp实际上是对strcmp库函数的一层间接调用,但是因为数组是字符指针数组,而对函数的调用需要利用到指针,所以pstrcmp()中的参数是二级指针,这在调用strcmp时需要一级的解引用*,而每个元素的大小(即函数的第三个参数为char型指针的大小)。2.在for()循环比较数组元素时需要注意下标越界的问题。3.打印输出的时候printf()函数利用“*”来精度控制字符串,指定打印字符串的长度,涉及到printf()的格式。
程序运行截图: