前缀树
前缀树
在计算机科学中,trie,又称前缀树或字典树,是一种有序树,用于保存关联数组,其中的键通常是字符串。与二叉查找树不同,键不是直接保存在节点中,而是由节点在树中的位置决定。一个节点的所有子孙都有相同的前缀,也就是这个节点对应的字符串,而根节点对应空字符串。一般情况下,不是所有的节点都有对应的值,只有叶子节点和部分内部节点所对应的键才有相关的值。
- 根据前缀信息选择树上的分支,可以节省大量时间。但比较浪费空间。
// 初始化前缀树对象
void Trie();
// 将字符串 word 插入前缀树中
void insert(string word);
// 返回前缀树中字符串 word 的实例个数
int search(string word);
// 返回前缀树中以 prefix 为前缀的字符串个数
int prefixNumber(string prefix);
// 从前缀树中移除字符串 word
void remove(string word);
动态结构实现
- 拉跨,不推荐
#include <vector>
#include <cstdlib>
#include <ctime>
#include <string>
#include <unordered_map>
using namespace std;
class TrieNode {
public:
int pass;
int end;
// 也可以用 unordered_map 实现
vector<TrieNode *> nexts;
TrieNode() {
pass = 0;
end = 0;
nexts.resize(26);
}
};
class Trie {
private:
TrieNode *root;
public:
Trie() {
root = new TrieNode;
}
// 将字符串 word 插入前缀树中
void insert(string word) {
TrieNode *cur = root;
cur->pass++;
for (int i = 0, path; i < word.length(); ++i) {
path = word[i] - 'a';
if (cur->nexts[path] == nullptr) cur->nexts[path] = new TrieNode();
cur = cur->nexts[path];
cur->pass++;
}
cur->end++;
}
// 返回前缀树中字符串 word 的实例个数
int search(string word) {
TrieNode *cur = root;
for (int i = 0, path; i < word.length(); ++i) {
path = word[i] - 'a';
if (cur->nexts[path] == nullptr) return 0;
cur = cur->nexts[path];
}
return cur->end;
}
// 返回前缀树中以 prefix 为前缀的字符串个数
int prefixNumber(string prefix) {
TrieNode *cur = root;
for (int i = 0, path; i < prefix.length(); ++i) {
path = prefix[i] - 'a';
if (cur->nexts[path] == nullptr) return 0;
cur = cur->nexts[path];
}
return cur->pass;
}
// 从前缀树中移除字符串 word
void remove(string word) {
if (search(word) <= 0) return;
TrieNode *cur = root;
cur->pass--;
for (int i = 0, path; i < word.length(); ++i) {
path = word[i] - 'a';
cur->nexts[path]--;
if (cur->nexts[path]->pass == 0) {
// 此处省略释放内存空间
cur->nexts[path] = nullptr;
return;
}
cur = cur->nexts[path];
}
cur->end--;
}
};
静态结构实现
NC124 字典树的实现
#include <vector>
#include <string>
#include <iostream>
using namespace std;
class Trie {
public:
vector<vector<int>> tree;
vector<int> pass;
vector<int> end;
int cnt;
const int maxN = 150001;
void build() {
cnt = 1;
tree.resize(maxN, vector<int>(26));
pass.resize(maxN, 0);
end.resize(maxN, 0);
}
void insert(string word) {
int cur = 1;
pass[cur]++;
for (int i = 0, path; i < word.length(); ++i) {
path = word[i] - 'a';
if (tree[cur][path] == 0)
tree[cur][path] = ++cnt;
cur = tree[cur][path];
pass[cur]++;
}
end[cur]++;
}
int search(string word) {
int cur = 1;
for (int i = 0, path; i < word.length(); ++i) {
path = word[i] - 'a';
if (tree[cur][path] == 0) return 0;
cur = tree[cur][path];
}
return end[cur];
}
int prefixNumber(string word) {
int cur = 1;
for (int i = 0, path; i < word.length(); ++i) {
path = word[i] - 'a';
if (tree[cur][path] == 0) return 0;
cur = tree[cur][path];
}
return pass[cur];
}
void remove(string word) {
if (search(word) <= 0) return;
int cur = 1;
for (int i = 0, path; i < word.length(); ++i) {
path = word[i] - 'a';
if (--pass[tree[cur][path]] == 0) {
tree[cur][path] = 0;
return;
}
cur = tree[cur][path];
}
end[cur]--;
}
};
int main() {
int m;
cin >> m;
Trie trie;
trie.build();
for (int i = 0; i < m; ++i) {
int opt;
string word;
cin >> opt >> word;
switch (opt) {
case 1:
trie.insert(word);
break;
case 2:
trie.remove(word);
break;
case 3:
cout << (trie.search(word) > 0 ? "YES" : "NO") << endl;
break;
case 4:
cout << trie.prefixNumber(word) << endl;
break;
}
}
}
接头密匙
#include <vector>
#include <string>
#include <iostream>
using namespace std;
class Trie {
public:
vector<vector<int>> tree;
vector<int> pass;
vector<int> end;
int cnt;
const int maxN = 2000001;
void build() {
cnt = 1;
tree.resize(maxN, vector<int>(12));
pass.resize(maxN, 0);
end.resize(maxN, 0);
}
// '0' ~ '9' 10个 0~9
// '#' 10
// '-' 11
int getPath(char ch) {
if (ch == '#') {
return 10;
} else if (ch == '-') {
return 11;
} else {
return ch - '0';
}
}
void insert(string word) {
int cur = 1;
pass[cur]++;
for (int i = 0, path; i < word.length(); ++i) {
path = getPath(word[i]);
if (tree[cur][path] == 0)
tree[cur][path] = ++cnt;
cur = tree[cur][path];
pass[cur]++;
}
end[cur]++;
}
int prefixNumber(string word) {
int cur = 1;
for (int i = 0, path; i < word.length(); ++i) {
path = getPath(word[i]);
if (tree[cur][path] == 0) return 0;
cur = tree[cur][path];
}
return pass[cur];
}
};
class Solution {
public:
vector<int> countConsistentKeys(vector<vector<int>> &b, vector<vector<int>> &a) {
Trie trie;
trie.build();
// 把生成的字符串加到前缀树
for (const auto &item: a) {
string str = "";
// 用 # 隔断数字,例如 [3,6,50,10] -> "3#44#-40#"
for (int i = 1; i < item.size(); ++i)
str.append(to_string(item[i] - item[i - 1]) + "#");
trie.insert(str);
}
vector<int> res;
for (const auto &item: b) {
string str = "";
for (int i = 1; i < item.size(); ++i)
str.append(to_string(item[i] - item[i - 1]) + "#");
res.emplace_back(trie.prefixNumber(str));
}
return res;
}
};
421. 数组中两个数的最大异或值
#include <vector>
#include <string>
#include <iostream>
using namespace std;
vector<vector<int>> tree;
int cnt;
// 数字只需要从哪一位开始考虑
int high;
// 计算前导 0 的个数
int countLeadingZeros(int i) {
if (i <= 0) return i == 0 ? 32 : 0;
// 最多 31 个前导 0
int n = 31;
// 大于等于 2^16
if (i >= 1 << 16) {
// 低 16 位不用再考虑了,因为更高位存在 1
// 此时最多 15 个前导 0
n -= 16;
// 逻辑右移 16 位,折半
i = (unsigned) i >> 16;
}
if (i >= 1 << 8) {
n -= 8;
i = (unsigned) i >> 8;
}
if (i >= 1 << 4) {
n -= 4;
i = (unsigned) i >> 4;
}
if (i >= 1 << 2) {
n -= 2;
i = (unsigned) i >> 2;
}
return n - ((unsigned) i >> 1);
}
class Solution {
public:
void insert(int num) {
int cur = 1;
// 从 high 开始往低位考虑
for (int i = high, state; i >= 0; i--) {
// 判断 high 位是 0 还是 1
state = (num >> i) & 1;
if (tree[cur][state] == 0)
tree[cur][state] = ++cnt;
cur = tree[cur][state];
}
}
void build(vector<int> &nums) {
tree.resize(3000001, vector<int>(2, 0));
cnt = 1;
int m = INT_MIN;
for (int num: nums)
m = max(num, m);
high = 31 - countLeadingZeros(m);
// 构建前缀树
for (int num: nums)
insert(num);
}
int maxXor(int num) {
int res = 0;
int cur = 1;
for (int i = high, state, want; i >= 0; i--) {
state = (num >> i) & 1;
// want: num 第 i 位希望遇到的状态
want = state ^ 1;
if (tree[cur][want] == 0) {
// 得不到想要的,就恢复
want ^= 1;
}
// want 此时为实际往下走的路
res |= (state ^ want) << i;
cur = tree[cur][want];
}
return res;
}
void clear() {
for (int i = 1; i <= cnt; i++)
tree[i][0] = tree[i][1] = 0;
}
int findMaximumXOR(vector<int> &nums) {
build(nums);
int res = 0;
for (int num: nums)
res = max(res, maxXor(num));
clear();
return res;
}
};
#include <vector>
#include <string>
#include <iostream>
#include <unordered_set>
#include <algorithm>
using namespace std;
// todo: 计算前导 0 的个数
int countLeadingZeros(int i) {
if (i <= 0) return i == 0 ? 32 : 0;
// 最多 31 个前导 0
int n = 31;
// 大于等于 2^16
if (i >= 1 << 16) {
// 低 16 位不用再考虑了,因为更高位存在 1
// 此时最多 15 个前导 0
n -= 16;
// 逻辑右移 16 位,折半
i = (unsigned) i >> 16;
}
if (i >= 1 << 8) {
n -= 8;
i = (unsigned) i >> 8;
}
if (i >= 1 << 4) {
n -= 4;
i = (unsigned) i >> 4;
}
if (i >= 1 << 2) {
n -= 2;
i = (unsigned) i >> 2;
}
return n - ((unsigned) i >> 1);
}
class Solution {
public:
int findMaximumXOR(vector<int> &nums) {
int m = INT_MIN;
for (int num: nums) m = max(num, m);
int res = 0;
unordered_set<int> set;
for (int i = 31 - countLeadingZeros(m); i >= 0; i--) {
// res : 31....i+1 已经达成的目标
int better = res | (1 << i);
set.clear();
for (int num: nums) {
// num : 31.....i 这些状态保留,剩下全成0
num = (num >> i) << i;
set.emplace(num);
// num ^ 某状态是否能达成 better 目标,就在 set 中找某状态 : better ^ num
if (set.find(better ^ num) != set.end()) {
res = better;
break;
}
}
}
return res;
}
};
212. 单词搜索 II
#include <vector>
#include <string>
#include <unordered_set>
#include <cstring>
using namespace std;
class Solution {
public:
// todo
static vector<string> findWords(vector<vector<char>> &board, vector<string> &words) {
build(words);
vector<string> ans;
for (int i = 0; i < board.size(); i++) {
for (int j = 0; j < board[0].size(); j++) {
dfs(board, i, j, 1, ans);
}
}
clear();
return ans;
}
private:
static const int MAXN = 10001;
static int tree[MAXN][26];
static int pass[MAXN];
static string end[MAXN];
static int cnt;
static void build(vector<string> &words) {
cnt = 1;
for (const string &word: words) {
int cur = 1;
pass[cur]++;
for (char c: word) {
int path = c - 'a';
if (tree[cur][path] == 0) {
tree[cur][path] = ++cnt;
}
cur = tree[cur][path];
pass[cur]++;
}
end[cur] = word;
}
}
static void clear() {
for (int i = 1; i <= cnt; i++) {
memset(tree[i], 0, sizeof(tree[i]));
pass[i] = 0;
end[i].clear();
}
}
// board : 二维网格
// i,j : 此时来到的格子位置,i行、j列
// t : 前缀树的编号
// List<String> ans : 收集到了哪些字符串,都放入ans
// 返回值 : 收集到了几个字符串
static int dfs(vector<vector<char>> &board, int i, int j, int t, vector<string> &ans) {
if (i < 0 || i == board.size() || j < 0 || j == board[0].size() || board[i][j] == 0) {
return 0;
}
// 不越界 且 不是回头路
// 用tmp记录当前字符
char tmp = board[i][j];
// 路的编号
// a -> 0
// b -> 1
// ...
// z -> 25
int road = tmp - 'a';
t = tree[t][road];
if (pass[t] == 0) {
return 0;
}
// i,j位置有必要来
// fix :从当前i,j位置出发,一共收集到了几个字符串
int fix = 0;
if (!end[t].empty()) {
fix++;
ans.push_back(end[t]);
end[t].clear();
}
// 把i,j位置的字符,改成0,后续的过程,是不可以再来到i,j位置的!
board[i][j] = 0;
fix += dfs(board, i - 1, j, t, ans);
fix += dfs(board, i + 1, j, t, ans);
fix += dfs(board, i, j - 1, t, ans);
fix += dfs(board, i, j + 1, t, ans);
pass[t] -= fix;
board[i][j] = tmp;
return fix;
}
};
int Solution::tree[MAXN][26];
int Solution::pass[MAXN];
string Solution::end[MAXN];
int Solution::cnt;