微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

字典树(Trie) 与双数组字典树(DoubleArrayTrie)

 ======================= **基础知识** =======================

字典树(Trie): 单词查找树, 可用于单词查找,字符串排序;

  在大部分的树中, 节点 代表 集合边 代表 关系;(很重要,代码实现中很多地方都体现);

  字典树的具体结构如下图,其中每一条边代表一个字符不同节点颜色代表以该节点结尾的单词是否存在(粉色:存在;白色:不存在)。

    

双数组字典树(DoubleArrayTrie): 逻辑结构还是那个结构,只是换了一种信息表示方法;空间利用率极高,节省空间;

                其中有类似 完全二叉树知识点;(父节点 与 子节点之间关系child_i = base[father] + i);

                也有些理念类似 hashmap ,都会涉及到冲突(check[child_i] = fater),以及各种解决冲突方式;

  结构性质:通过2个数组(base, check) 实现字典树的信息记录: base[] 记录每个节点的基数其对应的所有子节点可以通过 base[father] + i 来找到;而每个父节点并不一定用完26个子节点,所有就会有部分节点信息处于可用状态。所以对于后面节点来讲,可以利用前面父节点没用上的子节点;对于子节点来讲,如果可能由 >1个父节点到达,就通过check[] 标记父节点下标,有因为下标都 >0, 所以用负数表示结尾字符

  这里对于父节点基数的选取,来实现所有子节点都只有唯一指向的父节点下标 是 DAT 中可以做很多文章的地方;

  这种做法类似与DP 中两种解题思路(从哪里来;到哪里去); 对于DAT 来讲,通过check 数组记录了每个节点从哪里来;而TrieUpdated Trie 都通过记录到从该节点可以到哪里去;这就是对于信息的不同表示方法

    

  

  优点在空间利用率极高,远比Updated Trie节省空间;

    a. 基础版本Trie:通过在每个节点中Node *next[26] 实现每条边信息记录;每个节点空间为 sizeof(Node);

    b. Updated Trie:通过数组记录下标方式实现 int next[26] 实现每条边的信息记录;(sizeof(int) <= sizeof(Node*));

    c. Double Array Trie:将父节点与子节点 之间关系通过 类似完全二叉树的方式实现(child_i = base[father] + i),而不再是通过数组记录方式;通过check[child_i] = father 方式指明父节点,解决冲突问题,通过-father作为字符结尾标记;最好情况下每个节点空间 sizeof(int) * 2, 最差情况等价与Updated Trie;


======================= **代码演示** =======================

1. 字典树(Trie) 基本代码

  1 #include <string>
  2 #include <iostream>
  3 using namespace std;
  4 #define BASE 26
  5 
  6 class Node{
  7 public:
  8     Node():flag(false) {
  9         for(int i = 0; i < BASE; ++i) next[i] = nullptr;
 10     }
 11     ~Node() {}
 12     bool flag;
 13     Node *next[BASE];
 14 };
 15 
 16 class Trie{
 17 public:
 18     Trie() {
 19         root = new Node();
 20     }
 21     ~Trie() {
 22         clearNode(root);
 23     }
 24     bool insert(string s) {
 25         Node *pnode = root;
 26         for(auto &x : s) {
 27             int ind = x - 'a';
 28             if(pnode->next[ind] == nullptr) pnode->next[ind] = new Node();
 29             pnode = pnode->next[ind];
 30         }
 31         if(pnode->flag) return false;
 32         pnode->flag = true;
 33         return true;
 34     }
 35 
 36     bool search(string s) {
 37         Node *pnode = root;
 38         for(auto &x : s) {
 39             int ind = x - 'a';
 40             if(pnode->next[ind] == nullptr) return false;
 41             pnode = pnode->next[ind];
 42         }
 43         return pnode->flag;
 44     }
 45     void output() {
 46         __output(root, "");
 47         return;
 48     }
 49 
 50     Node *root;
 51 
 52 private:
 53     static void clearNode(Node *pnode) {
 54         if(pnode == nullptr) return;
 55         for(int i = 0; i < BASE; ++i) clearNode(pnode->next[i]);
 56         delete pnode;
 57         return;
 58     }
 59 
 60     static void __output(Node *p, string s) {
 61         if(p == nullptr) return;
 62         if(p->flag) printf("%s\n", s.c_str());
 63         for(int i = 0; i < BASE; ++i) {
 64             if(p->next[i]) __output(p->next[i], s+(char(i + 'a')));
 65         }
 66         return;
 67     }
 68 
 69 }; 
 70 
 71 
 72 int main()
 73 {
 74     int n,op;
 75     string input; 
 76     Trie t1;
 77     cin >> n;
 78     while(n--) {
 79         cin >> input;
 80         t1.insert(input);
 81     }
 82 
 83     printf("out put all words in seq:\n");
 84     t1.output();
 85 
 86     while(cin >> input) {
 87         printf("find string: %10s, result is %d\n", input.c_str(), t1.search(input));
 88     }
 89 
 90     return 0;
 91 }
 92 
 93  //input EXAM
 94 7
 95 hello
 96 world
 97 angle
 98 trie
 99 doublearray
