C++ __gnu_pbds(hash,可并堆,平衡树)

这篇博客详细介绍了GNU C++扩展库pb_ds,它提供了丰富的数据结构,如哈希表、平衡树和堆。其中,哈希表的cc_hash_table和gp_hash_table在效率上优于map。平衡树如ordered_set支持红黑树和伸展树,并提供了order_of_key和find_by_order等高效操作。此外,文中还提到了可并堆的不同实现,如pairing_heap_tag和binary_heap_tag等,以及它们的基本操作。这些数据结构和算法在不允许使用C++11时,是提高代码性能的有效工具。

pb_ds 是GNU-C++自带的一个C++的扩展库,其中实现了很多数据结构,比STL里面的功能更强大

#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/tree_policy.hpp>   // 用tree
#include<ext/pb_ds/hash_policy.hpp>   // 用hash
#include<ext/pb_ds/trie_policy.hpp>   // 用trie
#include<ext/pb_ds/priority_queue.hpp>// 用priority_queue
using namespace __gnu_pbds;
---
#include<bits/extc++.h>
using namespace __gnu_pbds;
//bits/extc++.h与bits/stdc++.h类似,bits/extc++.h是所有拓展库,bits/stdc++.h是所有标准库

哈希表

其中cc开头为拉链法,gp开头为探测法,个人实测探测法稍微快一些。

啥?操作?其实就和map差不多,支持[ ]和find。

#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/hash_policy.hpp>
using namespace __gnu_pbds;

cc_hash_table<string,int>mp1;//拉链法
gp_hash_table<string,int>mp2;//查探法(快一些)

说明:

在不允许使用C++11的时候,pb_ds库中的两种hash函数比map的效率大大提高 ,可以代替map作为一个哈希工具,使用方法和map完全一样,一般来说查探法的效率更高

可并堆

需要的头文件:

用法和普通的优先队列一样

#include<ext/pb_ds/priority_queue.hpp>
using namespace __gnu_pbds;
__gnu_pbds::priority_queue<int>q;//因为放置和std重复,故需要带上命名空间
__gnu_pbds::priority_queue<int,greater<int>,pairing_heap_tag> q;//最快
__gnu_pbds::priority_queue<int,greater<int>,binary_heap_tag> q;
__gnu_pbds::priority_queue<int,greater<int>,binomial_heap_tag> q;
__gnu_pbds::priority_queue<int,greater<int>,rc_binomial_heap_tag> q;
__gnu_pbds::priority_queue<int,greater<int>,thin_heap_tag> q;
__gnu_pbds::priority_queue<int,greater<int> > q;

pb_ds库的堆提供了五种tag,分别是binary_heap_tagbinomal_heap_tagpairing_heap_tagthin_heap_tagrc_binomal_heap_tag。 因为重名的原因一定要加上 __gnu_pbds::
在这里插入图片描述

常用操作:

push()  //会返回一个迭代器
top()    //同 STL
size()   //同 STL 
empty()  //同 STL 
clear()  //同 STL 
pop()    //同 STL 
join(priority_queue &other)            //合并两个堆,other会被清空
split(Pred prd,priority_queue &other)  //分离出两个堆
modify(point_iterator it,const key)    //修改一个节点的值

平衡树

#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/hash_policy.hpp>

using namespace __gnu_pbds;
template<typename T>
using ordered_set = tree<T,null_type,less<T>,rb_tree_tag,tree_order_statistics_node_update>;

// rb_tree_tag 和 splay_tree_tag 选择树的类型(红黑树和伸展树)
T // 自定义数据类型
null_type//无映射(老版本g++为null_mapped_type)
less<T>//Node的排序方式从小到大排序
tree_order_statistics_node_update//参数表示如何更新保存节点信息 tree_order_statistics_node_update会额外获得order_of_key()和find_by_order()两个功能。

