01Trie树 以及 m进制Trie 用于平衡树问题洛谷P3369 桶排洛谷P1177

用作普通平衡树

今天写 208. 实现 Trie (前缀树) - 力扣(LeetCode)突然想起来01Trie和平衡树有很多相似之处,所以尝试了一下搓一个,写过了洛谷P3369,因为01Trie常数较小,所以时间还是比较快的,差不多和红黑树一致。

这里因为P3369题目的原因,没有必要写value,但其实01Trie可以用来写成一个<int,type> 类型的map(其中type表示任意类型。)这个时候,01Trie类似于一个B+树,只有最低端的叶子节点有value值,其他的节点只有key值,因此,可以把叶子结点和非叶子节点定义成两种不同的结构体。

为什么说和B+树有些像呢?因为我这里把树高度统一处理成了maxlog(比如:对于 6 二进制写法为110 ,我统一扩大成了 00000000110(举个例子)),每次想找到某个值都要经过这么高的查询,直到叶子才会得到value,并且叶子结点的value值也是从左向右的升序的。

从最低位开始呢?

当然我们也可以不统一扩大位数,而是把数字从最后一位开始进行插入操作(比如:6 = 110 我们的处理顺序为 0 -> 1 -> 1 即和上面的写法反过来了),这样它就和没有B+树类似之处了,而这个时候我们可以说实现了路径在一定程度上的缩小。

那么这个时候我们怎么判断一个点是否是终端节点呢(即可能拥有value值的节点)?这样判断就好了:now->size != now->child[0]->size + now->child[1]->size 。 如果不等于,说明有一些数字在这里“停下了”,也就是位数结束了,这个和前缀树是一样的处理。

很有趣吧,虽然这样就没办法写这道题目了,因为当前节点的左孩子不一定比当前节点小,当前节点的右孩子不一定比当前节点大,这里我会给出这种思路实现的map的代码实现。

m进制Trie的一点小思考:

        01Trie只是Trie的一种特殊情况而已,可以用于普通平衡树,那么可以进一步想到,之所以是01是因为计算机二进制下运算比较方便,我们当然可以用十进制,八进制等等写出来 0~9Trie,0~7Trie,这个时候树高会进一步降低(比如使用十进制,树高可能可以控制在8或者9),如果从最高位开始那么这个时候就更像B+树了(节点上存的key值变得多了),又因为叶子结点还是升序的,所以可以使用二分查找或者顺序查找提高效率。

        这些思考就是m进制从最高位开始的Trie的基本思路,后面会给出代码实现,因为思想一致,不再过多赘述。

01Trie普通平衡树P3369代码实现与AC记录。

#include <iostream>
#include <cmath>
using namespace std;

//01前缀树解决平衡树问题

const int maxn = 1e7 + 10, maxlog = ceil(log2(maxn));

//01Tire实现了类似平衡树的性质,左子树的都小于本身,右子树都大于本身。
//因为负数第一位为1,这和平衡树性质不符合,所以要把所有负数加一个极大值,转化成正数。

class Tire01
{
private:
    struct node//child[0]是0,child[1]是1。前缀
    {
        node *child[2];
        int size;
        node(node *p)
        {
            child[0] = child[1] = p;
            size = 0;
        }
    } *root;

    void clear(node*now)
    {
        if(now==root)return;
        clear(now->child[0]);
        clear(now->child[1]);
        delete now;
    }

public:

    void clear()
    {
        clear(root->child[0]);
        clear(root->child[1]);
        root->child[0]=root->child[1]=root;
    }

    ~Tire01()
    {
        clear();
        delete root;
    }

    Tire01()//设置头结点root,让所有空节点都指向root
    {
        root = new node(nullptr);
        root->child[0] = root->child[1] = root;
    }

    void insert(int x)
    {
        node *p = root;

        for (int i = maxlog, t; i >= 0; --i)//从最高位开始,这和数的比较从最高位开始是一致的
        {
            t = (x >> i) & 1;//这一位上的数字是0还是1

            if (p->child[t] == root)
                p->child[t] = new node(root);//如空,则新建

            p = p->child[t];
            ++p->size;
        }
    }

    void erase(int x)//删除则更简单,直接沿路删除即可,因为不需要释放空间,
    {
        node *p = root;

        for (int i = maxlog, t; i >= 0; --i)
        {
            t = (x >> i) & 1;
            p = p->child[t];
            --p->size;
        }
    }

    int get_val(int k)//根据排名获得值
    {
        node *p = root;
        int ans = 0;

        for (int i = maxlog; i >= 0; --i)
        {
            if (p->child[0]->size >= k)//如果左边大,就走左边
                p = p->child[0];
            else
            {
                ans |= (1 << i);//如果左边小,说明这一位为1,
                k -= p->child[0]->size;//减去左边的个数(对应在平衡树就是减去小于的个数)
                p = p->child[1];
            }

            if (p == root)
                break;
        }

        return ans - maxn;//记得减去maxn,处理了负数
    }

