DS-大纲新增-并查集
考点-导图
简单版并查集
//并查集
#include<stdio.h>
#include<bits/stdc++.h>
using namespace std;
#define MAX_SIZE 100
int S[MAX_SIZE];//集合
void initiate(int S[]){//初始化
for(int i=0;i<MAX_SIZE;i++)
S[i]=-1;//初始化全都自成一派
}
int FindX(int S[],int x){//找x的教主
while(x>=0){
x=S[x];//继续向上找教主
}
return x;//找到教主返回其位置
}
void Union(int S[],int root1,int root2){//合成一个帮派
if(root1==root2)
return ;
S[root1]=root2;//合并
}
int main(){
initiate(S);//初始化
for(int i=0;i<MAX_SIZE;i++)
cout<<S[i]<<" ";
cout<<endl;
Union(S,1,2);
for(int i=0;i<MAX_SIZE;i++)
cout<<S[i]<<" ";
cout<<endl;
}
并查集的优化
//并查集
#include<stdio.h>
#include<bits/stdc++.h>
using namespace std;
#define MAX_SIZE 100
int S[MAX_SIZE];//集合
void initiate(int S[]){//初始化
for(int i=0;i<MAX_SIZE;i++)
S[i]=-1;//初始化全都自成一派
}
int FindX(int S[],int x){//找x的教主
while(x>=0){
if(S[x]>=0)
x=S[x];//继续向上找教主
else
break;
}
return x;//找到教主返回其位置
}
void Union(int S[],int root1,int root2){//合成一个帮派
//优化将结点数少的集合合并到结点数多的集合,使其高度不增,使得其FindX的效率提高
if(root1==root2)
return ;
if(S[root1]>S[root2]){//负数越大,绝对值就越小,其结点数就越少
S[root2]+=S[root1];//结点数转移
S[root1]=root2;//归并到树高的集合
}
else{
S[root1]+=S[root2];//结点数转移
S[root2]=root1;//归并到树高的集合
}
}
int main(){
initiate(S);//初始化
for(int i=0;i<MAX_SIZE;i++)
cout<<S[i]<<" ";
cout<<endl;
Union(S,0,2);
Union(S,0,4);
Union(S,0,7);
Union(S,1,3);
Union(S,1,8);
Union(S,1,6);
Union(S,1,10);
Union(S,1,0);
for(int i=0;i<MAX_SIZE;i++)
cout<<S[i]<<" ";
cout<<endl;
cout<<FindX(S,3)<<endl;
}
并查集的应用
无向图-邻接矩阵-判断一个图的连通性/连通分量
思想:遍历各条边,一条边连接两个顶点,判断这两个顶点是否属于同一个集合,如果不属于同一个集合则合并为一个集合,当处理完所有的边,扫描一遍顶点表/集合表,那些值为负数的即为每个集合的根结点,记录的是该集合中又多少个结点。
如图所示:
//根据并查集,判断一个无向图的连通性/连通分量
//并查集
#include<stdio.h>
#include<bits/stdc++.h>
using namespace std;
#define MAX_SIZE 6
typedef struct Graph{//邻接矩阵法
int g[MAX_SIZE][MAX_SIZE];//矩阵表
int v[MAX_SIZE];//集合、顶点表
}Graph;
Graph S;
void initiate(Graph &S){//初始化
memset(S.v,-1,sizeof(S.v));//初始化顶点表
memset(S.g,0,sizeof(S.g));//初始化无向图矩阵
}
int FindX(int S[],int x){//找x的教主
while(x>=0){
if(S[x]>=0)
x=S[x];//继续向上找教主
else
break;
}
return x;//找到教主返回其位置
}
void Union(Graph &S){//合成一个帮派
//优化将结点数少的集合合并到结点数多的集合,使其高度不增,使得其FindX的效率提高
for(int i=0;i<MAX_SIZE;i++){//扫描图
for(int j=i+1;j<MAX_SIZE;j++){
if(S.g[i][j]>0){//无向图有边
int si=FindX(S.v,i);//找到i的教主
int sj=FindX(S.v,j);//找到j的教主
if(si!=sj){//两个结点不同的教主,则合并
if(S.v[si]>S.v[sj]){//负数越大,绝对值就越小,其结点数就越少
S.v[sj]+=S.v[si];//结点数转移
S.v[si]=sj;//归并到树高的集合
}
else{
S.v[si]+=S.v[sj];//结点数转移
S.v[sj]=si;//归并到树高的集合
}
}
}
}
}
}
int main(){
int count=0;//连通分量
initiate(S);
cout<<"请输入无向图邻接矩阵:"<<endl;;
for(int i=0;i<MAX_SIZE;i++){
for(int j=0;j<MAX_SIZE;j++)
scanf("%d",&S.g[i][j]);
}
Union(S);
cout<<"顶点表:";
for(int i=0;i<MAX_SIZE;i++)
cout<<S.v[i]<<" ";
cout<<endl;
cout<<"邻接矩阵:"<<endl;
for(int i=0;i<MAX_SIZE;i++){
for(int j=0;j<MAX_SIZE;j++)
cout<<S.g[i][j]<<" ";
cout<<endl;
}
for(int i=0;i<MAX_SIZE;i++)
if(S.v[i]<0)
count++;//连通分量数+1
cout<<count<<endl;
}
结果如下:
无向图-邻接矩阵-判断一个图是否有环
思想:遍历各条边,一条边连接两个顶点,判断这两个顶点是否属于同一个集合,如果不属于同一个集合则合并为一个集合。
在处理的过程中若发现两个顶点本就已经在一个集合内,而如果又这两个顶点有边的话,那么这两个顶点之间必会有环。
图形如下:
//并查集应用-判断一个图是否有环
#include<stdio.h>
#include<bits/stdc++.h>
using namespace std;
#define MAX_SIZE 6
typedef struct Graph{//邻接矩阵法
int g[MAX_SIZE][MAX_SIZE];//矩阵表
int v[MAX_SIZE];//集合、顶点表
}Graph;
Graph S;
void initiate(Graph &S){//初始化
memset(S.v,-1,sizeof(S.v));//初始化顶点表
memset(S.g,0,sizeof(S.g));//初始化无向图矩阵
}
int FindX(int S[],int x){//找x的教主
while(x>=0){
if(S[x]>=0)
x=S[x];//继续向上找教主
else
break;
}
return x;//找到教主返回其位置
}
void Union(Graph &S){//合成一个帮派
int k=1;//输出环信息序号
//优化将结点数少的集合合并到结点数多的集合,使其高度不增,使得其FindX的效率提高
for(int i=0;i<MAX_SIZE;i++){//扫描图
for(int j=i+1;j<MAX_SIZE;j++){
if(S.g[i][j]>0){//无向图有边
int si=FindX(S.v,i);//找到i的教主
int sj=FindX(S.v,j);//找到j的教主
if(si!=sj){//两个结点不同的教主,则合并
if(S.v[si]>S.v[sj]){//负数越大,绝对值就越小,其结点数就越少
S.v[sj]+=S.v[si];//结点数转移
S.v[si]=sj;//归并到树高的集合
}
else{
S.v[si]+=S.v[sj];//结点数转移
S.v[sj]=si;//归并到树高的集合
}
}
else{//两个结点在同一个集合,那么如果该边存在,则必定有环
cout<<"第"<<k<<"个环集合:{ "<<si<<" ";
for(int i=0;i<MAX_SIZE;i++)
if(S.v[i]==si)
cout<<i<<" ";
cout<<"}"<<endl;
k++;
}
}
}
}
}
int main(){
int count=0;//连通分量
initiate(S);
cout<<"请输入无向图邻接矩阵:"<<endl;;
for(int i=0;i<MAX_SIZE;i++){
for(int j=0;j<MAX_SIZE;j++)
scanf("%d",&S.g[i][j]);
}
Union(S);
cout<<"顶点表:";
for(int i=0;i<MAX_SIZE;i++)
cout<<S.v[i]<<" ";
cout<<endl;
cout<<"邻接矩阵:"<<endl;
for(int i=0;i<MAX_SIZE;i++){
for(int j=0;j<MAX_SIZE;j++)
cout<<S.g[i][j]<<" ";
cout<<endl;
}
}
/*
0 1 1 0 0 0
1 0 1 0 0 0
1 1 0 0 0 0
0 0 0 0 1 1
0 0 0 1 0 1
0 0 0 1 1 0
*/
结果如下:
无向图-邻接表-判断一个图的连通分量
通过扫描顶点表的每个边表,判断当前的顶点表的位置的结点和边的另一端的另一个结点是否属于同一个集合,如果不是属于同一个集合,那么就合并为一个集合。
当扫描完边表和顶点表后,扫描更新的集合表,累加值小于0的个数,总的和就是集合根的个数,即连通分量。
//并查集应用-判断一个图的连通分量---邻接表
#include<stdio.h>
#include<bits/stdc++.h>
using namespace std;
#define MAX_SIZE 5
typedef struct ArcNode{
int adjv;//边的另一端的顶点信息
struct ArcNode *next;//下一条边
}ArcNode;
typedef struct Vnode{
char data;//顶点下标
struct ArcNode *first;//第一条边
}Vnode;
typedef struct LinkGraph{
Vnode g[MAX_SIZE];//邻接表
int S[MAX_SIZE];//顶点集合
int arcnum,vexnum;//边数,顶点数
}LinkGraph;
LinkGraph G;
void initiate(LinkGraph &G){
memset(G.S,-1,sizeof(G.S));
}
int FindX(int S[],int x){//找x的教主
while(x>=0){
if(S[x]>=0)
x=S[x];//继续向上找教主
else
break;
}
return x;//找到教主返回其位置
}
void Union(int S[],int root1,int root2){
if(root1==root2)
return;
if(S[root1]>S[root2]){//负数越大,绝对值就越小,其结点数就越少
S[root2]+=S[root1];//转移结点数
S[root1]=root2;//合并
}
else{
S[root1]+=S[root2];//结点数转移
S[root2]=root1;//归并到树高的集合
}
}
int ConnectCount(LinkGraph &G){
for(int i=0;i<MAX_SIZE;i++){//遍历顶点表
ArcNode *j=G.g[i].first;//取出第一条边
int ri=FindX(G.S,i);//找顶点i的集合
while(j!=NULL){//遍历边表
int rj=FindX(G.S,j->adjv);//找j边的另一端顶点的集合
if(ri!=rj){//两个的集合不同
Union(G.S,ri,rj);//合并
}
j=j->next;
}
}
}
int main() {
initiate(G);
ArcNode *pa=(ArcNode*)malloc(sizeof(ArcNode));
pa->adjv=1;//a-b
ArcNode *pa1=(ArcNode*)malloc(sizeof(ArcNode));
pa1->adjv=2;//a-c
pa1->next=NULL;
pa->next=pa1;
G.g[0].data='a';
G.g[0].first=pa;
ArcNode *pb=(ArcNode*)malloc(sizeof(ArcNode));
pb->adjv=0;//b-a
ArcNode *pb1=(ArcNode*)malloc(sizeof(ArcNode));
pb1->adjv=2;//b-c
pb1->next=NULL;
pb->next=pb1;
G.g[1].data='b';
G.g[1].first=pb;
ArcNode *pc=(ArcNode*)malloc(sizeof(ArcNode));
pc->adjv=0;//c-a
ArcNode *pc1=(ArcNode*)malloc(sizeof(ArcNode));
pc1->adjv=1;//c-b
pc1->next=NULL;
pc->next=pc1;
G.g[2].data='c';
G.g[2].first=pc;
ArcNode *pd=(ArcNode*)malloc(sizeof(ArcNode));
pd->adjv=4;//d-e
pd->next=NULL;
G.g[3].data='d';
G.g[3].first=pd;
ArcNode *pe=(ArcNode*)malloc(sizeof(ArcNode));
pe->adjv=3;//e-d
pe->next=NULL;
G.g[4].data='e';
G.g[4].first=pe;
cout<<"合并前的顶点表:";
for(int i =0;i<MAX_SIZE;i++)
cout<<G.S[i]<<" ";
cout<<endl;
cout<<"邻接表的信息如下:"<<endl;;
for(int i=0;i<MAX_SIZE;i++){
ArcNode *j=G.g[i].first;
cout<<G.g[i].data;
while(j!=NULL){
cout<<"->"<<G.g[j->adjv].data;
j=j->next;
}
cout<<endl;
}
ConnectCount(G);
cout<<"合并后的顶点表:";
int count=0;
for(int i =0;i<MAX_SIZE;i++){
if(G.S[i]<0)
count++;
cout<<G.S[i]<<" ";
}
cout<<endl;
cout<<"该无向图的连通分量数="<<count<<endl;
return 0;
}
结果如下:
无向图-邻接表–判断一个图是否有环
通过扫描顶点表的每个边表,判断当前的顶点表的位置的结点和边的另一端的另一个结点是否属于同一个集合,如果不是属于同一个集合,那么就合并为一个集合。
在扫描的过程中,如果发现两个结点属于同一个集合,且满足上三角的特性,j>i,则进行输出环
//并查集应用-判断一个图是否有环---邻接表
#include<stdio.h>
#include<bits/stdc++.h>
using namespace std;
#define MAX_SIZE 5
typedef struct ArcNode{
int adjv;//边的另一端的顶点信息
struct ArcNode *next;//下一条边
}ArcNode;
typedef struct Vnode{
char data;//顶点下标
struct ArcNode *first;//第一条边
}Vnode;
typedef struct LinkGraph{
Vnode g[MAX_SIZE];//邻接表
int S[MAX_SIZE];//顶点集合
int arcnum,vexnum;//边数,顶点数
}LinkGraph;
LinkGraph G;
int flag[MAX_SIZE];//用于不重复输出环的集合
void initiate(LinkGraph &G){
memset(G.S,-1,sizeof(G.S));
}
int FindX(int S[],int x){//找x的教主
while(x>=0){
if(S[x]>=0)
x=S[x];//继续向上找教主
else
break;
}
return x;//找到教主返回其位置
}
void Union(int S[],int root1,int root2){
if(root1==root2)
return;
if(S[root1]>S[root2]){//负数越大,绝对值就越小,其结点数就越少
S[root2]+=S[root1];//转移结点数
S[root1]=root2;//合并
}
else{
S[root1]+=S[root2];//结点数转移
S[root2]=root1;//归并到树高的集合
}
}
int ConnectCount(LinkGraph &G){
int k=1;//用于输出图中有几个环
for(int i=0;i<MAX_SIZE;i++){//遍历顶点表
ArcNode *j=G.g[i].first;//取出第一条边
int ri=FindX(G.S,i);//找顶点i的集合
while(j!=NULL){//遍历边表
int rj=FindX(G.S,j->adjv);//找j边的另一端顶点的集合
if(ri!=rj){//两个的集合不同
Union(G.S,ri,rj);//合并
}
else if(j->adjv>i){//在两个集合相同的情况下,判断两个结点的下标是否满足上三角,以防一条边扫两次
if(!flag[ri]){
cout<<"第"<<k<<"个环集合:{ "<<G.g[ri].data;
for(int l=0;l<MAX_SIZE;l++)
if(G.S[l]==ri)
cout<<","<<G.g[l].data;
cout<<"}"<<endl;
k++;
}
flag[ri]=1;//标记,不重复输出
}
j=j->next;
}
}
}
int main() {
initiate(G);
ArcNode *pa=(ArcNode*)malloc(sizeof(ArcNode));
pa->adjv=1;//a-b
ArcNode *pa1=(ArcNode*)malloc(sizeof(ArcNode));
pa1->adjv=2;//a-c
pa1->next=NULL;
pa->next=pa1;
G.g[0].data='a';
G.g[0].first=pa;
ArcNode *pb=(ArcNode*)malloc(sizeof(ArcNode));
pb->adjv=0;//b-a
ArcNode *pb1=(ArcNode*)malloc(sizeof(ArcNode));
pb1->adjv=2;//b-c
pb1->next=NULL;
pb->next=pb1;
G.g[1].data='b';
G.g[1].first=pb;
ArcNode *pc=(ArcNode*)malloc(sizeof(ArcNode));
pc->adjv=0;//c-a
ArcNode *pc1=(ArcNode*)malloc(sizeof(ArcNode));
pc1->adjv=1;//c-b
pc1->next=NULL;
pc->next=pc1;
G.g[2].data='c';
G.g[2].first=pc;
ArcNode *pd=(ArcNode*)malloc(sizeof(ArcNode));
pd->adjv=4;//d-e
pd->next=NULL;
G.g[3].data='d';
G.g[3].first=pd;
ArcNode *pe=(ArcNode*)malloc(sizeof(ArcNode));
pe->adjv=3;//e-d
pe->next=NULL;
G.g[4].data='e';
G.g[4].first=pe;
cout<<"合并前的顶点表:";
for(int i =0;i<MAX_SIZE;i++)
cout<<G.S[i]<<" ";
cout<<endl;
cout<<"邻接表的信息如下:"<<endl;;
for(int i=0;i<MAX_SIZE;i++){
ArcNode *j=G.g[i].first;
cout<<G.g[i].data;
while(j!=NULL){
cout<<"->"<<G.g[j->adjv].data;
j=j->next;
}
cout<<endl;
}
ConnectCount(G);
cout<<"合并后的顶点表:";
int count=0;//连通分量个数
int circle=0;//环的个数
for(int i =0;i<MAX_SIZE;i++){
if(G.S[i]<0)
count++;//累加连通分量数
if(flag[i]>0)
circle++;//累加环个数
cout<<G.S[i]<<" ";
}
cout<<endl;
cout<<"该无向图的连通分量数="<<count<<endl;
if(circle>0){
cout<<"该无向图有环;"<<"环的个数为:"<<circle<<endl;
}
else
cout<<"该无向图无环;"<<endl;
return 0;
}
结果如下: