字典树(Trie、prefix tree)及其应用(求一个数组中的最大异或值)
2017-10-16 13:53
453 查看
trie,又称前缀树或字典树,是一种有序树,用于保存关联数组,其中的键通常是字符串。与二叉查找树不同,键不是直接保存在节点中,而是由节点在树中的位置决定。一个节点的所有子孙都有相同的前缀,也就是这个节点对应的字符串,而根节点对应空字符串。一般情况下,不是所有的节点都有对应的值,只有叶子节点和部分内部节点所对应的键才有相关的值(字符串结尾的那个结点)。
trie中的键通常是字符串,但也可以是其它的结构。trie的算法可以很容易地修改为处理其它结构的有序序列,比如一串数字或者形状的排列。比如,求数组中的任意2个数的最大异或值(接下来例子会介绍)。
trie本质上是一种用空间换时间的数据结构,通过字符串的公共前缀来减少查询时间,trie的插入和查找的时间都为O(k)(设插入和查找字符串长度为k),缺点就是空间复杂度会比较高。
trie根结点不包含任何字符,除根节点外每个结点有一个字符,从根节点到某一个结点的路径结点的值串联起来,就构成了该字符串
trie主要有这么几种操作:
一、插入:
对于一个字符串,从trie的根节点开始寻找字符串的第一个字母,如果找到,就从找到的这个节点开始寻找第2个字母,这样一直寻找,知道把字符串 遍历完,若没有找到,则新建一个节点,节点的值赋值为字符串的这个字母。
二、查找:
从根结点按照字符串字母的顺序遍历,如果遍历完字符串,且该节点标记为结束的符号标记为true, 查找成功,否则,失败。
trie中的键通常是字符串,但也可以是其它的结构。trie的算法可以很容易地修改为处理其它结构的有序序列,比如一串数字或者形状的排列。比如,求数组中的任意2个数的最大异或值(接下来例子会介绍)。
trie本质上是一种用空间换时间的数据结构,通过字符串的公共前缀来减少查询时间,trie的插入和查找的时间都为O(k)(设插入和查找字符串长度为k),缺点就是空间复杂度会比较高。
trie根结点不包含任何字符,除根节点外每个结点有一个字符,从根节点到某一个结点的路径结点的值串联起来,就构成了该字符串
trie主要有这么几种操作:
一、插入:
对于一个字符串,从trie的根节点开始寻找字符串的第一个字母,如果找到,就从找到的这个节点开始寻找第2个字母,这样一直寻找,知道把字符串 遍历完,若没有找到,则新建一个节点,节点的值赋值为字符串的这个字母。
二、查找:
从根结点按照字符串字母的顺序遍历,如果遍历完字符串,且该节点标记为结束的符号标记为true, 查找成功,否则,失败。
#include<iostream> #include<vector> #include<cstring> #include<algorithm> #include<cmath> using namespace std; class TrieNode { public: char content; // the character included bool isend; // if the node is the end of a word int shared; // the number of the node shared ,convenient to implement delete(string key), not necessary in this problem vector<TrieNode*> children; // the children of the node // Initialize your data structure here. TrieNode() :content(' '), isend(false), shared(0) {} TrieNode(char ch) :content(ch), isend(false), shared(0) {} TrieNode* subNode(char ch) { if (!children.empty()) { for (auto child : children) { if (child->content == ch) return child; } } return nullptr; } ~TrieNode() { for (auto child : children) delete child; } }; class Trie { public: Trie() { root = new TrieNode(); } // Inserts a word into the trie. void insert(string s) { if (search(s)) return; TrieNode* curr = root; for (auto ch : s) { TrieNode* child = curr->subNode(ch); if (child != nullptr) { curr = child; } else { TrieNode *newNode = new TrieNode(ch); curr->children.push_back(newNode); curr = newNode; } ++curr->shared; } curr->isend = true; } // Returns true if the key is in the trie. bool search(string key) { TrieNode* curr = root; for (auto ch : key) { curr = curr->subNode(ch); if (curr == nullptr) return false; } return curr->isend == true; } // Returns if there is any word in the trie // that starts with the given prefix. bool startsWith(string prefix) { TrieNode* curr = root; for (auto ch : prefix) { curr = curr->subNode(ch); if (curr == nullptr) return false; } return true; } ~Trie() { delete root; } private: TrieNode* root; }; int main() { int t, n; Trie *obj = new Trie(); obj->insert("aaa"); obj->insert("aba"); obj->insert("abcd"); obj->insert("hidgwrg"); obj->insert("haha"); if (obj->search("abcd")) cout << "find!" << endl; else cout << "not find!" << endl; return 0; }
应用,trie一般用于字符串检索等,可以加快检索速度,还有就是类似,给定一个数组,求该数组中任意2个数的最大异或值。 那我们怎么求呢,假设数组中的每个数都小于2^32, 那我们可以把数组中的数看成32位数组成的0101这样的一个数, 比如5 可以表示成000...101, 那我们求与其取异或最大值的数在trie树种就很容易求了, 因为5的第一位为0,那我们首先在trie数中找以1开头的(因为1与0异或为1),每一位就找与当前位相反的(如果没有相反就找相同的,一直到查找完成。如果在某一个位置trie树没有子树了,那么这个位置的值就是对于该查找数的最大异或值,这样对于每一个数,最多查找32次就OK), 这样用贪心法就很容易完成了。 如果不考虑建trie树的时间,求n个数组中两两异或的最大值需要计算O(n*32)次,而暴力则需要O(n*n)次,可以看出trie树可以大大减少查询时间。
#include<iostream> #include<cstring> #include<algorithm> #include<cmath> using namespace std; const int MAXN = 100005; long long a[MAXN]; struct Trie { int son[MAXN * 13][2], cnt;// void init() { memset(son, 0, sizeof(son)); cnt = 0; } void insert(long long a) { int x = 0, alp;//x代表每个节点的标号, 其下一个节点的编号为son[x][0]或son[x][1] for (int i = 31; i >= 0; i--) { alp = (a >> i) & 1; if (!son[x][alp]) son[x][alp] = ++cnt;//如果这个节点没有孩子节点就创建它 x = son[x][alp];//将指针后移 } } long long find(int a) { int x = 0, alp; long long ret = 0; for (int i = 31; i >= 0; i--) { alp = !((a >> i) & 1); //取反查找 ret <<= 1; //因为是按照位的,所以是*2 if (son[x][alp]) { x = son[x][alp]; ret++;//如果和原来的那一为相反的存在的话,返回值就加上,并且在这个支路走 } else x = son[x][!alp]; //按照相同的顺序找 } return ret; } }trie; int main() { int t, n; cin>>t; while (t--) { trie.init(); cin>>n; for (int i = 1; i <= n; i++) { cin >> a[i]; trie.insert(a[i]); } long long ans = -1; for (int i = 1; i <= n; i++) ans = max(ans, trie.find(a[i])); printf("%lld\n", ans); } return 0; }
相关文章推荐
- LeetCode OJ:Implement Trie (Prefix Tree)(实现一个字典树(前缀树))
- 字典树的应用:求数组中异或最大的两个数
- 【串和序列处理 5】总结---自动机,KMP算法,Extend-KMP,后缀树,后缀数组,trie树,trie图及其应用
- [LeetCode] Implement Trie (Prefix Tree) 实现字典树(前缀树)
- leetcode 208. Implement Trie (Prefix Tree) 字典树的构造 + 必须要掌握的数据结构
- // 应用递归的方法 求一个数组的最大值
- HDU 4825 Xor Sum(字典树 经典应用,求一个数与其他数xor最大)
- 求一个数组中所有元素的最大值及其索引位置
- Trie(prefix tree,前缀树,字典树)
- 在一个有8个整数(18,25,7,36,13,2,89,63)的数组中找出其中最大的数及其下标。
- leetcode 208. Implement Trie (Prefix Tree)字典树
- 208. Implement Trie (Prefix Tree)字典树
- POJ 3764 The xor-longest Path ( 字典树应用—— 求连续段相异或最大最小的线性算法)(好题)
- Leetcode 208 Implement Trie (Prefix Tree) 字典树
- Leetcode 208 Implement Trie (Prefix Tree) 实现字典树 (前缀字典树)
- CSU-1216: 异或最大值-trie-01字典树
- [LeetCode] 208. Implement Trie (Prefix Tree) 实现字典树(前缀树)
- LeetCode *** 208. Implement Trie (Prefix Tree) (给指针数组赋空间)
- 找出一个数组中重复次数最多的字符暨找出Map中的最大Value及其对应的Key
- leetcode.208. Implement Trie (Prefix Tree) 字典树