图的连通性

图的连通性

  • 无向图:割点
    • 源代码
  • 无向图:边双连通分量
  • 有向图:强连通分量(SCC)

无向图:割点

前言

在一个连通分量G中,对任意一个点s做DFS,能访问到所有点,产生一棵“深搜优先生成树”T。

定理1:T的根结点s是割点,当且仅当s有2个或更多的子结点。

定理2:T的非根结点u是割点,当且仅当u存在一个子结点v,v及其后代都没有回退边连回u的祖先

源代码

#include
using namespace std;
const int maxn = 1e3+3;
int num[maxn];//记录DFS对每个点的访问顺序
int low[maxn];//记录该点与该点能回到祖先的num
// 只有low[v]>=num[u],就说明在v这条支路上没有回退边连回u的祖先,最多退到u本身
int dfn;//代表递归的顺序
vector<int> g[maxn];//存储图结构
bool iscut[maxn];//标记该点是否为割点
/*
 **割点**:
定理1:T的根结点s是割点,当且仅当s有2个或更多的子结点。
定理2:T的非根结点u是割点,当且仅当u存在一个子结点v,v及其后代都没有回退边连回u的祖先
*/

void DFS(int u,int father){//顶点u的父节点是father
	low[u]=num[u]=++dfn;//初始化
	int child=0;//u 的孩子数目
	for(int i=0;i<g[u].size();i++){
		int v=g[u][i];
		if(!num[v]){//未访问
			child++;
			DFS(v,u);
			low[u]=min(low[v],low[u]);//用后代的返回值更新
			if(low[v]>=num[u]&&u!=1){//定理2
				iscut[u]=true;
				// 若改为low[v]>num[u]&&u!=1 可求割边 (u,v)即为割边
			}else if(num[v]<num[u]&&v!=father){
				//处理回退边,father也是u的邻居,前面已访问
				low[u]=min(num[v],low[u]);
			}
		}
	}
	if(u==1&&child>2)iscut[1]=true;
}

int main(){
	int n,m;//点数,边数
	cin>>n>>m;
	int a,b;
	for(int i=0;i<m;i++){
		cin>>a>>b;
		g[a].push_back(b);
		g[b].push_back(a);
	}
	DFS(1,-1);//从1开始,-1表示根节点没有父节点
	for(int i=1;i<=n;i++)if(iscut[i])printf("%d ",i);//打印割点
}

无向图:边双连通分量

#include
using namespace std;
const int maxn = 1e3+3;
int low[maxn];//记录该点与该点能回到祖先的num
// 只有low[v]>=num[u],就说明在v这条支路上没有回退边连回u的祖先,最多退到u本身
int dfn;//代表递归的顺序
vector<int> g[maxn];//存储图结构
int n,m;//点数,边数

/*
 **边双连通分量**:
定义:如果任意两点之间,至少存在2条“边不重复”的路径,称为“边双连通”。
性质:
边双连通图中,去掉任意一个边,图仍然是连通的。
边双连通图中没有割边。
求解:
在DFS中,所有的low值相同的点的必定在同一个边双连通分量中
*/

void DFS(int u,int father){//顶点u的父节点是father
	low[u]=++dfn;//初始化
	for(int i=0;i<g[u].size();i++){
		int v=g[u][i];
		if(v==father)continue;//已访问
		if(!low[i]){
			DFS(v,u);
			low[u]=min(low[u],low[v]);
		}
	}
}

//将low值相同的点合并,(“缩点“),求解缩点的度
int deg[maxn];//计算每个缩点的度数
void tarjan(){
	for(int i=1;i<=n;i++){
		for(int j=0;j<g[i].size();j++)if(low[i]!=low[g[i][j]])deg[low[i]]++;
	}
}

int main(){
	
	cin>>n>>m;
	int a,b;
	for(int i=0;i<m;i++){
		cin>>a>>b;
		g[a].push_back(b);
		g[b].push_back(a);
	}
	DFS(1,-1);//从1开始,-1表示根节点没有父节点
	tarjan();
	// 至少在缩点树上增加多少条边,能使这棵树变为一个边双连通图。
	//至少增加的边数 =(总度数为1的结点数 + 1)/ 2
}

有向图:强连通分量(SCC)

#include
using namespace std;
/*
 **强连通分量**。
 如果一个有向图G不是强连通图,那么可以把它分成多个子图,其中每个子图的内部是强连通的,
 而且这些子图已经扩展到最大,不能与子图外的任意点强连通。
 称这样的一个“极大强连通”子图是G的一个强连通分量(Strongly Connected Component,SCC)
*/
const int maxn = 1e3+3;
int num[maxn];//记录DFS对每个点的访问顺序
int low[maxn];//记录该点与该点能回到祖先的num
// 只有low[v]>=num[u],就说明在v这条支路上没有回退边连回u的祖先,最多退到u本身
int dfn;//代表递归的顺序
vector<int> g[maxn];//存储图结构
int sta[maxn],top;//栈,栈顶,处理low重复问题
int sccno[maxn];//标记为同一个SCC
int cnt;//SCC的个数
int ans=0;//表示有多少对点可以相互到达的
void DFS(int u){
	sta[top++]=u;//入栈
	num[u]=low[u]=++dfn;//初始化
	for(int i=0;i<g[u].size();i++){
		int v=g[u][i];
		if(!num[v]){
			DFS(v);//DFS的最底层,是最后一个SCC
			low[u]=min(low[u],low[v]);
		}else if(!sccno[v])//处理回退边
			low[u]=min(low[u],num[v]);
	}
	if(low[u]==num[u]){//栈底的点是SCC的祖先
		cnt++;
        int x=0;//计算一个边内的点数
		while(true){
			int v = sta[--top];
			sccno[v]=cnt;
            x++;
			if(u==v){
                ans+=x*(x-1)/2;
                break;
            }
		}
	}
}

void tarjan(int n){
	//....对相关参量初始化
	for(int i=1;i<=n;i++)if(!num[i])DFS(i);
}

int main(){
	int n,m;//点数,边数
    scanf("%d%d",&n,&m);
	int a,b;
	for(int i=0;i<m;i++){
		scanf("%d%d",&a,&b);
		g[a].push_back(b);
	}
	tarjan(n);//强连通图判断
    printf("%d\n",ans);
}

你可能感兴趣的:(c++)