    int get_rank(int x)//根据值获得排名,注意,获得的是小于本身的个数!
    {
        int ans = 0;
        node *p = root;

        for (int i = maxlog; i >= 0; --i)
        {
            if ((x >> i) & 1)//为1,加上左子树的个数
            {
                ans += p->child[0]->size;
                p = p->child[1];
            }
            else
            {
                p = p->child[0];
            }

            if (p == root)
                break;
        }

        return ans;
    }

    int get_pre(int x)
    {
        return get_val(get_rank(x));//
    }

    int get_next(int x)
    {
        return get_val(get_rank(x + 1) + 1);//get_rank(x+1)获得了小于等于本身的个数,再加1,就是第一个大于本身的数。
    }
};

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);

    Tire01 it;
    int n;
    cin >> n;

    while (n--)
    {
        int op, x;
        cin >> op >> x;

        x += maxn;

        if (op == 1)
        {
            it.insert(x);
        }
        else if (op == 2)
        {
            it.erase(x);
        }
        else if (op == 3)
        {
            cout << it.get_rank(x) + 1 << '\n';//注意一定要加1,因为获得的是小于本身的个数
        }
        else if (op == 4)
        {
            cout << it.get_val(x - maxn) << '\n';
        }
        else if (op == 5)
        {
            cout << it.get_pre(x) << '\n';
        }
        else if (op == 6)
        {
            cout << it.get_next(x) << '\n';
        }
    }

    cout << endl;
    system("pause");
    return 0;
}

01Trie基于从最低位开始思路的桶排实现与AC记录(看起来效果一般)。

#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;

//01前缀树实现map功能,从低位到高位进行操作

const int maxlog = 32;

class Tire01
{
private:
    struct node//child[0]是0,child[1]是1。前缀
    {
        node *child[2];
        int size,value;
        node(node *p)
        {
            child[0] = child[1] = p;
            size = 0;
        }
    } *root;

    void clear(node*now)
    {
        if(now==root)return;
        clear(now->child[0]);
        clear(now->child[1]);
        delete now;
    }

public:

    void clear()
    {
        clear(root->child[0]);
        clear(root->child[1]);
        root->child[0]=root->child[1]=root;
    }

    ~Tire01()
    {
        clear();
        delete root;
    }

    Tire01()//设置头结点root,让所有空节点都指向root
    {
        root = new node(nullptr);
        root->child[0] = root->child[1] = root;
    }

    void insert(int x,int value)
    {
        node *p = root;
        int t;

        while(x)//从最低位开始,到最高位结束
        {
            t = x & 1;//这一位上的数字是0还是1
            x>>=1;

            if (p->child[t] == root)
                p->child[t] = new node(root);//如空,则新建

            p = p->child[t];
            ++p->size;
        }

        p->value=value;

        //如果之前这个地方已经插入过了,那么应该清除这次的size记录
        if(p->size==p->child[0]->size+p->child[1]->size)
        {
            for (int i = 0; i <= maxlog && x; ++i)
            {
                p = p->child[x>>i&1];
                --p->size;
            }
        }

    }

    void erase(int x)//删除则更简单,直接沿路删除即可,因为不需要释放空间,
    {
        node *p = root;

        for (int i = 0; (x>>i) ; ++i)
        {
            p = p->child[x>>i&1];
            --p->size;

            if(p==root)break;
        }

        //如果不存在这个值,那么删除应该失败,所以把size加回来。
        if(p->size==p->child[0]->size+p->child[1]->size||p==root)
        {
            for (int i = 0; i <= maxlog && x; ++i)
            {
                p = p->child[x>>i&1];
                ++p->size;
            }
        }
    }

    node* search(int x)
    {
        node *p = root;

        while(x)
        {
            p = p->child[x&1];
            x>>=1;

            if(p==root)break;
        }

        //如果查询的非存value的节点,那么返回nullptr。
        if(p->size==p->child[0]->size+p->child[1]->size||p==root)return nullptr;
        return p;
    }
};

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);

    Tire01 it;
    int n;
    cin >> n;

    int a[n+1];
    
    for(int i=1;i<=n;++i)
    {
        cin>>a[i];
        
        auto res=it.search(a[i]);

        //实际上stl中[]符号返回的是引用,如果不存在则新建,我这里没有新建,所以毕竟先查询一次探探路
        if(res==nullptr)it.insert(a[i],1);//如果空,插入1
        else it.insert(a[i],res->value+1);//不为空,则+1
    }

    sort(a+1,a+1+n);

    int tot=unique(a+1,a+1+n)-a-1;

    for(int i=1;i<=tot;++i)
    {
        auto res=it.search(a[i]);

        while(res->value--)
        {
            cout<<a[i]<<' ';
        }
        
        it.erase(a[i]);
    }

    cout << endl;
    system("pause");
    return 0;
}

