目录
无向图G中,如果存在从顶点v1到顶点v2有路径,那么v1和v2是连通的。
如果对于无向图中的任意两个顶点vi和vj之间都是连通的,那么无向图G是连通图。
连通分量:是无向图中的极大连通子图。
如图一所示为连通图:
如图二所示不是连通图(有是两个连通分量):
注:由于图二中存在v6,v7和v1,v2,v3,v4,v5是不完全连通的,所以属于两个不同的连通分量,这对于无向图来说是这样,而对于有向图来说的话,判断更加的复杂。
思路:首先读者需要对并查集 进行了解,其中主要是查找和合并操作以及路径压缩的方法。由于开始的时候对parent数组初始化都为节点自身,使用rank数组记录每一个集合中合并成的树的高度,将高度矮的合并到高度高的树上;判断无向图的连通分量,就是通过并查集的查找操作,查找到一个节点的根节点,判断这两个节点的根节点是否相同,不同的话表示属于两个集合,也就是两个不同的连通分量。最后就是输出每一个连通分量的集合。
- #include<stdio.h>
- #include<stdlib.h>
-
- #define MAXSIZE 15
-
- typedef int ElemType;
-
- int parent[MAXSIZE];
- int rank[MAXSIZE];
-
- void init(){
- for(int i=0;i<MAXSIZE;i++){
- parent[i]=i;
- rank[i]=0;
- }
- }
-
- //并查集查找操作(包含路径压缩)
- int find(int x){
- if(parent[x]==x){
- return x;
- }
- return parent[x]=find(parent[x]);
- }
-
- //合并操作
- void Union(int x,int y){
- int fx=find(x);
- int fy=find(y);
- //当x和y属于不同的集合时,进行合并操作
- if(fx!=fy){
- if(rank[fx]>rank[fy]){
- parent[fy]=fx;
- }else{
- parent[fx]=fy;
- if(rank[fx]==rank[fy]){
- rank[fx]++;
- }
- }
- }
- }
-
-
- int main(){
- int n,m;
- init();
- printf("请输入无向图的顶点数: ");
- scanf("%d",&n);
- printf("请输入无向图的边数: ");
- scanf("%d",&m);
- printf("请输入无向图的边: \n");
- for(int i=1;i<=m;i++){
- int u,v;
- scanf("%d %d",&u,&v);
- Union(u,v);
- }
- int count=0;
- //note用来记录每个连通分量的根节点
- int note[MAXSIZE];
- for(int i=1;i<=n;i++){
- int fx=find(i);
- if(fx==i){
- count++;
- note[count]=fx;
- }
- }
- printf("无向连通图的连通分量: %d\n",count);
- for(int i=1;i<=count;i++){
- printf("连通分量[%d]: ",i);
- for(int j=1;j<=n;j++){
- int fx=find(j);
- if(fx==note[i]){
- printf("%d\t",j);
- }
- }
- printf("\n");
- }
- return 0;
- }
- /*
- 7 6
-
- 1 2
- 1 4
- 2 3
- 3 4
- 2 5
- 6 7
- */
有向图G中,如果对于每一对,从vi到vj和vj到vi都存在路径,则图G为强连通图。
强连通分量:有向图中的极大连通子图。
如下图所示,虽然原图不是一个强连通图,但是有两个强连通分量:
关于深度优先搜索和广度优先搜索请看上面给出的链接。
思路:首先是进行深度优先遍历,并将遍历的结果存储在finish数组中;正向遍历完成之后就是进行逆向深度优先遍历,正向边使用edge二维数组存储,逆向边使用redge二维数组存储;逆向深度优先遍历的时候使用正向遍历的结果finish,从finish[n]到finish[1]进行遍历,并且使用k来记录强连通分量的个数,Set数组记录强连通分量的集合。
- #include<stdio.h>
- #include<stdlib.h>
-
- #define MAXSIZE 15
-
- typedef int ElemType;
-
- //用于记录深度优先搜索过程中访问的节点
- int finish[MAXSIZE];
- //用于记录节点是否被访问
- int visit[MAXSIZE];
- //记录正向边
- int edge[MAXSIZE][MAXSIZE];
- //记录反向边
- int redge[MAXSIZE][MAXSIZE];
- //图的顶点数和边数
- int n,m;
- //记录强连通分量的集合
- int Set[MAXSIZE];
- //k用来记录强连通分量的个数
- int k=0;
- //记录访问的顶点数
- int cnt=0;
-
- //初始化
- void init(){
- for(int i=0;i<MAXSIZE;i++){
- visit[i]=0;
- finish[i]=0;
- Set[i]=0;
- for(int j=0;j<MAXSIZE;j++){
- edge[i][j]=0;
- redge[i][j]=0;
- }
- }
- }
-
- //深度优先搜索
- void dfs(int v){
- visit[v]=1;
- for(int i=1;i<=n;i++){
- if(visit[i]==0&&edge[v][i]==1){
- dfs(i);
- }
- }
- //记录访问的节点
- finish[++cnt]=v;
- }
- //反向深度优先搜索
- void rdfs(int v,int k){
- visit[v]=1;
- Set[v]=k;
- for(int i=1;i<=n;i++){
- if(visit[i]==0&&redge[v][i]==1){
- rdfs(i,k);
- }
- }
- }
-
- //求解强连通分量
- void Solve(){
- //首先进行正向的深度优先搜索
- for(int i=1;i<=n;i++){
- if(visit[i]==0){
- dfs(i);
- }
- }
- //对visit重新进行初始化
- for(int i=0;i<MAXSIZE;i++){
- visit[i]=0;
- }
- //进行逆向的深度优先搜索
- for(int i=n;i>=1;i--){
- int v=finish[i];
- if(visit[v]==0){
- k++;
- rdfs(v,k);
- }
- }
- }
- //输出强连通分量集合
- void display(){
- //打印深度优先访问的结果
- printf("cnt = %d\n",cnt);
- for(int i=1;i<=cnt;i++){
- printf("%d\t",finish[i]);
- }
- printf("\n");
-
- printf("强连通分量的个数: %d\n",k);
- for(int i=1;i<=k;i++){
- printf("强连通分量[%d]: ",i);
- for(int j=1;j<=n;j++){
- if(Set[j]==i){
- printf("%d\t",j);
- }
- }
- printf("\n");
- }
- }
-
- int main(){
- init();
- printf("请输入无向图的顶点数: ");
- scanf("%d",&n);
- printf("请输入无向图的边数: ");
- scanf("%d",&m);
- printf("请输入无向图的边: \n");
- for(int i=1;i<=m;i++){
- int u,v;
- scanf("%d %d",&u,&v);
- edge[u][v]=1;
- redge[v][u]=1;
- }
- Solve();
- display();
- return 0;
- }
- /*
- 4 4
-
- 1 3
- 1 2
- 3 4
- 4 1
-
- 4 7
-
- 1 2
- 1 3
- 3 1
- 3 4
- 4 3
- 4 2
- 4 1
-
- 7 9
-
- 1 2
- 1 3
- 1 4
- 2 1
- 2 4
- 5 2
- 4 5
- 6 7
- 7 6
- */