字典树(Trie、prefix tree)及其应用(求一个数组中的最大异或值)

trie,又称前缀树或字典树,是一种有序树,用于保存关联数组,其中的键通常是字符串。与二叉查找树不同,键不是直接保存在节点中,而是由节点在树中的位置决定。一个节点的所有子孙都有相同的前缀,也就是这个节点对应的字符串,而根节点对应空字符串。一般情况下,不是所有的节点都有对应的值,只有叶子节点和部分内部节点所对应的键才有相关的值(字符串结尾的那个结点)

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;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值