问题描述:
给定一个英语词典,找出其中的所有变位词集合。例如,“pots”、“stop”和“tops”互为变位词,因为每一个单词都可以通过改变其他单词中的字母的顺序来得到。
问题解析:
变位词具有相同的长度,相同的字符,唯一的区别就是这些相同的字符按照不同的顺序排列成不同的字符串而已。如果有一种方法唯一标识这些相同的字符,那么这个问题好解决了。
解决方案:
方案1:
对于这个问题,最快想到的最直接的方法就是针对每一个单词跟字典中的其他单词进行比较。然而,假设一次比较至少花费1微秒的时间,则拥有二十万单词的字典将花费:200000单词 x 200000比较/单词 x 1微秒/比较 = 40000x10^6秒 = 40000秒 ≈ 11.1小时。比较的次数太多了,导致效率低下,我们需要找出效率更高的方法。
方案2:
按照字母顺序对每个单词进行标识并把这些具有相同标识的词集合到一起。 (1)将输入文件中的所有单词加标识并输出到另一个文件中。 (2)将有标识的输出文件中所有的词依(标识,单词)对的形式存储到内存。这里使用 C++的mutimap和set来完成。
这里出现了标识-单词(key-value)对,我们很容易想到C++中的关联容器multimap,使用multimap的好处就是:动态管理内存,容器大小动态改变;单词与它的标识一一对应,对于相同标识(key)的单词直接加在值(value)后面;无需根据标识排序,因为multimap会自动按关键字有序(默认升序)。
所以,在将每个单词及其标识存入multimap以后,就可以直接遍历输出了,每一个multimap元素就是一个变位词集合。
#include <iostream> #include <cstdlib> #include <map> #include <set>
#include <cstring> #include <string> using namespace std; #define WORDMAX 100
int char_comp(const void *x, const void *y){ return *((char*)x) - *((char*)y); }
char *mytolower(char *lword, char *word){
while (*word!='\0')
if (isalpha(*word) && isupper(*word)) *(lword++) = tolower(*(word++));
else *(lword++) = *(word++);
*lword = '\0';
return lword;
}void add_sign(FILE *rfile, FILE *wfile){
char word[WORDMAX], lword[WORDMAX],sign[WORDMAX];
while (fscanf(rfile, "%s", word) != EOF){
mytolower(lword, word);
strcpy(sign, lword);
qsort(sign, strlen(sign), sizeof(char), char_comp);
fprintf(wfile, "%s\t%s\t\n", sign, word);
}
}
void print_anagrams(FILE *rfile,FILE *wfile){
multimap<string, string> anagarams; set<string> myset;
char sign[WORDMAX], word[WORDMAX];
while (fscanf(rfile, "%s\t%s", sign, word) != EOF) {
myset.insert(sign);
anagarams.insert(make_pair(sign, word));
}
for (set<string>::iterator iter = myset.begin(); iter != myset.end(); ++iter){
multimap<string, string>::iterator it = anagarams.equal_range(*iter).first;
for (; it != anagarams.equal_range(*iter).second;++it){
cout << (*it).second << " ";
fprintf(wfile, "%s\t", (*it).second);
}
cout << endl;
}
}int main(){
FILE *rfile = fopen("dictionary.txt", "r");
if (rfile == NULL) cout << "不能打开dictionary.txt文件!\n";
FILE *wfile = fopen("sign_dictionary.txt", "w");
if (wfile == NULL) cout << "不能打开sign_dictionary.txt文件!\n";
add_sign(rfile, wfile);
fclose(rfile); fclose(wfile); rfile = NULL; wfile = NULL;
FILE *rrfile = fopen("sign_dictionary.txt", "r");
if (rrfile == NULL) cout << "不能打开sign_dictionary.txt文件!\n";
FILE *wwfile = fopen("anagrams.txt", "w");
if (wwfile == NULL) cout << "不能打开anagrams.txt文件!\n";
print_anagrams(rrfile, wwfile);
fclose(rrfile); fclose(wwfile); rrfile = NULL; wwfile = NULL;
system("pause");
return 0;
}
附:(2012.5.6百度实习笔试题)一个单词交换字母位置,可得另一个单词,如army->mary,成为兄弟单词。提供一个单词,在字典中找到它的兄弟。描述数据结构和查询过程。
**解题思路:
如果不允许进行预处理,那么我们只能顺序遍历整个字典,计算每个单词的标识与给定单词的标识比较。如果允许进行预处理,我们可以如上述思路二将所有单词加入一个map,然后输出关键字(给定单词的标识)对应的值,值中就包含了该单词的所有兄弟单词。