100 test
101 check
102 llo
103 trie
104 hel
105 twes
106 angle
Trie

 2. 使用数组下标代替指针,减少Trie 占用空间;其中index = 0 为 特殊位,index = 1 为根节点;

 1 #include <string>
 2 #include <iostream>
 3 using namespace std;
 4 
 5 #define BASE 26
 6 #define MAX_CNT 10000
 7 class Node {
 8 public:
 9     bool flag;
10     int next[BASE];
11     void clear(){
12         flag = 0;
13         for(int i = 0; i < BASE; ++i) next[i] = 0;
14         return;
15     }
16 }Trie[MAX_CNT];
17 
18 int root, cnt;
19 void clearTrie() {          //通过函数实现初始化
20     root = 1,cnt = 2;       //index = 0: 作为空值; index = 1 : 作为根; cnt = 2: 取决于下面NewNode实现;
21     Trie[root].clear();
22     return;
23 }
24 
25 int getNewNode(){
26     Trie[cnt].clear();
27     return cnt++;
28 }
29 
30 void insert(string s) {
31     int p = root;
32     for(auto x : s) {
33         int ind = x - 'a';
34         if(Trie[p].next[ind] == 0) Trie[p].next[ind] = getNewNode();
35         p = Trie[p].next[ind];
36     }
37     Trie[p].flag = true;
38     return;
39 }
40 
41 bool search(string s) {
42     int p = root;
43     for(auto x : s) {
44         int ind = x - 'a';
45         p = Trie[p].next[ind];
46         if(p == 0) return false;
47     }
48     return Trie[p].flag;
49 }
50 
51 void __output(int idx, string s) {
52     if(idx == 0) return;
53     if(Trie[idx].flag) printf("%s\n", s.c_str());
54     for(int i = 0; i < BASE; ++i) {
55         if(Trie[idx].next[i] == 0) continue;
56         __output(Trie[idx].next[i], s + char(i + 'a'));
57     }
58     return;
59 }
60 
61 void output() {
62     __output(root, "");
63     return;
64 }
65 
66 int main()
67 {
68     int n;
69     string input; 
70     clearTrie();
71     cin >> n;
72     while(n--) {
73         cin >> input;
74         insert(input);
75     }
76 
77     printf("out put all words in seq:\n");
78     output();
79 
80     while(cin >> input) {
81         printf("find string: %10s, result is %d\n", input.c_str(), search(input));
82     }
83 
84     return 0;
85 }
Updated_Trie

 3.双数组字典树(Double Array Trie): 使用2个数组以及类似完全二叉树父/子映射方式,记录树的一层层关系;;独立成词通过负数实现;

  下面代码实现中:对于check 数组中冲突问题,使用最简单的不断查找方式实现;

  1 #include <iostream>
  2 #include <cstring>
  3 using namespace std;
  4 #define BASE 26
  5 #define TEST_SEARCH(func, input) printf("%15s return %d\n", #func, func(input))
  6 //Default Trie
  7 struct Node {
  8     bool flag;
  9     Node *next[BASE];
 10     Node():flag(false){
 11         for(int i = 0; i < BASE; ++i) next[i] = nullptr;
 12     }
 13 };
 14 int cnt = 1;
 15 Node *root;
 16 
 17 void insert(string s) {
 18     Node *p = root;
 19     for(int i = 0; s[i]; ++i) {
 20         int idx = s[i] - 'a';
 21         if(p->next[idx] == nullptr) {
 22             p->next[idx] = new Node();
 23             cnt++;
 24         }
 25         p = p->next[idx];
 26     }
 27     p->flag = true;
 28     return;
 29 }
 30 
 31 bool search(string s) {
 32     Node *p = root;
 33     for(int i = 0; s[i]; ++i) {
 34         int ind = s[i] - 'a';
 35         if(p->next[ind] == nullptr) return false;
 36         p = p->next[ind];
 37     }
 38     return p->flag;
 39 }
 40 
 41 void __output(Node *p, string s) {
 42     if(p->flag) cout << s << endl;
 43     for(int i = 0; i < BASE; ++i) {
 44         if(nullptr == p->next[i])  continue;
 45         __output(p->next[i], s + char(i + 'a'));
 46     }
 47     return;
 48 }
 49 
 50 void output(){
 51     __output(root, "");
 52     return;
 53 }
 54 
 55 //Updated Trie;
 56 #define MAX_N 1000
 57 struct Node_updated{
 58     bool flag;
 59     int next[BASE];
 60 }Trie[MAX_N];
 61 
 62 int root_updated, cnt_updated;
 63 void clearTrie() {          //通过函数实现初始化
 64     root_updated = 1, cnt_updated = 2;      //index = 0: 作为空值; index = 1 : 作为根; cnt_updated = 2:
 65     Trie[root_updated].flag = false;
 66     for(int i = 0; i < BASE; ++i) Trie[root_updated].next[i] = 0;
 67     return;
 68 }
 69 
 70 int genNewNode(){
 71     Trie[cnt_updated].flag = false;
 72     for(int i = 0; i < BASE; ++i) Trie[cnt_updated].next[i] = 0;
 73     return cnt_updated;
 74 }
 75 
 76 void insert_updated(string s) {
 77     int cur = root_updated;
 78     for(int i = 0; s[i]; ++i) {
 79         int index = s[i] - 'a';
 80         if(0 == Trie[cur].next[index]) {
 81             Trie[cur].next[index] = genNewNode();
 82             cnt_updated++;
 83         }
 84         cur = Trie[cur].next[index];
 85     }
 86     Trie[cur].flag = true;
 87     return;
 88 }
 89 
 90 bool search_updated(string s) {
 91     int cur = root_updated;
 92     for(int i = 0; s[i]; ++i) {
 93         int index = s[i] - 'a';
 94         if(0 == Trie[cur].next[index]) return false;
 95         cur = Trie[cur].next[index];
 96     }
 97     return Trie[cur].flag;
 98 }
 99 
