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