Google字符串模糊匹配算法,字典树模糊查询
2013-05-20 23:26
471 查看
转载自:/article/9073764.html
好吧,我承认我又装13标题党了。其实是G查询关键词过程中匹配的一点大概的算法框架,G的模糊匹配大家都知道,比如你输入64什么的,G会自动列出你心里可能要找
到东西,如下图:
那这个算法是怎么实现的呢,用到了一种高级数据结构--字典树,或者说是字典树思想,因为字典树不规定你具体怎么实现,可以二维数组,可以map……也可以通常的结构体+next指针。可以通过一个题来讲述,就是2009ACM/ICPC
哈尔滨 reginal现场赛G题:Fuzzy Google Suggest(http://acm.hit.edu.cn/judge/show.php?Proid=2888&Contestid=0)讲解。当时我搞这题,不知道字典树,然后一直模拟,结果……(—
—|||)先用输入的单词构造一棵字典树,节点数据包括:cnt,表示节点上的字母被多少个单词经过;vis,0表示经过此节点不能继续匹配,1表示经过此节点可继续匹配,2表示此节点就是恰好用于匹配的前缀的最后一个字符;然后一个next数组,大小26,但不是node指针,而是int数组,表示当前节点的儿子,next[i]==-1表示当前节点没有第i个儿子,否则有,并将此儿子结点进行编号,其编号就是它在字典树中的编号。然后根据编辑距离进行dfs遍历;函数设计为dfs(int
x,int pos,intedit,char* key),x是trie树中第x个节点,pos表示匹配到了前缀字符key的第pos个字符,edit表示剩余可用的编辑距离。假如某个字符符合当前前缀的匹配条件,则trie节点向儿子结点递归,pos++,edit不变dfs(root[x].next[key[pos]-'a'],++pos,edit,key);否则尝试使用编辑距离:1,增加一个字符,此时要遍历26个字符,看增加哪个合法(即此字符在trie中出现了并且是当前key[pos]的儿子节点并且此字符不跟key[pos]相同),然后继续dfs,此时编辑距离少一个,key的位置不变,trie走向儿子节点,假设增加的字符编号为i,则dfs(root[x].next[i],++pos,edit-1,key);2,替换一个字符,此时edit减一,pos向前走一个,dfs(root[x].next[i],pos+1,edit-1,key);3,删除一个字符,删除表示为trie节点不变,但是前缀字符串key串往下走一个,相当于就没匹配上的忽略,dfs(x,pos+1,edit-1,key),若能遍历下去,且x节点之前不可通行,则将x标记为可通行.到达匹配终点的条件有三个:1,前缀串key一路匹配到了末尾,此时的结点x被标记为,root[x].vis=2,表示它是某个前缀串的终结者。2,在tire中一路通行突然edit用完透支了,那这个前缀串没有找到匹配的单词,回溯。3,碰到了某个节点x,root[x].vis=2,说明到x这个前缀串已经能够匹配。返回可以匹配。然后再利用dfs_calc函数计数符合匹配的单词数量:vis=2的结点。最后用dfs_clear()函数清理trie树。关于销毁trie树,见有人用一个for循环搞定的,那样只是把和根节点直接相连的结点进行了delete,但是其他的都变成悬空状态,并未被销毁。坏习惯(但对ACM题来说不失为一种销毁的捷径)。不过用struct写的交上去老是RE,极度掣肘,只好参看某牛的改作数组实现的trie:
RE的:
[b][cpp] view
plaincopy[/b]
#include<pzjay>
#<一坨头文件>
const int sup=500005;
int tot;//tire结点个数
int len;//记录前缀词 的长度
int ans;//记录此前缀匹配单词的个数
struct node
{
int cnt;//表示此字母被多少个单词经过
int vis;//vis=0表示经过此单词不能够到达要匹配的结点;1表示可以;2表示此字母就是匹配前缀的最后一个字母(即匹配完毕)
int next[26];
}root[sup];
void creat(char key[])
{
int i=0,index;
int k=1;//root下标
while(key[i])
{
index=key[i]-'a';
if(-1==root[k].next[index])
{
root[k].next[index]=tot;//将root[tot]的地址赋给tmp->next[index]
root[tot].cnt=1;
root[tot].vis=0;
++tot;
}
else
++root[root[k].next[index]].cnt;
k=root[k].next[index];
++i;
}
}
int dfs(int x,int pos,int edit,char* key)//返回是否成功匹配
{
if(2==root[x].vis)//到达一个匹配的结束点
return 1;
if(edit<0)
return 0;
if(pos==len)//到达前缀的末尾
{
root[x].vis=2;//该节点是前缀的结束字母,x之前的单词串被成功匹配
return 1;
}
int index=key[pos]-'a';
if(-1!=root[x].next[index])//还有儿子结点
if(dfs(root[x].next[index],pos+1,edit,key))
root[x].vis=1;
for(int i=0;i<26;++i)
{
index=key[pos]-'a';
if(index==i || -1==root[x].next[i])//在树中找可替换的字符
continue;
if(dfs(root[x].next[i],pos+1,edit-1,key))//将pos处的字母尝试用i+'a'代替
root[x].vis=1;
if(dfs(root[x].next[i],pos,edit-1,key))//插入一个字母
root[x].vis=1;
}
if(dfs(x,pos+1,edit-1,key))//delete
if(0==root[x].vis)
root[x].vis=1;
return root[x].vis;
}
void dfs_calc(int x)
{
if(2==root[x].vis)
{
ans+=root[x].cnt;
return;
}
for(int i=0;i<26;++i)
if(root[root[x].next[i]].vis > 0)
dfs_calc(root[x].next[i]);
}
void dfs_clear(int x)
{
root[x].vis=0;
for(int i=0;i<26;++i)
if(root[root[x].next[i]].vis > 0)
dfs_clear(root[x].next[i]);
}
int main()
{
int n;
//freopen("1.txt","r",stdin);
while(scanf("%d",&n)!=EOF)
{
tot=2;
char key[25];
int m;
int edit;//编辑距离
for(int i=0;i<sup;++i)
memset(root[i].next,-1,sizeof(root[i].next));
//fill(root[i].next,root[i].next+26,-1);
while(n--)
{
scanf("%s",key);
creat(key);
}
scanf("%d",&m);//m个前缀
while(m--)
{
ans=0;
scanf("%s %d",key,&edit);
len=strlen(key);
dfs(1,0,edit,key);
//1是x的起始遍历位置,0是前缀key的起始位置,edit是剩余的编辑距离
dfs_calc(1);//计数符合匹配的单词个数
dfs_clear(1);//清空x
printf("%d/n",ans);
}
}
return 0;
}
AC:
const int sup=700005;
int tot;//tire结点个数
int len;//记录前缀词 的长度
int ans;//记录此前缀匹配单词的个数
int root[sup][26];//每个节点最多26个分支
int cnt[sup],vis[sup];//cnt[i]记录字母i被多少个单词经过
void creat(char key[])
{
int k=1,index,i=0;
while(key[i])
{
index=key[i]-'a';
if(-1==root[k][index])
root[k][index]=tot++;
k=root[k][index];
++cnt[k];
++i;
}
}
int dfs(int x,int pos,int edit,char key[])
{
if(2==vis[x])
return 1;
if(edit<0)
return 0;
if(pos==len)//匹配完毕,节点x成为前缀词key的结尾字母
{
vis[x]=2;
return 1;
}//以上可以直接return的,都是最终的结果:匹配成功或者失败
//下面的只是递归到最重结果的过程,故是对vis赋值
int index=key[pos]-'a';
if(-1!=root[x][index])//可以继续往深层遍历
if(dfs(root[x][index],pos+1,edit,key))
vis[x]=1;//从x往下可以走到目标节点
for(int i=0;i<26;++i)
{
index=key[pos]-'a';
if(index==i || -1==root[x][i])//筛选掉跟要替换的字母相同的字母和未在trie树中出现的字母
continue;
if(dfs(root[x][i],pos+1,edit-1,key))//pos++,遍历下一个字母,表示替换一个trie树中存在的字母
vis[x]=1;
if(dfs(root[x][i],pos,edit-1,key))//pos不变.表示增加一个字母
vis[x]=1;
}
if(dfs(x,pos+1,edit-1,key))//删除一个字母
if(0==vis[x])
vis[x]=1;
return vis[x];
}
void dfs_calc(int x)
{
if(2==vis[x])
{
ans+=cnt[x];
return;
}
for(int i=0;i<26;++i)
if(vis[root[x][i]])
dfs_calc(root[x][i]);
}
void dfs_clear(int x)
{
vis[x]=0;
for(int i=0;i<26;++i)
if(vis[root[x][i]])
dfs_clear(root[x][i]);
}
int main()
{
int n;
char key[16];
while(scanf("%d",&n)!=EOF)
{
int edit,m;
memset(root,-1,sizeof(root));
memset(vis,0,sizeof(vis));
memset(cnt,0,sizeof(cnt));
tot=2;
while(n--)
{
scanf("%s",key);
creat(key);
}
scanf("%d",&m);
while(m--)
{
ans=0;
scanf("%s %d",key,&edit);
len=strlen(key);
dfs(1,0,edit,key);
dfs_calc(1);
printf("%d/n",ans);
dfs_clear(1);
}
}
return 0;
}参看:http://acmicpc.org.cn/wiki/index.php?title=2009_Harbin_Fuzzy_Google_Suggest_Solution
ps:转载注明出处:pzjay!
除了模糊匹配外还有精确匹配,金山词霸手机版E文输入,T9输入法等许多优秀的手机E文输入软件都采用了精确匹配。以T9输入法为例,它摒弃传统的输入按键模式,假如你想输入ccc,传统的是要摁3*3=9下2键,但是假如ccc是经常使用的高频词汇的话,T9输入法只摁三下即可。牵扯到频率,肯定又是字典树的应用了,题目相关:HDOJ1298
本题先输入一个单词表,包括单词以及该单词的权值。然后输入一些数字串,要求模拟手机输入的过程,每输入一个数字,就输出对应的单词(如果没有对应的就输出MANUALLY),如果输入的数字会对应不同的单词的前缀,就输出权值之和最高的前缀(如果权值一样就按字母表顺序)。用Sample来说明,输入了hell,hello,idea这3个单词,权值对应分别为3,4,8,开始输入数字:输入4,4可以对应i和h,i是idea的前缀,权值之和为8,h是hell和hello的前缀,权值之和是3+4=7,输出权值较大的i;继续输入3,43对应的可以是he和id,同样因为id的权值大于he,就输出id;接下来输入5,435就只能对应hel了……依此类推,每次输出的都是权值之和最高的词
思想:trie+BFS
算法流程:
1。根据输入的单词建树
2。根据输入的按键序列依次转化为可能的字符序列,维护一个双端队列,将树中出现过(通过查找字典树实现)的字符序列入列,用于下次增加字符序列
3。若当前枚举到的按键序列遍历完所有可能后若最大权值还是-1,说明该按键序列没有匹配的字符串;否则输出权值最大的字符串即可。注意若字符序列中间出现不匹配,那么以后的都不匹配,但此时仍然要继续遍历依次输出不匹配,不能退出。见过HH大神map实现trie树的代码,很好很强大。(map <string,int>表示string出现的频率int)
[b][cpp] view
plaincopy[/b]
#include<iostream>
#include<一坨头文件>
#include<转载注明pzjay原创>
const int sup=100;
int num[10];//num[i]表示第i个键上面的字母个数
char T9[10][4];//T9[i][j]表示第i个键上第j个字母
deque <string> dq;
int n;
struct node
{
int count;//记录出现次数
node* next[26];
node(int fre)
{
count=fre;
memset(next,NULL,sizeof(next));
}
};
node* root;
void creat(char key[],int freq)
{
int i=0,index;
node* tmp=root;
while(key[i])
{
index=key[i]-'a';
if(NULL==tmp->next[index])
tmp->next[index]=new node(freq);
else
tmp->next[index]->count+=freq;
tmp=tmp->next[index];
++i;
}
}
int find(string key)
{
int i=0,index;
node* tmp=root;
while(i<key.length())
{
index=key[i]-'a';
if(NULL==tmp->next[index])
return -1;
tmp=tmp->next[index];
++i;
}
return tmp->count;//返回权值
}
void init()
{
int i,j;
char tmp='a';
for(i=2;i<10;++i)
num[i]=3;
++num[7];
++num[9];//第7和9个按键上各4个字母
for(i=2;i<10;++i)
for(j=0;j<num[i];++j)
T9[i][j]=tmp++;
}
void dele()//删除字典树
{
for(int i=0;i<26;++i)
if(root->next[i])
delete root->next[i];
delete root;
}
int main()
{
init();//初始化数组
char key[110];
int Case;
scanf("%d",&Case);
char tmp;
int frequency;
string str;
for(int pzjay=1;pzjay<=Case;++pzjay)
{
root=new node(0);
scanf("%d",&n);
while(n--)
{
scanf("%s %d",key,&frequency);
creat(key,frequency);
}
scanf("%d",&n);
int id;
string head;
string ans;
int max_frequency;
printf("Scenario #%d:/n",pzjay);
int increment,size;
while(n--)
{
scanf("%s",key);
size=1;//初始队列中一个元素
while(!dq.empty())
dq.pop_back();
dq.push_back("");//首先压入双端队列一个空字符串
//转载注明出处:pzjay
for(int i=0;key[i]!='1';++i)
{
id=key[i]-'0';//将按键转化为数字
increment=0;
max_frequency=-1;
for(int k=0;k<size;++k)
{
head=dq.front();//或者dq[0]也可
dq.pop_front();
for(int j=0;j<num[id];++j)
{
str=head+T9[id][j];
int value=find(str);
if(-1!=value)//找到了
{
dq.push_back(str);
++increment;//记录本次新增了多少个元素,本次新增的元素就是下次拓展的起点
if(value > max_frequency)
{
max_frequency=value;
ans=str;
}
}
}
}
size=increment;
if(max_frequency!=-1)
printf("%s/n",ans.c_str());
else
printf("MANUALLY/n");//其实这时可以退出for了,不过继续遍历也无妨,因为中间断掉,后面的肯定都不行
}
printf("/n");
}
printf("/n");
dele();
}
return pzjay;
}
字典树容易理解,用处广泛并且本文pzjay原创,— —|||
好吧,我承认我又装13标题党了。其实是G查询关键词过程中匹配的一点大概的算法框架,G的模糊匹配大家都知道,比如你输入64什么的,G会自动列出你心里可能要找
到东西,如下图:
那这个算法是怎么实现的呢,用到了一种高级数据结构--字典树,或者说是字典树思想,因为字典树不规定你具体怎么实现,可以二维数组,可以map……也可以通常的结构体+next指针。可以通过一个题来讲述,就是2009ACM/ICPC
哈尔滨 reginal现场赛G题:Fuzzy Google Suggest(http://acm.hit.edu.cn/judge/show.php?Proid=2888&Contestid=0)讲解。当时我搞这题,不知道字典树,然后一直模拟,结果……(—
—|||)先用输入的单词构造一棵字典树,节点数据包括:cnt,表示节点上的字母被多少个单词经过;vis,0表示经过此节点不能继续匹配,1表示经过此节点可继续匹配,2表示此节点就是恰好用于匹配的前缀的最后一个字符;然后一个next数组,大小26,但不是node指针,而是int数组,表示当前节点的儿子,next[i]==-1表示当前节点没有第i个儿子,否则有,并将此儿子结点进行编号,其编号就是它在字典树中的编号。然后根据编辑距离进行dfs遍历;函数设计为dfs(int
x,int pos,intedit,char* key),x是trie树中第x个节点,pos表示匹配到了前缀字符key的第pos个字符,edit表示剩余可用的编辑距离。假如某个字符符合当前前缀的匹配条件,则trie节点向儿子结点递归,pos++,edit不变dfs(root[x].next[key[pos]-'a'],++pos,edit,key);否则尝试使用编辑距离:1,增加一个字符,此时要遍历26个字符,看增加哪个合法(即此字符在trie中出现了并且是当前key[pos]的儿子节点并且此字符不跟key[pos]相同),然后继续dfs,此时编辑距离少一个,key的位置不变,trie走向儿子节点,假设增加的字符编号为i,则dfs(root[x].next[i],++pos,edit-1,key);2,替换一个字符,此时edit减一,pos向前走一个,dfs(root[x].next[i],pos+1,edit-1,key);3,删除一个字符,删除表示为trie节点不变,但是前缀字符串key串往下走一个,相当于就没匹配上的忽略,dfs(x,pos+1,edit-1,key),若能遍历下去,且x节点之前不可通行,则将x标记为可通行.到达匹配终点的条件有三个:1,前缀串key一路匹配到了末尾,此时的结点x被标记为,root[x].vis=2,表示它是某个前缀串的终结者。2,在tire中一路通行突然edit用完透支了,那这个前缀串没有找到匹配的单词,回溯。3,碰到了某个节点x,root[x].vis=2,说明到x这个前缀串已经能够匹配。返回可以匹配。然后再利用dfs_calc函数计数符合匹配的单词数量:vis=2的结点。最后用dfs_clear()函数清理trie树。关于销毁trie树,见有人用一个for循环搞定的,那样只是把和根节点直接相连的结点进行了delete,但是其他的都变成悬空状态,并未被销毁。坏习惯(但对ACM题来说不失为一种销毁的捷径)。不过用struct写的交上去老是RE,极度掣肘,只好参看某牛的改作数组实现的trie:
RE的:
[b][cpp] view
plaincopy[/b]
#include<pzjay>
#<一坨头文件>
const int sup=500005;
int tot;//tire结点个数
int len;//记录前缀词 的长度
int ans;//记录此前缀匹配单词的个数
struct node
{
int cnt;//表示此字母被多少个单词经过
int vis;//vis=0表示经过此单词不能够到达要匹配的结点;1表示可以;2表示此字母就是匹配前缀的最后一个字母(即匹配完毕)
int next[26];
}root[sup];
void creat(char key[])
{
int i=0,index;
int k=1;//root下标
while(key[i])
{
index=key[i]-'a';
if(-1==root[k].next[index])
{
root[k].next[index]=tot;//将root[tot]的地址赋给tmp->next[index]
root[tot].cnt=1;
root[tot].vis=0;
++tot;
}
else
++root[root[k].next[index]].cnt;
k=root[k].next[index];
++i;
}
}
int dfs(int x,int pos,int edit,char* key)//返回是否成功匹配
{
if(2==root[x].vis)//到达一个匹配的结束点
return 1;
if(edit<0)
return 0;
if(pos==len)//到达前缀的末尾
{
root[x].vis=2;//该节点是前缀的结束字母,x之前的单词串被成功匹配
return 1;
}
int index=key[pos]-'a';
if(-1!=root[x].next[index])//还有儿子结点
if(dfs(root[x].next[index],pos+1,edit,key))
root[x].vis=1;
for(int i=0;i<26;++i)
{
index=key[pos]-'a';
if(index==i || -1==root[x].next[i])//在树中找可替换的字符
continue;
if(dfs(root[x].next[i],pos+1,edit-1,key))//将pos处的字母尝试用i+'a'代替
root[x].vis=1;
if(dfs(root[x].next[i],pos,edit-1,key))//插入一个字母
root[x].vis=1;
}
if(dfs(x,pos+1,edit-1,key))//delete
if(0==root[x].vis)
root[x].vis=1;
return root[x].vis;
}
void dfs_calc(int x)
{
if(2==root[x].vis)
{
ans+=root[x].cnt;
return;
}
for(int i=0;i<26;++i)
if(root[root[x].next[i]].vis > 0)
dfs_calc(root[x].next[i]);
}
void dfs_clear(int x)
{
root[x].vis=0;
for(int i=0;i<26;++i)
if(root[root[x].next[i]].vis > 0)
dfs_clear(root[x].next[i]);
}
int main()
{
int n;
//freopen("1.txt","r",stdin);
while(scanf("%d",&n)!=EOF)
{
tot=2;
char key[25];
int m;
int edit;//编辑距离
for(int i=0;i<sup;++i)
memset(root[i].next,-1,sizeof(root[i].next));
//fill(root[i].next,root[i].next+26,-1);
while(n--)
{
scanf("%s",key);
creat(key);
}
scanf("%d",&m);//m个前缀
while(m--)
{
ans=0;
scanf("%s %d",key,&edit);
len=strlen(key);
dfs(1,0,edit,key);
//1是x的起始遍历位置,0是前缀key的起始位置,edit是剩余的编辑距离
dfs_calc(1);//计数符合匹配的单词个数
dfs_clear(1);//清空x
printf("%d/n",ans);
}
}
return 0;
}
AC:
const int sup=700005;
int tot;//tire结点个数
int len;//记录前缀词 的长度
int ans;//记录此前缀匹配单词的个数
int root[sup][26];//每个节点最多26个分支
int cnt[sup],vis[sup];//cnt[i]记录字母i被多少个单词经过
void creat(char key[])
{
int k=1,index,i=0;
while(key[i])
{
index=key[i]-'a';
if(-1==root[k][index])
root[k][index]=tot++;
k=root[k][index];
++cnt[k];
++i;
}
}
int dfs(int x,int pos,int edit,char key[])
{
if(2==vis[x])
return 1;
if(edit<0)
return 0;
if(pos==len)//匹配完毕,节点x成为前缀词key的结尾字母
{
vis[x]=2;
return 1;
}//以上可以直接return的,都是最终的结果:匹配成功或者失败
//下面的只是递归到最重结果的过程,故是对vis赋值
int index=key[pos]-'a';
if(-1!=root[x][index])//可以继续往深层遍历
if(dfs(root[x][index],pos+1,edit,key))
vis[x]=1;//从x往下可以走到目标节点
for(int i=0;i<26;++i)
{
index=key[pos]-'a';
if(index==i || -1==root[x][i])//筛选掉跟要替换的字母相同的字母和未在trie树中出现的字母
continue;
if(dfs(root[x][i],pos+1,edit-1,key))//pos++,遍历下一个字母,表示替换一个trie树中存在的字母
vis[x]=1;
if(dfs(root[x][i],pos,edit-1,key))//pos不变.表示增加一个字母
vis[x]=1;
}
if(dfs(x,pos+1,edit-1,key))//删除一个字母
if(0==vis[x])
vis[x]=1;
return vis[x];
}
void dfs_calc(int x)
{
if(2==vis[x])
{
ans+=cnt[x];
return;
}
for(int i=0;i<26;++i)
if(vis[root[x][i]])
dfs_calc(root[x][i]);
}
void dfs_clear(int x)
{
vis[x]=0;
for(int i=0;i<26;++i)
if(vis[root[x][i]])
dfs_clear(root[x][i]);
}
int main()
{
int n;
char key[16];
while(scanf("%d",&n)!=EOF)
{
int edit,m;
memset(root,-1,sizeof(root));
memset(vis,0,sizeof(vis));
memset(cnt,0,sizeof(cnt));
tot=2;
while(n--)
{
scanf("%s",key);
creat(key);
}
scanf("%d",&m);
while(m--)
{
ans=0;
scanf("%s %d",key,&edit);
len=strlen(key);
dfs(1,0,edit,key);
dfs_calc(1);
printf("%d/n",ans);
dfs_clear(1);
}
}
return 0;
}参看:http://acmicpc.org.cn/wiki/index.php?title=2009_Harbin_Fuzzy_Google_Suggest_Solution
ps:转载注明出处:pzjay!
除了模糊匹配外还有精确匹配,金山词霸手机版E文输入,T9输入法等许多优秀的手机E文输入软件都采用了精确匹配。以T9输入法为例,它摒弃传统的输入按键模式,假如你想输入ccc,传统的是要摁3*3=9下2键,但是假如ccc是经常使用的高频词汇的话,T9输入法只摁三下即可。牵扯到频率,肯定又是字典树的应用了,题目相关:HDOJ1298
本题先输入一个单词表,包括单词以及该单词的权值。然后输入一些数字串,要求模拟手机输入的过程,每输入一个数字,就输出对应的单词(如果没有对应的就输出MANUALLY),如果输入的数字会对应不同的单词的前缀,就输出权值之和最高的前缀(如果权值一样就按字母表顺序)。用Sample来说明,输入了hell,hello,idea这3个单词,权值对应分别为3,4,8,开始输入数字:输入4,4可以对应i和h,i是idea的前缀,权值之和为8,h是hell和hello的前缀,权值之和是3+4=7,输出权值较大的i;继续输入3,43对应的可以是he和id,同样因为id的权值大于he,就输出id;接下来输入5,435就只能对应hel了……依此类推,每次输出的都是权值之和最高的词
思想:trie+BFS
算法流程:
1。根据输入的单词建树
2。根据输入的按键序列依次转化为可能的字符序列,维护一个双端队列,将树中出现过(通过查找字典树实现)的字符序列入列,用于下次增加字符序列
3。若当前枚举到的按键序列遍历完所有可能后若最大权值还是-1,说明该按键序列没有匹配的字符串;否则输出权值最大的字符串即可。注意若字符序列中间出现不匹配,那么以后的都不匹配,但此时仍然要继续遍历依次输出不匹配,不能退出。见过HH大神map实现trie树的代码,很好很强大。(map <string,int>表示string出现的频率int)
[b][cpp] view
plaincopy[/b]
#include<iostream>
#include<一坨头文件>
#include<转载注明pzjay原创>
const int sup=100;
int num[10];//num[i]表示第i个键上面的字母个数
char T9[10][4];//T9[i][j]表示第i个键上第j个字母
deque <string> dq;
int n;
struct node
{
int count;//记录出现次数
node* next[26];
node(int fre)
{
count=fre;
memset(next,NULL,sizeof(next));
}
};
node* root;
void creat(char key[],int freq)
{
int i=0,index;
node* tmp=root;
while(key[i])
{
index=key[i]-'a';
if(NULL==tmp->next[index])
tmp->next[index]=new node(freq);
else
tmp->next[index]->count+=freq;
tmp=tmp->next[index];
++i;
}
}
int find(string key)
{
int i=0,index;
node* tmp=root;
while(i<key.length())
{
index=key[i]-'a';
if(NULL==tmp->next[index])
return -1;
tmp=tmp->next[index];
++i;
}
return tmp->count;//返回权值
}
void init()
{
int i,j;
char tmp='a';
for(i=2;i<10;++i)
num[i]=3;
++num[7];
++num[9];//第7和9个按键上各4个字母
for(i=2;i<10;++i)
for(j=0;j<num[i];++j)
T9[i][j]=tmp++;
}
void dele()//删除字典树
{
for(int i=0;i<26;++i)
if(root->next[i])
delete root->next[i];
delete root;
}
int main()
{
init();//初始化数组
char key[110];
int Case;
scanf("%d",&Case);
char tmp;
int frequency;
string str;
for(int pzjay=1;pzjay<=Case;++pzjay)
{
root=new node(0);
scanf("%d",&n);
while(n--)
{
scanf("%s %d",key,&frequency);
creat(key,frequency);
}
scanf("%d",&n);
int id;
string head;
string ans;
int max_frequency;
printf("Scenario #%d:/n",pzjay);
int increment,size;
while(n--)
{
scanf("%s",key);
size=1;//初始队列中一个元素
while(!dq.empty())
dq.pop_back();
dq.push_back("");//首先压入双端队列一个空字符串
//转载注明出处:pzjay
for(int i=0;key[i]!='1';++i)
{
id=key[i]-'0';//将按键转化为数字
increment=0;
max_frequency=-1;
for(int k=0;k<size;++k)
{
head=dq.front();//或者dq[0]也可
dq.pop_front();
for(int j=0;j<num[id];++j)
{
str=head+T9[id][j];
int value=find(str);
if(-1!=value)//找到了
{
dq.push_back(str);
++increment;//记录本次新增了多少个元素,本次新增的元素就是下次拓展的起点
if(value > max_frequency)
{
max_frequency=value;
ans=str;
}
}
}
}
size=increment;
if(max_frequency!=-1)
printf("%s/n",ans.c_str());
else
printf("MANUALLY/n");//其实这时可以退出for了,不过继续遍历也无妨,因为中间断掉,后面的肯定都不行
}
printf("/n");
}
printf("/n");
dele();
}
return pzjay;
}
字典树容易理解,用处广泛并且本文pzjay原创,— —|||
相关文章推荐
- Google字符串模糊匹配算法,字典树模糊查询
- Google字符串模糊匹配算法,字典树模糊查询
- 快速字符串模糊匹配--基于Horspool的模糊匹配算法
- 字符串的模糊匹配算法(从简单到复杂)
- 生成查询的模糊匹配字符串
- 生成查询的模糊匹配字符串.sql
- 中文字符串模糊匹配算法|C# Levenshtein Distance
- 生成查询的模糊匹配字符串.sql
- sql中生成查询的模糊匹配字符串
- sql中生成查询的模糊匹配字符串
- 基于KMP与Levenshtein模糊匹配算法的银行联行号查询(转)
- 基于KMP与Levenshtein模糊匹配算法的银行联行号查询
- Levenshtein 算法:相似度--模糊查询,数据匹配
- 字符串查找算法总结(暴力匹配、KMP 算法、Boyer-Moore 算法和 Sunday 算法)
- 一种比KMP更优的字符串模式匹配算法
- 字符串查找算法总结(暴力匹配、KMP 算法、Boyer-Moore 算法和 Sunday 算法)
- 字符串模式匹配算法——BM、Horspool、Sunday、KMP、KR、AC算法一网打尽
- SQL语句实现按关健字模糊查询,并按匹配度排序
- SQL SERVER模糊匹配查询
- 字符串模式匹配的几种算法