并查集:
查找多个元素是否属于同一集合,或将它们合并到同一集合中。
思想:
利用数组的下标和值进行连接,下标代表元素,值是该元素所属的集合。例:n[2] = 2, n[3] = 2,数字2和3属于同一集合。
题目:
hdoj:http://acm.hdu.edu.cn/showproblem.php?pid=1232
思路:
利用并查集来查询有多少个集合,每个集合间建一条路,最后答案就是集合数 - 1。
AC代码:
#include<cstdio>
#include<iostream>
#include<set>
#include<algorithm>
using namespace std;
const int M = 1000 + 10;
set<int>s;
set<int>::iterator it;
int nums[M];//建一个数组来储存数据
void inti( int a)//将每个元素的所属集合初始化为自身
{
for(int i = 1; i <= a; i++)
nums[i] = i;
}
int findelm(int a)//查询元素所属的集合
{
if(nums[a] == a)
return a;
return nums[a] = findelm(nums[a]);
}
void mix(int a, int b)//将不是同一集合的元素合并,合并后的集合以较小的数为准。需要注意的是这样合并只是修改顶点元素,并不修改子元素,所以在计算子元素时需要再进行一次查询,来更新它们的所属集合
{
int tmp1 = findelm(a), tmp2 = findelm(b);
if(tmp1 > tmp2)
nums[tmp1] = tmp2;
else
nums[tmp2] = tmp1;
}
main()
{
int n, m;
while(scanf("%d", &n) && n)
{
s.clear();
scanf("%d", &m);
inti(n);
while(m--)
{
int a, b;
scanf("%d %d", &a, &b);
if(findelm(a) != findelm(b))
mix(a, b);
}
for(int i = 1; i <= n; i++)
{
findelm(i);//更新所属集合
s.insert(nums[i]);
}
printf("%d\n", s.size() - 1);
}
}
最小生成树:
元素之间相连有消耗,将它们变成一种树状结构(生成树)后,消耗最少就是最小生成树。
思想(kruskal算法):
利用并查集+贪心的思想,从消耗开始升序排列,如果两点是同一集合就跳过,如果不是就将消耗累加,最后结果就是最少消耗。
题目:
hdoj:http://acm.hdu.edu.cn/showproblem.php?pid=1233
思路:同思想。
AC代码:
#include<cstdio>
#include<set>
#include<algorithm>
using namespace std;
const int M = 100 + 10;
const int S = 5000 + 10;
int nums[M];
struct Road
{
int a, b, l;
bool operator < (const Road &i)const//升序排列重载运算符
{
return l < i.l;
}
}road[S];
void inti( int a)
{
for(int i = 1; i <= a; i++)
nums[i] = i;
}
int findelm(int a)
{
if(nums[a] == a)
return a;
return nums[a] = findelm(nums[a]);
}
void mix(int a, int b)
{
int tmp1 = findelm(a), tmp2 = findelm(b);
if(tmp1 > tmp2)
nums[tmp1] = tmp2;
else
nums[tmp2] = tmp1;
}
set<int>s;
set<int>::iterator it;
main()
{
int tCase;
while(scanf("%d", &tCase) && tCase)
{
inti(tCase);
s.clear();
int n = tCase * (tCase - 1) / 2;
for(int i = 0; i < n; i++)
scanf("%d %d %d", &road[i].a, &road[i].b, &road[i].l);
sort(road, road + n);
int res = 0;
for(int i = 0; i < n; i++)
{
if(findelm(road[i].a) != findelm(road[i].b))//如果不在同一集合,就将它们合并并累加消耗
{
mix(road[i].a, road[i].b);
res += road[i].l;
}
}
printf("%d\n", res);
}
}
然而,当我们遇到过大的数据或非数字数据(比如10^9, 字符串)时,我们就难以用数组来实现并查集,这就需要c++的一种stl:map,利用它的对应关系来实现并查集(感觉比开数组好用多了)。
题目:
hdoj:http://acm.hdu.edu.cn/showproblem.php?pid=1856
思路:
利用map来实现并查集,并计数,求最多的朋友数。
AC代码:
#include<cstdio>
#include<map>
#include<algorithm>
using namespace std;
const int M = 100000 + 20;
map<int, int>m;
map<int, int>::iterator it;
map<int, int>mm;
int findele(int a)
{
if(m[a] == a)
return a;
else
return m[a] = findele(m[a]);
}
void mix(int a, int b)
{
int tmp1 = findele(a), tmp2 = findele(b);
if(tmp1 > tmp2)
m[tmp1] = tmp2;
else
m[tmp2] = tmp1;
}
main()
{
int tCase;
while(~scanf("%d", &tCase))
{
if(tCase == 0)
{
puts("1");
continue;
}
m.clear();
mm.clear();
while(tCase--)
{
int a, b;
scanf("%d %d", &a, &b);
if(m.find(a) == m.end())
m[a] = a;
if(m.find(b) == m.end())
m[b] = b;
if(m[a] != m[b])
mix(a, b);
}
for(it = m.begin(); it != m.end(); it++)
{
findele(it ->first);
if(mm.find(it ->second) == mm.end())
mm[it->second] = 1;
else
mm[it->second]++;
}
int maxn = 0;
for(it = mm.begin(); it != mm.end(); it++)
{
if(maxn < it ->second)
maxn = it ->second;
}
printf("%d\n", maxn);
}
}