蓝桥杯备考:哈希表和unorderd_set
哈希表的概念,哈希表是什么
哈希表,又叫散列表,是根据关键字进行访问的数据结构
哈希表建立了一种关键字和存储地址之间的映射关系,使每个关键字与结构中的唯一存储
位置相对应。在理想情况下,散列表中进行查找的时间复杂度是O(1),与元素数量无关
接下来我们用一个案例来说明:统计一下字符串“abcabcc"中,每一个字符出现的次数,字符串只包含小写字母
代码
#include <iostream> using namespace std; int a[26]; int main() { string s; cin >> s; for(auto e : s) { a[e-'a']++; } return 0; }
这时候,我们的hash(key)也就是key-'a'
那么哈希冲突是啥呢?
哈希函数可能把两个或者两个以上的关键字映射到同一个存储位置上,这时候就是我们的哈希冲突,也叫散列冲突,起冲突的不同关键字称之为同义词
第一种方法就是 创建一个1e9+10大小的数组,用hash(key)=key来存储哈希值
但是这种方法是行不通的,我们创建这么大的数组空间复杂度太高了
所以我们要选第二种方法,我们可以对每个数模上7来代表存储位置,这样的话我们只需要创建一个大小为7的数组就足够了 我们的哈希函数也就是hash(key) = key%7
1%7 = 1, 3%7 = 3 17%7 = 3 100000000%7 = 6 49%7 = 0 49%7 = 0
可以看到有两个关键字的存储位置都是3,这两个关键字就是同义字
哈希冲突是不可避免的,我们要做的不是消除哈希冲突,我们要做的是处理哈希冲突和创造好的哈希函数,
常见的哈希函数,以及怎么处理哈希冲突
哈希函数1:直接定址法
直接取关键字的某个线性函数值作为散列地址
比如hash(key) = key,hash(key) = a*key+b
我们之前的案例
统计一下字符串“abcabcc"中,每一个字符出现的次数,字符串只包含小写字母
就是一种直接定址法
hash(key)=key-'a' 也就是1*key-'a' 是线性的
我们的直接定址法适合元素分布比较连续而不是很跳跃的,不然会很浪费我们的空间
哈希函数2:除留余数法
我们哈希冲突那里的案例,用的就是除留余数法,除留余数,顾名思义就是用key%M作为存储地址,我们的M尽量取成一个素数(减少哈希冲突),最好接近哈希表的大小
处理哈希冲突法1:线性探测法
这种方法就是,我们从发生哈希冲突的位置上依次向后探测,找到一个空位置把值放进去
如果走到表尾了,到下一个就回绕到表头
当我们用除留余数法适,我们的除数一般是选择原来数组长度的两倍附近的一个素数,因为如果我们只找一倍数组附近的素数的话,就显得很紧凑,一不小心的话我们的时间复杂度就会变成O(N),我们弄的大一点的话,我们查一个数的时候需要跳跃的格子相应的就会少
本题数组长度是8,2*8=16,16附近找一个素数,我们就找11吧为了更好看
hash(key)=key%11
INF指的就是我们不可能存到的值,可以当成无穷大
那么我们用线性探测法来进行一下我们的存储
19%11 = 8,我们放在8的位置 30%11 = 8 这时候就出现了哈希冲突,30就要从哈希冲突的位置向后探测,找空位置放进去
下一个元素是5,5%11 还是5,我们放在5的位置
36%11是3,我们放在3这个位置
13%11是2,我们放在2这个位置
20%11 是9,这时候又会发生哈希冲突,我们还是向后探测
21%11是10,再次发生哈希冲突,由于走到表尾我们会回绕会表头,所以
最后一个元素是12,12应该放在12%11 也就是1的位置
处理哈希冲突2 链地址法
我们还是取模数是11,这时候我们的数组每个位置存的就不是元素的值了而是一个一个的指针
我们把发生哈希冲突的值串在一起构成小的链表,这样做的弊端是如果我们发生哈希冲突的元素非常多,那么串在一起查找的时间复杂度就是O(N)了
19%11是8,我们挂在8下面 30%11是8,我们也挂在8下面,
接下来,我们继续算,5%11=5 挂在下标5下面,36%11就是3,挂在下标3下面,13%11就是2,挂在2下面
20%11就是9挂在9下面,21%11就是10,挂在10下面,12%11就是1,挂在1下面,24%11就是2,挂在2下面,96%11就是8放在8下面
模拟实现哈希表
方法1:线性探测法
我们先给出一个测试用例
12
1 1
1 2
1 3
1 4
1 5
2 2
1 6
2 5
1 7
2 8
2 4
2 10
#include <iostream>
#include <cstring>
using namespace std;
const int N = 23,INF = 0x3f3f3f3f;
int h[N];
void init()
{
memset(h, 0x3f, sizeof(h));
}
int f(int x)
{
int id = (x % N + N) % N;
while (h[id] != INF && h[id] != x)
{
id++;
if (id == N) id = 0;
}
return id;
}
void insert(int x)
{
int id = f(x);
h[id] = x;
}
bool find(int x)
{
int id = f(x);
return h[id] == x;
}
int main()
{
init();
int n; cin >> n;
while (n--)
{
int op; cin >> op;
int x; cin >> x;
if (op == 1)
{
insert(x);
}
else if (op == 2)
{
if (find(x))cout << "yes" <<endl;
else cout << "no" << endl;
}
}
return 0;
}
方法2,链地址法
链地址法和我们的链式前向星是差不多的,我们就简单用代码来实现一下了。
#include <iostream>
using namespace std;
const int N = 23;
int h[N];//哈希表
int ne[N], id,e[N];
int f(int x)
{
return (x % N + N) % N;
}
void insert(int x)
{
int idx = f(x);
id++;
e[id] = x;
ne[id] = h[idx];
h[idx] = id;
}
bool find(int x)
{
int idx = f(x);
for (int i = h[idx]; i; i = ne[i])
{
if (e[i] == x)
return true;
}
return false;
}
int main()
{
int n; cin >> n;
while (n--)
{
int op; cin >> op; int x; cin >> x;
if (op == 1)
{
insert(x);
}
else
{
if (find(x)) cout << "yes" << endl;
else cout << "no" << endl;
}
}
return 0;
}
unordered_set和unordered_map
这个和我们set的用法是一样一样的,只不过我们set是有序的,unordered_set是无序的
只不过我们没有了lower_bound 和 upper_bound,然后哈希表查找和插入的时间复杂度都是O(1)而红黑树是O(logN),我们的有序set是红黑树实现的,我们的无序set是哈希表实现的
无序set测试
#include <iostream>
#include <unordered_set>
using namespace std;
int a[] = { 10,60,20,70,80,30,90,40,100,50 };
int main()
{
unordered_set <int> mp;
for (auto e : a)
{
mp.insert(e);
}
for (auto e : mp)
{
cout << e << " ";
}
cout << endl;
if (mp.count(10)) cout << "10:yes" << endl;
else cout << "10:no" << endl;
if (mp.count(20)) cout << "20:yes" << endl;
else cout << "20:no" << endl;
if (mp.count(120)) cout << "120:yes" << endl;
else cout << "120:no" << endl;
cout << "删除后" << endl;
mp.erase(10);
if (mp.count(10)) cout << "10:yes" << endl;
else cout << "10:no" << endl;
return 0;
}
和set是一样的,不过多陈述,然后我们的无序map是经常用来实现图的数据结构的,因为它的插入和查找效率很高
#include <iostream>
#include <unordered_map>
using namespace std;
int main()
{
unordered_map <int, vector<int>> edges;
int n; cin >> n;
int a, b;
while (n--)
{
cin >> a >> b;
edges[a].push_back(b);
edges[b].push_back(a);
}
return 0;
}
哈希表算法题
1.哈希表模板题
#include <iostream>
#include <unordered_map>
using namespace std;
int main()
{
unordered_map <string,int> mp;
int T;cin>>T;
while(T--)
{
int op;cin >> op;
string t;
int a;
if(op == 1)
{
cin >> t >> a;
mp[t] = a;
cout << "OK" << endl;
}
else if(op == 2)
{
cin >> t;
if(!mp.count(t)) cout << "Not found" << endl;
else cout << mp[t] << endl;
}
else if(op == 3)
{
cin >> t;
if(!mp.count(t)) cout << "Not found" << endl;
else {
mp.erase(t);
cout << "Deleted successfully" << endl;
}
}
else
{
cout << mp.size() << endl;
}
}
}
2.不重复数字
这道题我们用cin和cout过不了,我们换成scanf和printf就能过了,因为cin cout耗时多
#include <iostream>
#include <unordered_map>
using namespace std;
int main()
{
int T;scanf("%d",&T);
while(T--)
{
unordered_map <int,int> mp;
int n;scanf("%d",&n);
while(n--)
{
int t;scanf("%d",&t);
if(!mp.count(t)) printf("%d ",t);
mp[t]++;
}
printf("\n");
}
return 0;
}
3.阅读理解
#include <iostream>
#include <unordered_map>
#include <set>
using namespace std;
unordered_map <string,set<int>> mp;
int main()
{
int n;cin >> n;
string t;
int len;
for(int i = 1;i<=n;i++)
{
int tmp = i;
cin >> len;
while(len--)
{
cin >> t;
mp[t].insert(tmp);
}
}
int m;cin >> m;
while(m--)
{
cin >> t;
for(auto e : mp[t])
{
cout << e << " ";
}
cout << endl;
}
return 0;
}
4.AB数对
这道题我们只要转换成A=B+C就行了,我们要做的就是把所有的数的出现次数都存在map里,然后我们只要找到符合B+C也就是A的出现次数的和就行了
#include <iostream>
#include <unordered_map>
typedef long long ll;
using namespace std;
const int N = 2e5+10;
ll a[N];
unordered_map <int,int> mp;
int main()
{
int n;cin >> n;
int c;cin >> c;
for(int i = 1;i<=n;i++)
{
cin >> a[i];
mp[a[i]]++;
}
ll ret = 0;
for(int i = 1;i<=n;i++)
{
ret+=mp[a[i]+c];
}
cout << ret<< endl;
}
P3405 [USACO16DEC] Cities and States S
这道题比如我们看MI-FL 对应FL-MI,我们只要找到这种情况有多少对就行了
我们可以先把MIFL字符串存下来,当反过来的FLMI出现的时候,我们就计数器++就行了
#include <iostream>
#include <unordered_map>
using namespace std;
unordered_map <string,int> mp;
int main()
{
int ret = 0;
int n;cin >> n;
string a,b;
while(n--)
{
cin >> a >> b;
a = a.substr(0,2);
if(a==b) continue;
ret+=mp[b+a];
mp[a+b]++;
}
cout << ret << endl;
return 0;
}