http://blog.youkuaiyun.com/MonkeyAndy
/--------MonkeyAndy原创,转载请注明出处--------------/
学习了字典树后想起百度的笔试题可以使用这个方法解决,写下与大家分享,欢迎批评指正,共同进步
题目描述:
给一个单词a,如果通过交换单词中字母的顺序可以得到另外的单词b,那么b是a的兄弟单词,比如的单词army和mary互为兄弟单词。 现在要给出一种解决方案,对于用户输入的单词,根据给定的字典找出输入单词有哪些兄弟单词。请具体说明数据结构和查询流程,要求时间和空间效率尽可能地高。
分析:
要从大量的字典中查找某一个单词,采用普通的数据结构,只能采取顺序遍历的情况,这样效率是非常低下的,所以只能采用字典树来存储。字典树采取了由空间换时间的策略,其优势在于插入和查找可以同时进行。使用trie:因为当查询如字符串abc是否为某个字符串的前缀时,显然以b,c,d....等不是以a开头的字符串就不用查找了。所以建立trie的复杂度为O(n*len),而建立+查询在trie中是可以同时执行的,建立的过程也就可以成为查询的过程。所以总的复杂度为O(n*len),实际查询的复杂度也只是O(len)。(说白了,就是Trie树的平均高度h为len,所以Trie树的查询复杂度为O(h)=O(len)。好比一棵二叉平衡树的高度为logN,则其查询,插入的平均时间复杂度亦为O(logN))。
关于字典树的更多介绍请参考
http://blog.youkuaiyun.com/v_july_v/article/details/6897097
所用的数据结构为
#define MAX 26
typedef struct Trie{
char * data;
struct Trie *childs[MAX];
}Trie, *pTrie;
思路:
(1) 根据给定的字典,构造出字典中单词的字典树,根节点为 pTrie root;
(2)根据输入的单词,将其字母顺序进行排列组合,得到所有可能的单词组合,长度为n的单词有n!种组合情况(不考虑重复情况)char * words[n!];
勘误:
应改为:根据输入的单词,求其全排列,将结果存放于 char * words[n!] 中.
(3)遍历words,以words[i]为关键字,在字典树中进行查找,若找到,则返回true,并加入到 brothers[ ]中;否则返回false;
从字典树中查找单词的流程如下:
(1)遍历单词中的所有字母,若 word[i] 在字典树中相应的节点处存在( ptr->child[index(word[i])]->data == word[i]),则指针 ptr=ptr->child[index(words[i])] ;
(2)否则,查找失败,返回false;
(3)遍历结束后,ptr此时指向word[len-1]所在的节点,此时遍历ptr->childs[] 节点,查找是否存在非空的节点,若存在说明word只是字典中某个单词的一个前缀,则返回false;否则返回true;
解释:
words数组中存放的长度为n的单词的所有肯能的兄弟单词,而word为char *类型的变量, word中存放的是words中的某个单词words[i].
所用到的函数
//初始化函数
void init(pTrie & root)
{
root->data = '';
for(int i=0; i<MAX; i++)
root->childs[i] = NULL;
}
//构造字典树
void creat(pTrie root, char *word)
{
pTrie ptr = root;
for(int i=0; i<strlen(word); i++)
{
if(ptr->childs[getIndex(word[i])] == NULL)
{
ptr->childs[getIndex(word[i])]->data = word[i];
ptr->childs[getIndex(word[i])] = (pTrie)malloc(sizeof(Tire));
for(int j=0; j<MAX; j++)
ptr->childs[getIndex(word[i])]->childs[j] = NULL;
}
ptr = ptr->childs[getIndex(word[i])];
}
}
//勘误 : 构造字典树
void creat(pTrie root, char *word)
{
pTrie ptr = root;
for(int i=0; i<strlen(word); i++)
{
if(ptr->childs[getIndex(word[i])] == NULL)
{
ptr->childs[getIndex(word[i])] = (pTrie)malloc(sizeof(Tire));
//应先开辟内存空间再进行赋值操作
ptr->childs[getIndex(word[i])]->data = word[i];
for(int j=0; j<MAX; j++)
ptr->childs[getIndex(word[i])]->childs[j] = NULL;
}
ptr = ptr->childs[getIndex(word[i])];
}
}
//查找单词
bool search(pTrie root, char *word)
{
ptr=root;
bool flag = true;
for(int i=0; i<strlen(word); i++)
{
if(ptr->childs[getIndex(word[i])]->data == word[i])
ptr=ptr->childs[getIndex(word[i])];
else{
flag = false;
break;
}
}
for(int j=0;j<MAX; j++){
if(ptr->childs[j] != NULL){
flag = false;
break;
}
}
return flag;
}
补充:
全排列算法原理和其递归实现
全排列是将一组数按一定顺序进行排列,如果这组数有n个,那么全排列数为n!个。现以{1, 2, 3, 4, 5}为例说明如何编写全排列的递归算法。1、首先看最后两个数4, 5。 它们的全排列为4 5和5 4, 即以4开头的5的全排列和以5开头的4的全排列。由于一个数的全排列就是其本身,从而得到以上结果。2、再看后三个数3, 4, 5。它们的全排列为3 4 5、3 5 4、 4 3 5、 4 5 3、 5 3 4、 5 4 3 六组数。即以3开头的和4,5的全排列的组合、以4开头的和3,5的全排列的组合和以5开头的和3,4的全排列的组合.从而可以推断,设一组数p = {r1, r2, r3, ... ,rn}, 全排列为perm(p),pn = p - {rn}。因此perm(p) = r1perm(p1), r2perm(p2), r3perm(p3), ... , rnperm(pn)。当n = 1时perm(p} = r1。为了更容易理解,将整组数中的所有的数分别与第一个数交换,这样就总是在处理后n-1个数的全排列。
void Swap(char* a, char* b) {// 交换a和b char temp = *a; *a = *b; *b = temp; } void Perm(char list[], int k, int m) { //生成list [k:m ]的所有排列方式 int i; if (k == m) {//输出一个排列方式 for (i = 0; i <= m; i++) putchar(list[i]); putchar('\n'); } else // list[k:m ]有多个排列方式 // 递归地产生这些排列方式 for (i=k; i <= m; i++) { Swap (&list[k], &list[i]); Perm (list, k+1, m); Swap (&list [k], &list [i]); } }