学习笔记——Tire树

简介

字典树,即Trie树,是一种常用的数据结构。比如一个单词“apple”,可以分为五个字母,一般以一个虚节点为起始节点,记为 r o o t root ,那么apple这个单词就可以表示为 r o o t root ->a->p->p->l->e,同理, b a d bad 就是 r o o t root ->b->a->d。字典树的优点在于其不仅增加单词的复杂度是线性的,其查询单词或前缀的复杂度也是线性的。就好像翻字典一样,先找第一个字母,再第二个,比盲目的大海捞针要快上许多。

一种错误的实现

之前我有想到过开一个二维的数组 t r e e tree [字母][深度],来记录,比如bad就是tree[‘b’][0]=tree[‘a’][1]=tree[‘d’][2]=true。但是这样做会存在一个问题,比如我记录单词aa,ab,以及ba,那就会使得tree[‘a’][0],tree['b][0],tree[‘a’][1],tree[‘b’][1]=true,然后查询bb时就会出现“bb存在”的错误,然而“bb”是不存在的,所以这种表达方式会产生一些无中生有的交叉错误。

正常的实现方式(数组实现)

以一个例子来说明。
给出一个数组a,求max( (a[i] + a[j]) xor a[k] ) (i, j, k互不相同)
Input
第一行给出T代表数据组数,对于每一组数据第一行给出n代表数组中数的数量,第二行给出n个数分别代表a中第i的元素
1≤T≤1000,3≤n≤1000,0≤ai≤1e9且n>100的数据最多只有10组Output对于每一组数据,输出答案,每组数据占一行。
Sample Input
2
3
1 2 3
3
100 200 300
Sample Output
6
400

这里以01字典树为例。(即用字典树表示二进制,其他情况类似)

struct Tree {
 struct node {//表示一个节点
  array<int, 2> next;//该节点的两个儿子(0或1)
  int size = 0;//该节点被覆盖的次数,就是有多少个二进制数字经过该节点(删除数据时用到)
 };
 vector<node> tree;//字典树
 vector<int>Data;//记录数组a
 int N;
 int now = 0;
 inline void Add(int num) {/加入一个数字
  bitset<33> bit = num;//先转化为二进制形式
  int root = 0;//默认以0为我们的虚根
  tree[root].size++;//0被覆盖
  for (int i = 32; i >= 0; i--) {
   int id = bit[i];
   if (!tree[root].next[id])//如果覆盖过程种发现之前还未出现该节点
    tree[root].next[id] = ++now;//新分配一个节点
   root = tree[root].next[id]; //往下覆盖
   if (root >= (int)tree.size() - 1)
    tree.resize(root + 1);
   tree[root].size++;//被覆盖到的节点覆盖数加1
  }
 }
 inline void Insert() {
  while (N--) {
   int num;
   cin >> num;
   Add(num);
   Data.emplace_back(num);
  }
 }
 inline void Delete(const int&num)//删除 {
  bitset<33> bit = num;
  int root = 0;
  tree[root].size--;//虚节点覆盖数减一
  for (int i = 32; i >= 0; i--) {//类似增加
   if (root >= (int)tree.size() - 1)
    tree.resize(root + 1);
   int id = bit[i];
   root = tree[root].next[id];
   tree[root].size--;
  }
 }
 inline int getAns(const int& i,const int&j) {
  bitset<33> bit = i + j;//求i+j的最大xor值
  int Ans = 0;
  int root = 0;
  for (int index = 32; index >= 0; index--) {
   if (root >= (int)tree.size() - 1)
    tree.resize(root + 1);
   int id = bit[index];
   if (tree[root].next[!id] &&/*其子节点被覆盖数为0时,说明该节点为叶节点,走不了咯*/ tree[tree[root].next[!id]].size)//优先选择和当前为相反的,由于是高位到低位(高位权重大)。
    Ans += (1 << index), root = tree[root].next[!id];//1 xor 0 = 1
   else/* if (tree[root].next[id] && tree[tree[root].next[id]].size)*/
   //这个条件可以去掉,因为建树时无论数字多大,都是建成33位,有前导0补足,所以每个数字二进制长度一样,所以一定有路。
    root = tree[root].next[id];
  }
  return Ans;
 }
 void inline Input() {
  cin >> N;
  tree.resize(1);
  Insert();
 }
 inline long long Query() {
  int Ans = 0;//暴力枚举。。。
  //从1到N,删两个点求最大,再加回去。。。
  for (int i = 0; i < Data.size(); i++) {
   for (int j = i + 1; j < Data.size(); j++) {
    Delete(Data[i]); Delete(Data[j]);
    Ans = max(Ans, getAns(Data[i], Data[j]));
    Add(Data[i]); Add(Data[j]);
   }
  }
  return Ans;
 }
 inline void Clear() {
  tree.clear();
  Data.clear();
 }
};
int main() {
 ios::sync_with_stdio(false);
 int T;
 cin >> T;
 while (T--) {
  Ans.Input();
  cout << Ans.Query() << endl;
  Ans.Clear();
 }
 system("pause");
 return 0;
}
发布了20 篇原创文章 · 获赞 22 · 访问量 2496

猜你喜欢

转载自blog.csdn.net/qq_42971794/article/details/89647412