ordered_set<Node> Tree;  // Node 自定义struct 注意重载less
Tree.insert(Node);       // 插入
Tree.erase(Node);        // 删除
Tree.order_of_key(Node); // 求Node的排名:当前数小的数的个数 +1
Tree.find_by_order(k);   // 返回排名为k+1的iterator 即有k个Node比*it小
Tree.join(b);            // 将b并入Tree,前提是两棵树类型一致并且二没有重复元素
Tree.split(v, b);        // 分裂,key小于等于v的元素属于Tree,其余属于b
Tree.lower_bound(Node);  // 返回第一个大于等于x的元素的迭代器
Tree.upper_bound(Node);  // 返回第一个大于x的元素的迭代器

//以上的所有操作的时间复杂度均为O(logn)
//注意,插入的元素会去重,如set
ordered_set<T>::point_iterator it=Tree.begin();  // 迭代器
//显然迭代器可以++,--运算
P3369 【模板】普通平衡树

因为tree里不能有相同的数,但是实际会插入相同的数,所以把这些数左移20位在加上一个常数操作( n < 2 20 n<2^{20} n<220,如果 a < b a<b a<b,那么一定有 { ( a < < 20 ) + n } < { b < < 20 } \{(a<<20) +n\}<\{b<<20\} {(a<<20)+n}<{b<<20},这样就保证了在不影响相对大小关系的情况下,消除了相同的数。

别的操作看代码就可以理解了,非常巧妙的搞定了相同数的情况。

rb_tree_tag
#include<bits/stdc++.h>
#include<ext/pb_ds/tree_policy.hpp>
#include<ext/pb_ds/assoc_container.hpp>
using namespace std;
using namespace __gnu_pbds;
using ll=long long;

template <class T=int> T rd()
{
    T res=0;T fg=1;
    char ch=getchar();
    while(!isdigit(ch)) {if(ch=='-') fg=-1;ch=getchar();}
    while( isdigit(ch)) res=(res<<1)+(res<<3)+(ch^48),ch=getchar();
    return res*fg;
}
template<typename T>
using ordered_set = tree<T,null_type,less<T>,rb_tree_tag,tree_order_statistics_node_update>;

const int N=100010;
int n,m;
ll k,ans;
ordered_set<ll> T;
int main()
{
    n=rd();
    for(int i=1;i<=n;i++)
    {
        int op=rd();k=rd<ll>();
        if(op==1) T.insert((k<<20)+i);
        if(op==2) T.erase(T.lower_bound(k<<20));
        if(op==3) printf("%d\n",T.order_of_key((k<<20))+1);
        if(op == 4)ans=*T.find_by_order(k-1),printf("%lld\n",ans>>20);
        if(op == 5)ans=*--T.lower_bound(k<<20),printf("%lld\n",ans>>20);
        if(op == 6)ans=*T.upper_bound((k<<20)+n),printf("%lld\n",ans>>20);
    }
    return 0;
}
splay_tree_tag

Code from

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <map>
#include <ext/pb_ds/assoc_container.hpp> 
using namespace std;
using namespace __gnu_pbds;
#define Node pair<int,int>
map <int,int> s;
tree< Node ,null_type,less< Node >,splay_tree_tag,tree_order_statistics_node_update> T;
int n,op,x;
int main()
{
    scanf("%d",&n);
    for(register int i = 1; i <= n; i++)
        switch(scanf("%d%d",&op,&x), op)
        {
            case 1 :T.insert(Node(x,s[x]++));
                    break;
            case 2 :T.erase(Node(x,--s[x]));
                    break;
            case 3 :printf("%d\n",(int)T.order_of_key(Node(x,0))+1);
                    break;
            case 4 :printf("%d\n",T.find_by_order(x-1)->first);
                    break;
            case 5 :printf("%d\n",T.find_by_order(
                    T.order_of_key(Node(x,0))-1
                                                      )->first);
                    break;
            case 6 :printf("%d\n",T.find_by_order(
                    T.order_of_key(Node(x,s[x]-1))+(T.find(Node(x,0)) == T.end() ? 0 : 1)
                                                      )->first);
                    break;
            default:break;
        }
    return 0;
}

底层代码


参考以及进阶

比STL还STL?——平板电视
pb_ds库的一些常用方法
C++ __gnu_pbds(平板电视)超详细教程(C++内置的平衡树,字典树,hash)

### 用法详解 `__gnu_pbds::tree` 是 GNU Policy-Based Data Structures (PBDS) 库中的一个组件,它提供了一个高效的平衡搜索树实现。这个库在算法竞赛中特别有用,因为它支持一些 STL 中的 `set` 和 `map` 不直接支持的操作,比如以 Θ(log n) 的时间复杂度查找第 k 个元素。 #### 定义与初始化 要使用 `tree`,首先需要包含头文件 `<ext/pb_ds/tree_policy.hpp>` 和 `<ext/pb_ds/assoc_container.hpp>`,使用命名空间 `__gnu_pbds`。对于某些编译器,可能还需要额外的编译选项来启用 PBDS 库。 ```cpp #include <ext/pb_ds/tree_policy.hpp> #include <ext/pb_ds/assoc_container.hpp> using namespace __gnu_pbds; ``` 然后可以定义一个 `tree` 实例。例如,创建一个整数类型的有序集合: ```cpp tree<int, null_type, less<int>, rb_tree_tag, tree_order_statistics_node_update> my_tree; ``` 这里的模板参数解释如下: - 第一个参数 `int` 表示键的类型。 - 第二个参数 `null_type` 表示值的类型(当不需要存储值时)。 - 第三个参数 `less<int>` 是比较函数对象,用于保持键的升序排列。 - 第四个参数 `rb_tree_tag` 指定底层数据结构为红黑树。 - 最后一个参数 `tree_order_statistics_node_update` 启用了统计操作,如找到第 k 个元素或确定某个元素的排名。 #### 常见操作 ##### 插入元素 插入一个新的元素到 `tree` 中非常简单,类似于 `std::set` 的用法: ```cpp my_tree.insert(20); ``` ##### 删除元素 从 `tree` 中删除一个元素同样直观: ```cpp my_tree.erase(20); ``` ##### 查找第 k 大元素 由于启用了 `tree_order_statistics_node_update`,我们可以快速地找到第 k 小的元素(索引从 0 开始): ```cpp int k = 0; // 寻找最小的元素 auto it = my_tree.find_by_order(k); if (it != my_tree.end()) { cout << "The " << k + 1 << "th smallest element is " << *it << endl; } ``` ##### 确定元素的排名 如果想要知道特定元素在 `tree` 中的排名(即有多少个元素小于该元素),可以这样做: ```cpp int value = 20; int rank = my_tree.order_of_key(value); cout << "There are " << rank << " elements smaller than " << value << endl; ``` #### 示例代码 以下是一个完整的示例,演示了如何使用 `__gnu_pbds::tree` 进行基本操作: ```cpp #include <iostream> #include <ext/pb_ds/tree_policy.hpp> #include <ext/pb_ds/assoc_container.hpp> using namespace std; using namespace __gnu_pbds; int main() { // 定义一个支持统计操作的tree tree<int, null_type, less<int>, rb_tree_tag, tree_order_statistics_node_update> my_tree; // 插入几个数值 my_tree.insert(10); my_tree.insert(20); my_tree.insert(30); my_tree.insert(40); // 找到第2小的元素 auto it = my_tree.find_by_order(1); // 索引从0开始 if (it != my_tree.end()) { cout << "The 2nd smallest element is " << *it << endl; } // 计算小于25的元素数量 int count = my_tree.order_of_key(25); cout << "There are " << count << " elements smaller than 25." << endl; return 0; } ``` 这段代码展示了如何定义 `tree`、插入元素、查找第 k 小的元素以及计算小于给定值的元素数量。 ### 注意事项 - 在使用 `__gnu_pbds::tree` 之前,请确保你的开发环境支持 GNU C++ 扩展库。 - 因为 `__gnu_pbds::tree` 是非标准的扩展,所以在一些在线评测系统上可能不可用,或者需要特殊的编译标志。 - 使用 `tree_order_statistics_node_update` 可以获得更复杂的统计功能,但这也意味着学习曲线会稍微陡峭一些。 ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值