100 void __output(int idx, string s) {
101     if(Trie[idx].flag) printf("%s\n", s.c_str());
102     for(int i = 0; i < BASE; ++i) {
103         if(0 == Trie[idx].next[i]) continue;
104         __output(Trie[idx].next[i], s + char(i + 'a'));
105     }
106     return;
107 }
108 
109 void output_updated() {
110     __output(root_updated, "");
111     return;
112 }
113 
114 
115 //DOUBLE ARRAY TRIE
116 int *base, *check, root_DAT, cnt_DAT;
117 int getBase(int r, int *base, int *check) {
118     int b = 1, flag = 0;
119     while(0 == flag) {
120         b++;
121         flag = 1;
122         for(int i = 0; i < BASE; ++i) {
123             if(0 == Trie[r].next[i]) continue;
124             if(0 == check[b + i]) continue;  //不用base 数组,因为base 用来指向子节点,而叶子节点没有子节点,
125                                             //                  而check 指向父节点,每个节点都有父节点(除了根,不过根 = 1,不可能到达);
126             flag = 0;
127             break;
128         }
129     }
130     return b;
131 }
132 
133 void convertTodoubleArrayTrie(int r, int d_r, int *base, int *check) {
134     if(r == 0) return;
135     base[d_r] = getBase(r, base, check);
136     for(int i = 0; i < BASE; ++i) {
137         if(0 == Trie[r].next[i]) continue;
138         check[base[d_r] + i] = d_r;
139         if(Trie[Trie[r].next[i]].flag) check[base[d_r] + i] = -d_r;
140     }
141     cnt_DAT = max(cnt_DAT, d_r);
142     for(int i = 0; i < BASE; ++i) {
143         if(0 == Trie[r].next[i]) continue;
144         convertTodoubleArrayTrie(Trie[r].next[i], base[d_r] + i, base, check);
145         cnt_DAT = max(cnt_DAT, base[d_r] + i);
146     }
147 
148     return;
149 }
150 
151 bool search_DAT(string s) {
152     int r = root_DAT;
153     for(int i = 0; s[i]; ++i) {
154         int idx = s[i] - 'a';
155         if(abs(check[base[r] + idx]) != r) return false;
156         r = base[r] + idx;
157     }
158     return check[r] < 0;
159 }
160 
161 
162 
163 int main()
164 {
165     //Default Trie
166     root = new Node();
167     cnt = 1;
168     //Updated Trie
169     clearTrie();
170 
171     int n;
172     string input;
173     cin >> n;
174     while(n--){
175         cin >> input;
176         insert(input);
177         insert_updated(input);
178     }
179 
180     printf("--------------Output all--------------\n");
181     output();
182 #ifdef DEBUG
183     output_updated();
184 #endif
185     printf("----------Done-------------------\n");
186 
187     //Double Array Trie
188     base = (int *)malloc(sizeof(int) * MAX_N);
189     check = (int *)malloc(sizeof(int) * MAX_N);
190     memset(base, 0, sizeof(int) * MAX_N);
191     memset(check, 0, sizeof(int) * MAX_N);
192     root_DAT = 1, cnt_DAT = 1;
193     convertTodoubleArrayTrie(root_updated, root_DAT, base, check);
194 
195 #ifdef DEBUG
196     printf("%6s:","index");
197     for(int i = 0; i < cnt_DAT; ++i) {
198         printf("%3d,",i);
199     }
200     printf("\n%6s:","base");
201     for(int i = 0; i < cnt_DAT; ++i) {
202         printf("%3d,",base[i]);
203     }
204     printf("\n%6s:","check");
205     for(int i = 0; i < cnt_DAT; ++i) {
206         printf("%3d,",check[i]);
207     }
208     printf("\n");
209 #endif
210 
211     printf("%s:\n%25s:%lu\n%25s:%lu\n%25s:%lu\n","memory usage check",
212                 "default Trie size", sizeof(Node) * cnt,
213                 "Updated Trie size", sizeof(Node_updated) * cnt_updated,
214                 "Double Array Trie size", sizeof(int) * 2 * (cnt_DAT + 1));
215     printf("----------------------------------------\n");
216     while(cin >> input) {
217         printf("search word: %s\n", input.c_str());
218         TEST_SEARCH(search, input);
219         TEST_SEARCH(search_updated, input);
220         TEST_SEARCH(search_DAT, input);
221     }
222     return 0;
223 }
DoubleArrayTrie

 

