并查集
入门题p1551 亲戚 详情见洛谷
ac代码
#include<iostream>
using namespace std;//针对多个集合确定每个集合的代表 通过代表判断两个元素是否在同一集合中 ---> 并查集
int relative[5010];//第i个人亲戚的代表
int n, m, p;
void init(){//最初 每个人都是自己这一个集合的代表
for(int i = 1; i <= n; i ++)
relative[i] = i;
}
int find(int x){//查找该元素的代表
if(x == relative[x]) return x;
else return find(relative[x]);
}
void Conform(int a, int b){//确定两个元素的代表
int x = find(a), y = find(b);//找出各自代表
//若两个元素代表不一致
if(x != y) relative[y] = x;//将其中一方的代表设置为另一方集合的代表 合并两个集合
}
int main(){
cin >> n >> m >> p;
init();
int a, b;
for(int i = 0; i < m; i ++){
cin >> a >> b;
Conform(a, b);
}
while(p --){
cin >> a >> b;
if(find(a) == find(b))
cout << "Yes" << endl;
else
cout << "No" << endl;
}
}
存在的问题
模板优化
// 一定不要忘了初始化,每个元素单独属于一个集合
void init() {
for (int i = 1; i <= n; i++)
f[i] = i;
}
int find(int x) { // 查询集合的“代表”
if (x == fa[x])return x;
return fa[x] = find(fa[x]); // 顺便【路径压缩】
// 这里写的很巧妙 返回值与赋值语句加在一起 使以后的递归次数降为1
}
void join(int c1, int c2) { // 合并两个集合
// f1为c1的代表,f2为c2的代表
int f1 = find(c1), f2 = find(c2);
if (f1 != f2) {
if (size[f1] < size[f2]) // 【取较大者】作为代表
swap(f1, f2);
fa[f2] = f1;
size[f1] += size[f2]; // 只有“代表”的size是有效的
}
}
哈希表
入门题p3370---针对多个字符串 统计不同的个数 采用计数排序的思想 + 哈希表的数据结构
计数排序:对于给定的一组数据 开辟额外的数组记录该数在序列中出现的次数 flag[3] = 2 代表3在序列中出现了两次 遍历完数组后 再去遍历开辟的记录数组 组值>0代表出现过 依次输出组值不为0的即可得到有序序列
从计数排序可以看出额外的数组下标就是当前数据的值 或经过映射后的值 若数据过大 考虑使用映射的方法 比如经典的取模运算 取模运算保证得到非负数 满足下标要求 121 % 11 = 0 hash[0] = 121
由此又产生了问题 随着取模范围的大小变化 会产生或多或少的哈希冲突(死去的数据结构对你说了句hello world) 比如 22 % 11 = 0 hash[0] = 11
解决哈希冲突
1. 设计一个复杂的哈希函数 降低重复的可能 但不能完全避免 % 11 太过简单
2. 十字链表法 --- 彻底解决问题 利用链表或二维数组 倾向于二维数组 设计简单 比如 % 11 若结果一样 将这些结果依次放到对应的一维数组中 查找时时间复杂度会取决于冲突次数 效率偏低
参考代码
vector<long long> hash[maxh];
// 以下是插入集合的方式,查找也是类似
void int insert(x){
int h = f(x); //计算哈希值
for (int i == 0, sz=hash[h].size(); i<sz; i++)
if (x == hash[h][i]) // 从数组中找到了这一项
return; // 什么都不做退出
hash[h].push_back(x); // 插入这个元素
}
3. 多哈希方法 采用二维的哈希数组 设计两个哈希函数 比如 % 11 % 5 得到的余数 分别作为行列下标 冲突概率会大幅降低
哈希函数设计
ac代码
#include<iostream>
#include<vector>
using namespace std;
const int N = 1e5;
int base = 261;
int mod = 23333;//额外开辟的数组下标范围就在 0 ~ mod - 1 大小为 mod
vector<string> link[N];//额外开辟的数组 下标为哈希函数计算结果 值为输入元素
int ans;
void insert(string s){
long long hash = 0;
for(int i = 0; s[i]; i ++)//计算哈希值 进行映射
hash = (hash * base + s[i]) % mod;
int length = link[hash].size();
for(int i = 0; i < length; i ++)//用二维数组模拟十字链表
if(s == link[hash][i]) return;
link[hash].push_back(s);
ans ++;
}
int main(){
int n;
cin >> n;
string s;
while(n --){
cin >> s;
insert(s);
}
cout << ans;
return 0;
}
理念介绍完毕 真神登场
特别注意 s.end()返回末尾哨兵的迭代器 其值为当前集合的长度
s.find(x)在找不到元素时会返回s.end() 千万不要用 x == *s.find(x)做判断 有可能当前集合的长度正好等于你要找的元素值 血的教训--->调了我半个小时呀 我说这哪有bug呀
补充 s.insert(x) 在集合中插入x 它的返回值为pair<该值的迭代器,是否插入成功>
这里的插入成功是指该值第一次插入 若之前集合中存在 则返回false 对于某些题可能会容易解决一些 这道题也可以使用 代替插入失败的判断条件
洛谷p5250 利用集合特性解决问题 详情见专页
注意:又是血的教训(大喜)m.insert({a, b}) != m[a] = b;
区别在于若m[a]已经存在值 第一种方法不会改变原来的值 而第二种会覆盖掉之前的值
洛谷p5266 入门题 熟悉相关函数操作
迭代器介绍:C++迭代器_Zhc_AuC的博客-CSDN博客 比较详细
关于O2优化:C++手动开启O2优化(以及-O -O1 -O2 -O3优化的知识点)(竞赛可用)_c++ o2优化 了什么?_xuanweiace的博客-CSDN博客