C++ 图论之并查集
程序员文章站
2022-06-08 19:06:12
...
首先,我们定义一个数组,用双亲表示法来表示各棵树(所有的集合元素个数总和为N):
int Tree[N];
用Tree[i]来表示结点i的双亲结点,若Tree[i]为-1则表示该结点不存在双亲结点,即结点i为其所在树的根结点。
那么,为了查找结点x所在树的根结点,我们定义以下函数:
int findRoot(int x){
int ret;
while (Tree[x]!=1){
x=Tree[x]; // 若当前结点为非根结点则一直查找其双亲结点
}
ret=x; // 返回根结点编号
return ret;
}
另外若需要在查找过程中添加路径压缩的优化,我们修改以上两个函数为:
int findRoot(int x){
int ret;
int temp=x;
while (Tree[x]!=1){
x=Tree[x];
}
ret=x;
x=temp; // 再做一次从结点x到根结点的遍历
while(Tree[x]!=1){
int t=Tree[x];
Tree[x]=ret;
x=t; // 遍历过程中将这些结点的双亲结点都设为已经查找得到的根结点编号
}
return ret;
}
有了这些函数,结合并查集的工作原理和集合合并方法,我们就可以开始试着解决并查集问题了。
应用一: 连通分量
附C++代码:
//
// 1012.cpp
// 畅通工程
//
// Created by chenmeiqi on 2019/4/2.
// Copyright © 2019年 chenmeiqi. All rights reserved.
//
#include <iostream>
using namespace std;
int Tree[1000];
int findRoot(int x){
int temp=x;
int ret;
while (Tree[x]!=0){
x=Tree[x]; // 若当前结点为非根结点则一直查找其双亲结点
}
ret=x; // 根结点编号
x=temp; // 再做一次从结点x到根结点的遍历
while(Tree[x]!=0){
int t=Tree[x];
Tree[x]=ret;
x=t; // 遍历过程中将这些结点的双亲结点都设为已经查找得到的根结点编号
}
return ret;
}
int main(int argc, const char * argv[]) {
int n,m;
int temp_m;
int city_1,city_2;
while (cin>>n) {
if(n==0){
break;
}
cin>>m;
for(int i=0;i<1000;i++){ // 初始化
Tree[i]=0;
}
int res=0;
temp_m=m;
while (temp_m--) {
cin>>city_1>>city_2;
city_1=findRoot(city_1); // 找到第一个城市的根结点
city_2=findRoot(city_2); // 找到第二个城市的根结点
if (city_1!=city_2) { // 不相等就合并根结点
Tree[city_1]=city_2;
}
}
for (int j=1; j<=n; j++) {
if (Tree[j]==0) {
res++;
}
}
res-=1; // 减1就是连通分量的值
cout<<res<<endl;
}
return 0;
}
应用二:也是连通分量
先放一个我写的第一版C++代码:
//
// 1444.cpp
// More is Better
//
// Created by chenmeiqi on 2019/4/3.
// Copyright © 2019年 chenmeiqi. All rights reserved.
//
#include <iostream>
using namespace std;
int Tree[10000000];
int res[10000000];
int findRoot(int x){
int res;
while (Tree[x]!=0) {
x=Tree[x];
}
res=x;
return res;
}
int cmp(int a,int b){
return a>b;
}
int main(int argc, const char * argv[]) {
int n;
int temp_n;
int boy_1,boy_2;
int temp_boy_1;
while (cin>>n) {
temp_n=n;
for (int i=0; i<10000000; i++) {
Tree[i]=0;
res[i]=0;
}
while (temp_n--) {
cin>>boy_1>>boy_2;
temp_boy_1=boy_1;
boy_1=findRoot(boy_1);
boy_2=findRoot(boy_2);
if(boy_1!=boy_2){
Tree[boy_1]=boy_2;
while (Tree[temp_boy_1]!=0) { // 更新根结点
int t=Tree[temp_boy_1];
Tree[temp_boy_1]=boy_2;
temp_boy_1=t;
}
}
}
for (int i=1; i<=10000000; i++) { // 计算每个集合里的个数
if(Tree[i]!=0){
res[Tree[i]]++;
}
}
sort(res, res+10000000,cmp); // 排序,找到最多的那个
cout<<res[0]+1<<endl;
}
return 0;
}
不是很满意,时间复杂度过高。
写完后结合答案修正了一下:
//
// 1444.cpp
// More is Better
//
// Created by chenmeiqi on 2019/4/3.
// Copyright © 2019年 chenmeiqi. All rights reserved.
//
#include <iostream>
using namespace std;
#define N 10000001 // 后面没有分号!!
int Tree[N];
int sum[N];
int findRoot(int x){
int res;
while (Tree[x]!=0) {
x=Tree[x];
}
res=x;
return res;
}
int cmp(int a,int b){
return a>b;
}
int main(int argc, const char * argv[]) {
int n;
int temp_n;
int boy_1,boy_2;
int res;
while (cin>>n) {
temp_n=n;
res=1;
for (int i=1; i<N; i++) {
Tree[i]=0;
sum[i]=1; // 初始时都是1
}
while (temp_n--) {
cin>>boy_1>>boy_2;
boy_1=findRoot(boy_1);
boy_2=findRoot(boy_2);
if(boy_1!=boy_2){
Tree[boy_1]=boy_2;
sum[boy_2]+=sum[boy_1];
}
}
for(int i=1;i<N;i++){
if(Tree[i]==0 && res<sum[i]){
res=sum[i];
}
}
cout<<res<<endl;
}
return 0;
}
嗯,感觉简洁了很多。。
应用三:Kruskal 算法(最小生成树)
附代码:
//
// 1017.cpp
// 还是畅通工程
//
// Created by chenmeiqi on 2019/4/8.
// Copyright © 2019年 chenmeiqi. All rights reserved.
//
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
int Tree[100];
struct Edge
{
int begin; // 起始顶点
int end; // 终点
int weight; // 该边权值
bool operator < (const Edge& a) const{ // 重载 < 运算符
return weight<a.weight;
};
};
int findRoot(int x){ // 找根结点
int res=0;
while(Tree[x]!=0){
x=Tree[x];
}
res=x;
return res;
}
int main(int argc, const char * argv[]) {
int n,a,b,temp;
while(cin>>n){
for (int i=0; i<100; i++) {
Tree[i]=0; // 初始化
}
if(n==0){
break;
}
int res=0;
int number=n*(n-1)/2;
vector<Edge> edges;
while(number--){
Edge e;
cin>>e.begin>>e.end>>e.weight;
edges.push_back(e);
}
sort(edges.begin(),edges.end()); // 排序,以从权值小的边开始遍历
int count=edges.size();
for(int i=0;i<count;i++)
{
a=findRoot(edges[i].begin);
b=findRoot(edges[i].end);
temp=edges[i].begin;
if(a!=b){
Tree[a]=b;
while(Tree[temp]!=0){ // 路径压缩优化
int t=Tree[temp];
Tree[temp]=b;
temp=t;
}
res+=edges[i].weight; // 累加该边权值
}
}
cout<<res<<endl;
}
return 0;
}
应用四:还是Kruskal算法
附代码:
//
// 1144.cpp
// Freckles
//
// Created by chenmeiqi on 2019/4/8.
// Copyright © 2019年 chenmeiqi. All rights reserved.
//
#include <iostream>
#include <cmath>
#include <algorithm>
#include <iomanip>
using namespace std;
#define N 101
#define M 5050 // n(n-1)/2
int Tree[N];
struct Vertex{ // 顶点集(坐标)
float x;
float y;
}v[N];
struct Edge // 边集
{
int vertex_1;
int vertex_2;
float weight;
bool operator < (const Edge& a) const{
return weight<a.weight;
}
}e[M];
float getPosition(Vertex a, Vertex b){ // 求两点间距离
float res;
res=sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
return res;
}
int findRoot(int x){ // 并查集
while(Tree[x]!=0){
x=Tree[x];
}
return x;
}
int main(int argc, const char * argv[]) {
int n,temp_n;
int a,b;
float res;
while(cin>>n){
res=0;
for(int i=0;i<N;i++){
Tree[i]=0;
}
temp_n=n;
int t=0;
while(n--){
cin>>v[t].x>>v[t].y;
t++;
}
int k=0;
for(int i=0;i<temp_n;i++){
for(int j=i+1;j<temp_n;j++){
e[k].vertex_1=i;
e[k].vertex_2=j;
e[k].weight=getPosition(v[i],v[j]);
k++;
}
}
sort(e,e+k); // 注意这里数量是 k 不是 n
for(int i=0;i<k;i++){
a=findRoot(e[i].vertex_1);
b=findRoot(e[i].vertex_2);
if(a!=b){
Tree[a]=b;
res+=e[i].weight;
}
}
cout<<setiosflags(ios::fixed)<<setprecision(2)<<res<<endl; // 保留两位小数
}
return 0;
}