图的连通性判别

首先约定使用邻接表来储存图的数据,使用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出的所有节点都是一个强连通子图的节点。

posted @ 2022-04-12 22:33  带带绝缘体  阅读(219)  评论(0)    收藏  举报