======================= **经典问题** =======================

1. 208. 实现 Trie (前缀树) :Trie 裸题;

 1 class Trie {
 2 public:
 3 #define BASE 26
 4     struct Node{
 5         bool flag;
 6         int next[BASE];
 7         Node():flag(false) {
 8             for(int i = 0; i < BASE; ++i) next[i] = 0;
 9         }
10     };
11 
12     vector<Node> inform;
13     Trie():inform(vector<Node>(2,Node())) {}
14     
15     void insert(string word) {
16         int r = 1;
17         for(int i = 0; word[i]; ++i) {
18             int index = word[i] - 'a';
19             if(0 == inform[r].next[index]) {
20                 inform[r].next[index] = inform.size();
21                 inform.push_back(Node());
22             }
23             r = inform[r].next[index];
24         }
25         inform[r].flag = true;
26         return;
27     }
28     
29     bool search(string word) {
30         int r = 1;
31         for(int i = 0; word[i]; ++i) {
32             int index = word[i] - 'a';
33             if(0 == inform[r].next[index]) return false;
34             r = inform[r].next[index];
35         }
36         return  inform[r].flag;
37     }
38     
39     bool startsWith(string prefix) {
40         int r = 1;
41         for(int i = 0; prefix[i]; ++i) {
42             int index = prefix[i] - 'a';
43             if(0 == inform[r].next[index]) return false;
44             r = inform[r].next[index];
45         }
46         return true;
47     }
48 };
49 
50 /**
51  * Your Trie object will be instantiated and called as such:
52  * Trie* obj = new Trie();
53  * obj->insert(word);
54  * bool param_2 = obj->search(word);
55  * bool param_3 = obj->startsWith(prefix);
56  */
UpdatedTrie