m进制Trie(以35进制为例)普通平衡树P3369代码实现与AC记录

#include <iostream>
#include <cmath>
using namespace std;


//m进制Trie,和01Trie思想是一致的,
//先获得maxn,而且maxn必须是m的幂数
const int m=35,N=1e7+10, maxlog=ceil(log(N)/log(m)),maxn=pow(m,maxlog);

class m_Tire
{
private:
    struct node
    {
        node *child[m];
        int size;
        node(node *p)
        {
            for(int i=0;i<m;++i)child[i]=p;
            size = 0;
        }
    } *root;

    void clear(node*now)
    {
        if(now==root)return;
        
        for(int i=0;i<m;++i)
            clear(now->child[i]);
        delete now;
    }

public:

    void clear()
    {
        for(int i=0;i<m;++i)
        {
            clear(root->child[i]);
            root->child[i]=root;
        }
    }

    ~m_Tire()
    {
        clear();
        delete root;
    }

    m_Tire()//设置头结点root,让所有空节点都指向root
    {
        root = new node(nullptr);
        for(int i=0;i<m;++i)
            root->child[i]=root;
    }

    void insert(int x)
    {
        node *p = root;

        for (int power=maxn,t; power; power/=m)
        {
            t = (x / power)  % m;//获得这一位的数字,即余数

            if (p->child[t] == root)
                p->child[t] = new node(root);//如空,则新建

            p = p->child[t];
            ++p->size;
        }
    }

    void erase(int x)//删除则更简单,直接沿路删除即可,因为不需要释放空间,
    {
        node *p = root;

        for (int power=maxn, t; power; power/=m)
        {
            t = (x /power) % m;
            p = p->child[t];
            --p->size;
        }
    }

    int get_val(int k)//根据排名获得值
    {

        node *p = root;
        int ans = 0;

        for (int power=maxn,j,sum; power; power/=m)
        {
            for(j=0,sum=0;j<m&&sum<k;++j)//获得第一位sum大于等于地方
                sum+=p->child[j]->size;

            //if(sum>=k)
           // {

            k-=(sum-p->child[j-1]->size);//注意一定要减去小于本身的那些数的个数,这个时候j指向的是第一个大于等于,所以把左边的全部剪掉
            p=p->child[j-1];
            ans+=(j-1)*power;//注意细微的差别,这里也要加上

            //}

            //这部分是不被需要的,因为sum一定大于等于k,这是上面for循环所保证的。
            // else
            // {
            //     k-=sum;
            //     p=p->child[j];
            //     ans+=j*power;
            // }

            if (p == root)
                break;
        }

        return ans - maxn;//记得减去maxn,处理了负数
    }

    int get_rank(int x)//根据值获得排名,注意,获得的是小于本身的个数!
    {
        int ans = 0;
        node *p = root;

        for (int power=maxn,t,sum,i; power; power/=m)
        {
            for(i=0,sum=0,t=(x/power)%m;i<t;++i)
                sum+=p->child[i]->size;
            
            ans+=sum;
            p=p->child[t];

            if (p == root)
                break;
        }

        return ans;
    }

    int get_pre(int x)
    {
        return get_val(get_rank(x));//
    }

    int get_next(int x)
    {
        return get_val(get_rank(x + 1) + 1);//get_rank(x+1)获得了小于等于本身的个数,再加1,就是第一个大于本身的数。
    }
};

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);

    m_Tire it;
    int n;
    cin >> n;

    while (n--)
    {
        int op, x;
        cin >> op >> x;

        x += maxn;

        if (op == 1)
        {
            it.insert(x);
        }
        else if (op == 2)
        {
            it.erase(x);
        }
        else if (op == 3)
        {
            cout << it.get_rank(x) + 1 << '\n';//注意一定要加1,因为获得的是小于本身的个数
        }
        else if (op == 4)
        {
            cout << it.get_val(x - maxn) << '\n';
        }
        else if (op == 5)
        {
            cout << it.get_pre(x) << '\n';
        }
        else if (op == 6)
        {
            cout << it.get_next(x) << '\n';
        }
    }


    cout << endl;
    system("pause");
    return 0;
}

其他平衡树

手搓平衡树:一颗B-树,一颗AVL树,一颗RB树,一颗Splay树,一颗带旋Treap,一颗无旋fhq-Treap-优快云博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DogDu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值