trie树前缀匹配

题目:搜索功能一般都有根据你的输入快速显示对应关键字的功能,比如你输入”刘”, 搜索框的下拉列表会显示“刘德华”,”刘若英”,”刘欢”等,你继续输入‘德’,将查询关键字变成”刘德”,显示的候选字列表会显示”刘德华”,”刘德华专辑”,”刘德华演唱会”等。如果让你用算法和数据结构实现这个功能(用户每次多输入一个字母都可以得到最佳的查询结果,每次返回最多不超过10条),你会如何设计。能否用程序实现。

一、算法设计
1) 采用trie树来实现前缀匹配,如果匹配成功,返回所有前缀相同的字符串,否则返回空;
2) 如果匹配上的字符串超过10条,用堆排找出搜索热度前10的字符串(这部分没有写代码);

二、代码与运行结果

代码参考了网络上他人的代码成果,针对题目进行了修改,特此声明。
完整代码。

  1 #include <iostream>
  2 #include <vector>
  3 using namespace std;
  4 
  5 const int sonNums = 26;
  6 
  7 struct node
  8 {
  9     bool endFlag;
 10     node* sons[sonNums];
 11 
 12     node() 
 13     { 
 14         endFlag = false; 
 15         for ( int i = 0; i < sonNums; i++ )
 16             sons[i] = NULL;
 17     }
 18 };
 19 
 20 class Trie
 21 {
 22 public:
 23     void insert(const char* str);
 24     bool query(const char* str);
 25     void getStr(node *tree, vector<char*>& ret, vector<char> prefix);
 26     Trie() { root = new node();}
 27 
 28 private:
 29     node *root;
 30 };
 31 
 32 void Trie::insert(const char* str)
 33 {
 34     cout << "insert: " << str << endl;
 35 
 36     node *p = root;
 37     while( *str != '\0')
 38     {
 39         int index = *str - 'a';
 40         if ( p->sons[index] == NULL )
 41         {
 42             p->sons[index] = new node();
 43         }
 44         p = p->sons[index];
 45         str++;
 46     }
 47     p->endFlag = true;
 48 }
 49 
 50 bool Trie::query(const char* str)
 51 {
 52     node *p = root;
 53     const char *tmp = str;
 54     vector<char*> ret;
 55     vector<char> prefix;
 56 
 57     int index = *str - 'a';
 58     if ( !p->sons[index])
 59     {
 60         cout << "\nprefix query for \"" << tmp << "\" failed" << endl;
 61         return false;
 62     }
 63 
 64     while( *str != '\0' && p )
 65     {
 66         int index = *str - 'a';
 67         if ( p->sons[index] )
 68         {
 69             prefix.push_back(*str);
 70         }
 71 
 72         p = p->sons[index];
 73         str++;
 74     }
 75 
 76     if ( p != NULL )
 77     {
 78         getStr( p, ret, prefix );
 79 
 80         vector<char*>::iterator iter;
 81         cout << "\nprefix query for \""<< tmp << "\", results:" << endl;
 82         for ( iter = ret.begin(); iter != ret.end(); iter++ )
 83             cout << *iter << endl;
 84 
 85         return true;
 86     }
 87     else
 88     {
 89         cout << "\nprefix query for \"" << tmp << "\" failed" << endl;
 90         return false;
 91     }
 92 }
 93 
 94 void Trie::getStr(node *tree, vector<char*>& ret, vector<char> prefix)
 95 {
 96     if ( tree->endFlag )
 97     {    
 98         char *str = new char[prefix.size()+1];
 99         int i;
100         for ( i=0; i<prefix.size(); i++ )
101             str[i] = prefix[i];
102         str[i] = '\0';
103         ret.push_back(str);
104     }
105 
106     node *p = tree;
107     for (int i = 0; i < 26; i++)
108     {
109         if ( p->sons[i] != NULL )
110         {
111             prefix.push_back('a'+i);
112             getStr( p->sons[i], ret, prefix);
113         }
114     }
115 }
116 
117 int main()
118 {
119     Trie t;
120     t.insert("a");
121     t.insert("abandon");
122     t.insert("abandoned");
123     t.insert("abashed");
124 
125     t.query("a");
126     t.query("aba");
127     t.query("aban");
128     t.query("ban");
129     
130     return 0;
131 }
View Code

运行结果:

 1 insert: a
 2 insert: abandon
 3 insert: abandoned
 4 insert: abashed
 5 
 6 prefix query for "a", results:
 7 a
 8 abandon
 9 abandoned
10 abanshed
11 
12 prefix query for "aba", results:
13 abandon
14 abandoned
15 abanshed
16  
View Code

三、存在的问题

1) 中文处理比较麻烦,只实现了英文字母的匹配;
2) 对每个节点开辟了26个分支,用分支下标表示字母,这种方式节省了时间浪费了空间,如果要节省空间应采用链表;
3) 为了挑选最热的10个字符串,我把所有的匹配结果都找出来然后用堆排找出最热的10个字符串,我没想到更好的方法;
4)未考虑查询扩展,比如汉字拼音扩展。

原文地址:https://www.cnblogs.com/simonote/p/3086958.html