2. 1268. 搜索推荐系统 :  Trie + 记忆化 + 函数封装;

  这题是字符补全的程序实现,Trie 在实际应用中根据具体需求来实现,所以并没有固定的接口与 形式;

  1 class Solution {
  2 public:
  3 #define BASE 26
  4     class Node{
  5         public:
  6             Node():flag(false){
  7                 for(int i = 0; i < BASE; ++i) next[i] = nullptr;
  8             }
  9             bool flag;
 10             Node *next[BASE];
 11     };
 12     Node *root;
 13     void dfs(Node *r, string s, vector<string> &ans) {
 14         if(3 == ans.size()) return;
 15         if(!r) return;
 16         if(r->flag) ans.push_back(s);
 17         for(int i = 0; i < BASE; ++i) {
 18             if(!r->next[i]) continue;
 19             dfs(r->next[i], s + char('a' + i), ans);
 20         }
 21         return;
 22     }
 23 
 24     vector<vector<string>> suggestedProducts(vector<string>& products, string searchWord) {
 25         root = new Node();
 26         for(auto &x : products) {
 27             Node *r = root;
 28             for(int i = 0; x[i]; ++i){
 29                 int index = x[i] - 'a';
 30                 if(!r->next[index]) r->next[index] = new Node();
 31                 r = r->next[index];
 32             }
 33             r->flag = true;
 34         }
 35 
 36         vector<vector<string>> ans;
 37         Node *r = root;
 38         string s = "";
 39         for(auto &c : searchWord) {
 40             s += c;
 41             int index = c - 'a';
 42             vector<string> ret;
 43             if(r) {
 44                 dfs(r->next[index], s, ret);
 45                 r = r->next[index];
 46             }
 47             ans.push_back(ret);
 48         }
 49         return ans;
 50     }
 51 };
 52 
 53 //Updted Trie + 数组记忆化,空间换时间
 54 class Solution {
 55 #define MAX_N 20000
 56 #define BASE 26
 57 struct Trie{
 58     Trie():flag(false) {
 59         for(int i = 0; i < BASE; ++i) next[i] = 0;
 60         p_recomd = new set<string>();
 61     }
 62     bool flag;
 63     int next[BASE];
 64     set<string> *p_recomd;
 65 }trie[MAX_N];
 66 
 67 int root = 1;
 68 int next = 2;
 69 
 70 void insert(string s) {
 71     int p = root;
 72     for(auto x : s) {
 73         int ind = x - 'a';
 74         if(!trie[p].next[ind]) trie[p].next[ind] = next++;
 75         p = trie[p].next[ind];
 76         trie[p].p_recomd->insert(s);
 77         if((trie[p].p_recomd)->size() > 3) (trie[p].p_recomd)->erase(--((trie[p].p_recomd)->end()));
 78     }
 79     trie[p].flag = true;
 80     return;
 81 }
 82 
 83 vector<vector<string>> search(string s) {
 84     int p = root;
 85     vector<vector<string>> ret;
 86     for(auto x : s) {
 87         int ind = x - 'a';
 88         p = trie[p].next[ind];
 89         ret.push_back(vector<string>((trie[p].p_recomd)->begin(), (trie[p].p_recomd)->end()));
 90     }
 91     return ret;
 92 }
 93 
 94 
 95 public:
 96     vector<vector<string>> suggestedProducts(vector<string>& products, string searchWord) {
 97         for(auto x : products) insert(x);
 98         return search(searchWord);
 99     }
100 };
Trie+记忆化

 
======================= **应用场景** =======================

Trie最典型的应用:Trie 的具体实现根据不同应用场景不同而不同,所以没有固定的接口 或 形式;

    单词查找:判断是否存在边,以及是否结束节点是否存在单词;

    字符串排序:1)建立字典树;2)DFS该字典树,输出单词,即可实现字符串排序;O(n)

    还可以用于字符补全,拼写检查

   

Double Array Trie:

  1.字符串预处理后需要空间小,节省内存;

  2. 将所有字符数组序列化到文件中,方便传输到其他电脑/手机; 也就意味着可以将字符串处理过程放在服务器端,然后将处理好的这个很小的文件传输到用户终端,方便用户快速检索;

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