字典树在车站查询功能中的应用
1.在12306的火车票订票系统中,当我们在出发地或者目的地框中输入一个汉语拼音的简写时,就会出现相应的地名。如输入"wh"就会出现"武汉","威海","芜湖"等地名供选择。
2.用数据库实现上面的功能:建立一张表包括两个字段,一个字段用于存储汉字地名,另一个用于存储汉字拼音的简写。对于每次查询需要遍历整张表的记录数,筛选出满足条件的记录。假设每次查询字符串的长度为n,数据库的记录数为m。那么每次查询的时间复杂就是O(n*m)。另外数据库的信息一般都存放在磁盘中。
3.使用字典树的思想:在系统初始化的时候将拼音简写与汉字名称形成一棵字典树放入到内存中。每次查询只需要n次比较就可以找到结果。
4.数据的逻辑结构
struct TrieNode{ string name;//存储汉字地名信息 int flag;//标识是否是一个简写的结尾 TrieNode *next[26]; };
5.字典树的初始化,假设已经有一个名为in.dat文件中写好了车站名与简拼之间的关系。从文件中读取数据,对于每一个<车站名,简拼>将简拼根据字典树的原理从根节点依次插入到字典树中。当处理到简拼的最后一个字符时,将车站名称存放到该节点中。代码实现如下:
#include<stdio.h> #include<string.h> #include<string> #include<iostream> using namespace std; struct TrieNode{ string name;//存储汉字地名信息 int flag;//标识是否是一个简写的结尾 TrieNode *next[26]; TrieNode() { flag=0; int i; for(i=0;i<26;i++) { next[i]=NULL; } } }; TrieNode *root = new TrieNode();//定义一个根节点 /** * str拼音简写,Cname车站名称 */ void insert(char str[],string Cname) { TrieNode *p = root; int len=strlen(str),i,index; for(i=0;i<len;i++) { //根据字符的值映射其所在的内存单元。这应该也算是一种简单的hash index = str[i]-'a'; if(p->next[index]==NULL)p->next[index]=new TrieNode(); p=p->next[index]; } //最后一节点要有车站名和简写结束的标记 p->flag=1; p->name=Cname; } /** * str待查询的简写字符串如"nj" */ int query(char str[]) { TrieNode *p = root; int len=strlen(str),i,index; for(i=0;i<len;i++) { //只允许查询的字符串有包含小写字母 if(str[i]>='z' || str[i]<='a')return -1; index = str[i]-'a'; p=p->next[index]; if(p==NULL)break; } if(p==NULL || p->flag==0)//待查询的字符串没有与其对应的结果 { cout<<"Not Found!"<<endl; return 0; }else{ //打印找到的结果 cout<<p->name<<endl; return 1; } } int main() { char simple[15],tname[20]; //读取配置文件信息 freopen("in.dat","rw",stdin);//输入重定向,从文件中读取数据 while(scanf("%s%s",simple,tname)!=EOF) { string tempName(tname); //生成字典树 insert(simple,tempName); } query("nj");//南京 query("bj");//北京 query("nb");//没找到 query("sb");//没找到 query("右");//不会打印出任何信息 query("njn");//南京南 return 0; }
in.data的数据如下:
nj 南京
njn 南京南
bj 北京
sh 上海
gz 广州
wh 武汉
6.从query函数中可以看出查找一个字符串的时间复杂度为O(n)。其效率比使用数据库存储要快很多。
7.在in.data中的最后一行加入"wh 芜湖",再执行query("wh");结果只显示"芜湖",也就是说对于相同的简写后面的数据会将前面的数据信息覆盖。解决办法:将TrieNode结构中的string name换成queue<string>队列行式,这样当有相同的简写时就将地名插入到队列中,查询时输出队列中的所有元素。当然也可以将name形成一个链表的形式,如使用链表形式,则数据结构可如下设计:
struct Node{ string name; Node *child; }; struct TrieNode{ struct Node place; int flag; int next[26]; };
8. 在12306上当我们输入"w"时也给出地名供选择,而不是"wh"这样完整的情况下才能查询。要实现这种功能,只需要递归查询"w"所在节点的孩子节点,就可以查询出所有以"w"简写开头的地名。
实现代码如下:
void querySubNode(TrieNode *p) { int i; TrieNode *temp = p; if(p==NULL)return; if(p->flag){ cout<<p->name<<endl; } for(i=0;i<26;i++) { if(temp->next[i]!=NULL) { querySubNode(temp->next[i]); } } }
上面功能是基于第5步中的代码实现的。当再次执行query("n")时就会出现"南京","南京南"相应的信息。
9.用字典树和数据库实现车站查询功能的对比
(1).使用数据库实现时,信息存放在磁盘中,查询是对整个表进行比较。查询速度慢
(2).使用字典树在系统初始化时,即将信息以字典树的结构加入到内存。查询速度那是相当快。车站与简写对应信息变化时,只需要重新生成字典树就行。