关于卡 STL 哈希表
unordered_map 的实现和用法
首先介绍 unordered_map 的一些函数。
hash_function():使用的哈希函数。
bucket(x):
bucket_size(i):编号为
bucket_count():桶的个数。
max_load_factor():每个桶平均至多能容纳元素个数,默认为
max_load_factor(f):将 max_load_factor() 设为
load_factor():每个桶平均元素个数。
size():元素个数。
rehash(a):重构哈希表,使 bucket_count() 变为某个不小于
reserve(n):相当于 rehash(ceil(n/max_load_factor()))。
注意调用 bucket(x) 或 count(x) 或 find(x) 不会视为插入 mp[x] 就会视为插入了 size() 加
所以尽量用 count(x)(返回 find(x)(返回 first 为 mp.end()) 等函数代替 mp[x]。
示例:
#include<bits/stdc++.h>
using namespace std;
unordered_map<int,int>mp;
int main(){ios::sync_with_stdio(0),cin.tie(0);
auto f=mp.hash_function();
cout<<f(123456)<<'\n';//123456
cout<<mp.max_load_factor()<<'\n';//1
for(int i=1;i<=100;++i)mp[i]=1;
cout<<mp.bucket_count()<<'\n';//199
cout<<mp.bucket(199)<<' '<<mp.bucket(200)<<'\n';//0 1
cout<<mp.bucket_size(1)<<'\n';//1
mp[200];
cout<<mp.size()<<'\n';//101
cout<<mp.bucket_size(1)<<'\n';//2
mp.erase(200);
cout<<mp.bucket_size(1)<<'\n';//1
for(int i=1;i<=10000;++i)mp[i];
cout<<mp.bucket_count()<<'\n';//15173
for(int i=1;i<=9900;++i)mp.erase(i);
cout<<mp.bucket_count()<<'\n';//15173
mp.rehash(1234),cout<<mp.bucket_count()<<'\n';//1289
cout<<mp.load_factor()<<'\n';//0.0775795
mp.max_load_factor(0.01);
mp.rehash(1234),cout<<mp.bucket_count()<<'\n';//10273
mp.reserve(1234),cout<<mp.bucket_count()<<'\n';//126271
return 0;
}
unordered_map 的实现方法是,插入元素
遍历桶
若某一时刻 rehash(bucket_count()*2))。若删除若干元素,不会进行重构,只有手动 rehash(0) 才能让
hack unordered_map
容易发现,hash_function() 的实现很蠢,对于一个整数 hash_function()(x) 得到的结果就是
要 hack unordered_map,只需要多次向同一个桶中插入数并访问即可。也就是多次插入模
如何找到这些
运行以下程序:
#include<bits/stdc++.h>
using namespace std;
unordered_map<int,int>mp;
set<int>st;
int main(){ios::sync_with_stdio(0),cin.tie(0);
for(int i=1;i<=1000000;++i){
mp[i];
st.insert(mp.bucket_count());
}
for(auto o:st)cout<<o<<' ';
return 0;
}
得到的结果即为
比如取
#include<bits/stdc++.h>
using namespace std;
unordered_map<int,int>mp;
int main(){ios::sync_with_stdio(0),cin.tie(0);
for(int i=0;i<10000;++i)mp[i*15173];
for(int i=0;i<100000;i++)mp[(i%10000)*15173];
cout<<mp.size()<<' '<<mp.bucket_count();
return 0;
}
只进行了
hack pbds hash_table
pbds 的哈希函数实现有所不同,之前的 hack 方法失效了。
不过 pbds 对
所以只需要插入很多模
#include<bits/extc++.h>
using namespace std;
__gnu_pbds::gp_hash_table<long long,long long>mp;
int main(){ios::sync_with_stdio(0),cin.tie(0);
for(int i=0;i<100000;++i)mp[4294967296ll*i];
cout<<mp.size();
return 0;
}
本地大约需要
如何防止 hack
1.手动 rehash
假设需要插入 rehash(n)。
这样可以大幅减小常数(不需要重构),但是最坏情况下复杂度仍然是
所有
#include<bits/stdc++.h>
using namespace std;
unordered_map<int,int>mp;
set<int>s;
int main(){ios::sync_with_stdio(0),cin.tie(0);
for(int i=1;i<=1000000;++i)mp.rehash(i),s.insert(mp.bucket_count());
for(auto o:s)cout<<o<<' ';
return 0;
}
容易发现这样的数大约只有
2.手写 hash 函数
容易发现默认的 hash_function() 实现很蠢,重新写一个就行了。
#include<bits/stdc++.h>
using namespace std;
mt19937 rd(chrono::steady_clock::now().time_since_epoch().count());
int RD=rd();
struct H{size_t operator()(int x)const{return x^RD;}};
unordered_map<int,int,H>mp;
int main(){
for(int i=0;i<10000;++i)mp[i*15173];
for(int i=0;i<100000;i++)mp[(i%10000)*15173];
cout<<mp.size()<<' '<<mp.bucket_count();
return 0;
}
修改后重新跑之前例子中的程序,只需要不到
注意在
#include<bits/stdc++.h>
using namespace std;
mt19937 rd(chrono::steady_clock::now().time_since_epoch().count());
int RD=rd();
struct H{size_t operator()(long long x)const{return(x>>32)^x^RD;}};
unordered_map<long long,long long,H>mp;
int main(){
for(int i=0;i<100000;++i)mp[i*4294967296ll];
return 0;
}
pbds 的 hash_table 也可以用同样的方式。
3.手写哈希表
unordered_map 本身还是太慢,
考虑手写哈希表,
例题:CF1333C Eugene and an array(数据很强)