图的连通性判别
首先约定使用邻接表来储存图的数据,使用stl的vector容器实现邻接表。节点编号从1开始
1.无向图的连通分量
考虑无向图的连通分量,不考虑图中边的权重。
算法:使用一个数组comp储存每个节点所属连通分量的id,id==0的话说明该节点未被标记。然后用对所有未被标记的节点逐个使用DFS算法。算法结束后即可得到正确的comp数组。
/*无向图的连通分量*/
#include <iostream>
#include <vector>
#include <set>
using namespace std;
/*节点的最大个数,假设为1000*/
const int max_v=1000;
/*实际节点个数*/
int num_v;
/*边数*/
int num_e;
/*邻接表,只存储连接边的另一个节点的编号*/
vector<int> graph[max_v];
/*标记节点所属的连通分量,初始化为0*/
int comp[max_v]={0};
/*当前所属连通分量的id*/
int comp_id=1;
/*记录有几个连通分量*/
int comp_num=0;
/*读取stdin输入,根据此将一个无向图储存到邻接表中*/
void input(){
cin>>num_v>>num_e;
for(int i=0;i<num_e;i++){
int v1,v2;
cin>>v1>>v2;
graph[v1].push_back(v2);
graph[v2].push_back(v1);
}
}
/*DFS,并将遍历到的节点更新visit状态*/
void DFS(int v){
/*修改v的标志位*/
comp[v]=comp_id;
for(int i=0;i<graph[v].size();i++){
int to=graph[v][i];
if(!comp[to]){
DFS(to);
}
}
}
int main(void){
input();
for(int i=1;i<=num_v;i++){
if(comp[i]==0){
DFS(i);
comp_num++;
comp_id++;
}
}
cout<<"连通分量数量: "<<comp_num<<endl;
for(int i=1;i<=comp_num;i++){
cout<<"连通分量"<<i<<":";
for(int j=1;j<=num_v;j++){
if(comp[j]==i) cout<<j<<",\t";
}
cout<<endl;
}
return 0;
}
有向图的强连通分量
使用Tarjan算法。Tarjan算法的原理本鼠实在理解不了,只好嗯背算法流程了。
先贴代码,再用一个测试样例分析:
/*寻找所有强连通分量*/
#include <iostream>
#include <vector>
#include <stack>
using namespace std;
const int v_max=1000;
/*节点的最大个数,假设为1000*/
const int max_v=1000;
/*实际节点个数*/
int num_v;
/*边数*/
int num_e;
/*邻接表,只存储连接边的另一个节点的编号*/
vector<int> graph[max_v+1];
/*标记节点所属的连通分量,初始化为0*/
int comp[max_v+1]={0};
/*当前所属连通分量的id*/
int comp_id=1;
/*记录有几个连通分量*/
int comp_num=0;
/*dfn数组*/
int dfn[max_v+1]={0};
/*low数组*/
int low[max_v+1]={0};
/*存储节点编号的栈*/
stack<int> mystack;
/*时间戳*/
int order=1;
/*节点是否已在栈中*/
bool instack[max_v+1]={false};
/*读取stdin输入,根据此将一个无向图储存到邻接表中*/
void input(){
cin>>num_v>>num_e;
for(int i=0;i<num_e;i++){
int v1,v2;
cin>>v1>>v2;
graph[v1].push_back(v2);
//graph[v2].push_back(v1); 有向图不能这么写
}
}
int min(int i,int j){return i<j ? i : j;}
void tarjan(int v_id){
/*visit到新的节点,将dfn和low中对应的值初始化为相等的*/
dfn[v_id]=order;
low[v_id]=order;
order++;
/*更新instack数组*/
instack[v_id]=true;
/*当前节点进栈*/
mystack.push(v_id);
/*进行dfs*/
for(int i=0;i<graph[v_id].size();i++){
int next = graph[v_id][i];
/*与v_id相邻的节点没有被visit过*/
if(dfn[next]==0){
tarjan(next);
/*更新low值*/
low[v_id]=min(low[v_id],low[next]);
}
/*与v_id相邻的节点已经在栈中(有环)*/
else if(instack[next]){
low[v_id]=min(low[v_id],dfn[next]);
}
}
/*判断以v_id为root的子树是否为一个强连通分量*/
if(low[v_id]==dfn[v_id]){
/*一直pop stack直到把v_id对应的节点pop出来*/
while(true){
int i=mystack.top();
mystack.pop();
instack[i]=false; /*更新栈*/
/*更新comp数组,增加新的强连通分量*/
comp[i]=comp_id;
if(i==v_id) break;
}
comp_id++;
comp_num++;
}
}
int main(void){
input();
for(int i=1;i<=num_v;i++){
if(comp[i]==0)
tarjan(i);
}
cout<<"强连通分量数量: "<<comp_num<<endl;
for(int i=1;i<=comp_num;i++){
cout<<"强连通分量"<<i<<":";
for(int j=1;j<=num_v;j++){
if(comp[j]==i) cout<<j<<",\t";
}
cout<<endl;
}
return 0;
}
关键的数据结构: Tarjan算法基于DFS,可以在线性时间内完成强连通分量的计算。 每个节点有两个属性:dfn和low。 dfn定义是时间戳,简单来说就是DFS的过程中,节点被visit的顺序。dfn属性在节点被visit后就不可改变。 low初始化时值与dfn一致,但是后续可以改变。 定理:两个节点的low值相等是这两个节点在同一强连通分量中的**充分条件**(**但不是必要条件,不能根据low来直接找强连通分量**)
算法流程:
使用递归的思想。若一个节点 node 被初次visit,初始化它的dfn属性和low属性。然后该节点压栈。将与当前节点相邻的所有节点分为两类:一种是还未被visit的,其集合记为\(S_1\);另一种是被visit过且当前在栈中的,其集合记为\(S_2\)。对集合\(S_1\)中的所有节点i,先递归的调用tarjan(i),然后在递归结束回溯回来时,更新node节点的low值,公式为 ** low[node]=min(low[node],low[i]); **
同理对于集合\(S_2\)中的所有节点i,直接更新node节点的low值,公式为 **low[node]=min(low[node],dfn[i]); **
完成上述遍历后,判断当前node节点的low值和dfn值是否相等。根据定理,若两者相等,则说明node是一个强连通子图的root。这时候pop stack直到pop出node节点。这个过程中pop出的所有节点都是一个强连通子图的节点。

浙公网安备 33010602011771号