变位词问题:给定一个英语字典,找出其中的所有变位词集合,例如,"pots","stop"和"tops"互为变位词,因为每一个单词都可以通过改变其他单词中字母的顺序来得到。
如果考虑单词中字母的所有可能排列,那算法会出奇的低效和复杂。实际上,每个变位词相当于一个等价类。算法的关键是要选择一个标识来标识这个等价类,使得在相同变位词类中的单词具有相同的标识。然后,将所有相同标识的单词集中在一起。我们可以使用基于排序的标识(可见要使用排序技术),将单词中的字母按照字母表排序。例如上面标识即为opst。
可以用三个程序来,通过协同工作来实现,第一个程序用来标识单词,第一个程序排序标识后的文件,第三个将这些单词压缩为每个变位词类占一行的形式。运行时让这三个独立程序用
管道连接,即可完成处理过程。
(1)sign程序:为单词建立标识。假定文件中没有超过一百个字母的单词,并且输入文件仅包含小写字母和换行符。
#include <stdio.h> #include <stdlib.h> #include <string.h> #define WORDMAX 100 int charcomp(char *x,char *y){ return *x-*y; } int main(void){ char word[WORDMAX],sig[WORDMAX]; while(scanf("%s",word)!=EOF){ /* 输入单词 */ strcpy(sig,word); qsort(sig,strlen(sig),sizeof(char),charcomp); /* 对单词中的字母进行排序,将结果作为标识 */ printf("%s %s/n",sig,word); /* 输出标识和单词本身 */ } return 0; }
(2)sort程序:对标识后的文件排序。可以使用Linux/Unix中现成的sort程序。
(3)squash程序:将所有单词压缩为每个变位词类占一行的形式。
#include <stdio.h> #include <string.h> #define WORDMAX 100 int main(void){ char word[WORDMAX],sig[WORDMAX],oldsig[WORDMAX]; int linenum=0; strcpy(oldsig,""); /* 标识先初始化为空串 */ while(scanf("%s %s",sig,word)!=EOF){ /* 每个变位词类占一行 */ if(strcmp(oldsig,sig)!=0 && linenum>0) /* 若当前标识与上一次标识不同,说明开始一个新的变位词类,则打印换行符(第一条记录除外) */ printf("/n"); strcpy(oldsig,sig); linenum++; printf("%s ",word); /* 在当前行上打印该单词 */ } printf("/n"); return 0; }
通过下面的命令构建变位词列表:sign <dictinary | sort | squash >gramilist。将文件dictionary输入到程序sign,经过处理最后输出到gramlist文件。
这里通过使用标识这种数据结构,使算法效率大大提高。可见好的数据结构设计可以将大程序缩减为小程序,可以节省时间和空间、提高可移植性和可维护性。数据的表现形式是程序设计的根本。常用的原则有:
(1)使用数组重新编写重复代码。
(2)封装复杂结构。当需要非常复杂的数据结构时,使用抽象术语进行定义,并将操作表示为类。
(3)尽可能使用高级工具。如超文本、名字-值对、电子表格、数据库、特定领域的编程语言等,都是特定问题领域中的强大工具。
(4)从数据得出程序的结构。在编写代码之前,优秀的程序员会彻底理解输入、输出和中间数据结构,并围绕这些结构创建程序。
关键算法设计思想:排序策略、标识等价类、协同程序。