(C++)通过深度优先搜索得到无向图中的所有连通分量

(C++)通过深度优先搜索得到无向图中的所有连通分量

我们知道一个图的极大连通子图是这个图的连通分量,而一个连通图只有一个连通分量,就是图本身。本篇的内容是DFS的应用之一:找到一个图中所有的连通分量,代码参考《算法》第四版。

一.构造一个无向图

从本篇开始,我使用模版图类来表达一个无向图,图类及相关函数声明如下所示:

/* 节点类,图的节点数据类型可以任意给定 */
template<typename VertexType> class Vertex_new
{
public:
    Vertex_new(VertexType val)
    {
        in_out = 0;
        value = val;
    }
    VertexType value;
    int in_out;
    list<Vertex_new<VertexType> *> neighbours;
};




/* 模版图类,图的节点数据类型可以任意给定 */
template<typename VertexType> class undirectGraph_new
{
public:
    /*************************************************
    Function:   undirectGraph_new
    Description:    依据txt中对图的描述,对图进行初始化操作
    Calls:          addEdge
    Input:          string path 一个路径字符串
    Output:       一张可以代表每个节点连接情况的邻接表
    *************************************************/
    undirectGraph_new(string path);
    
    /*************************************************
    Function:   countOfVertex()
    Description:    一般在初始化后调用,用于返回图的节点数量
    Return:   int 类型的数据,代表节点数量
    *************************************************/
    int countOfVertex();
    
    /*************************************************
    Function:   countOfEdge()
    Description:    一般在初始化后调用,用于返回图的节点的边数数量
    Output:      int 类型的数据,代表边的数量
    *************************************************/
    int countOfEdge();
    
    /*************************************************
    Function:   addEdge
    Description:    一般在初始化图时使用,作用是根据具体情况,将节点加入邻接表
    Calls:          isInserted
    Input:          两个VertexType A和B ,代表两个节点的值
    *************************************************/
    void addEdge(VertexType firstVertex, VertexType secondVertex);
    
    /*************************************************
    Function:   print_allVertexAndEdge
    Description:    打印图的结构
    Output:      图中的每个节点及节点对应的邻居节点
    *************************************************/
    void print_allVertexAndEdge();
    
    /*************************************************
       Function:   isInserted
       Description:   判断一个节点的值是否已经是邻接表的某一个key(即判断这个节点是否已经被初始化)
       Input:          一个VertexType类型的节点值
       *************************************************/
    bool isInserted(VertexType v);
    
    
    //connectedComponent
    
    /*************************************************
       Function:   findConnectComponent
       Description:    找到图中的连通分量,并保存在id_cc当中
       *************************************************/
    void findConnectComponent();
    /*************************************************
       Function:   printConnectComponent
       Description:   打印连通分量
       *************************************************/
    void printConnectComponent();
    
    /*************************************************
       Function:   connected
       Description:    判断两个节点是否在一个连通分量里,  该函数需要在调用findConnectComponent()之后才有效
       Input:          两个value为VertexType 类型的顶点v和w
       *************************************************/
    bool connected(Vertex_new<VertexType> v, Vertex_new<VertexType> w);
    
    /*************************************************
       Function:   countOfCC()
       Description:    获得连通分量的个数
       Return:   int类型,连通分量的个数
       *************************************************/
    int countOfCC();//连通分量的个数
    
    
    /*************************************************
          Function:   id
          Description:    获得某顶点的连通分量id
          Return:   int类型,连通分量id
          *************************************************/
    int id(VertexType v);
      
private:
    
    /*************************************************
    Function:   initialVertexMarked_cc
    Description:    初始化每个节点的检测状况,全部初始化为“未检测”状态
    *************************************************/
    void initialVertexMarked_cc();
    
    /*************************************************
       Function:   isMarked_cc
       Description:   判断一个节点是否已经被检测
       Return:   返回true/false
       *************************************************/
    bool isMarked_cc(VertexType v);
    
    /*************************************************
    Function:   dfs_cc
    Description:   为连通算法专用的深度优先搜索,在搜索过程中记录路径上的点并赋予其连通分量id
    *************************************************/
    void dfs_cc(VertexType v);
    
    
    int m_countOfVertex;//顶点数
    int m_countOfEdge;//边数
    int cc_id;//代表在图中检测到的节点数
    map<VertexType, Vertex_new<VertexType>* > VertexSet;//邻接矩阵
    vector<vector<int> > m_GraphMatrix;
    multimap<int, VertexType> id_cc;//按所属的连通分量组id来存储各节点
    map<VertexType, bool> VertexMarked_cc;//VertexMarked_cc存储了各节点是否已经被访问过的信息,true代表访问过,VertexType代表节点的值
};

具体实现在GitHub,查看第169行到569行Graph_practise.hpp

二.测试用例

(C++)通过深度优先搜索得到无向图中的所有连通分量_第1张图片

本篇所用的图结构如图上所示(算法书上的图例子),通过一个txt文件可以很容易生成这个图,很明显这里有三个连通分量。

我们的目的就是通过深度搜索,找到图中的这三个连通分量

在搜索之前,声明一个变量cc_id,代表连通分量的ID,图中的每个节点,都有属于自己的连通分量ID。在搜索前,将cc_id置为0,代表第一个连通分量ID为0。

三.通过DFS找到连通分量
  1. 首先我们可以创建一个能够监控“节点是否被访问过”的数据结构,这里使用了map 前一个参数VertexType代表某节点的值, 第二个bool类型参数代表节点是否被访问。在搜索之前,我们需要初始化一下这个数据结构,使所有节点的访问状态都置为False
template<typename VertexType> void undirectGraph_new<VertexType>::initialVertexMarked_cc()
{
   typename map<VertexType, Vertex_new<VertexType>* >::iterator iter = VertexSet.begin();
   for(iter; iter != VertexSet.end(); iter++)
   {
   //借助邻接表来查看有哪些节点需要初始化
   VertexMarked_cc.insert(pair<VertexType, bool>(iter->first, false));
   //iter->first 就是邻接表中代表节点Value的部分。
   //邻接表的某一行为: 节点A的值 : 节点A对象(包含节点A的邻居)
   }
}
  1. 接着遍历每个节点,查看是否已经被访问,如果没有,则递归地调用深度搜索函数(从某个节点开始一直深入,直到没有与最开始的节点连通的路径)。从一个节点开始递归地进行深度搜索,最后得到的就是一个图内极大的连通子图(连通分量),因此从一个节点搜索完毕后,所做的事情就是把搜索路径上所有节点都标记为同一个连通分量ID。最开始调用的DFS递归函数在最后返回后,使cc_id更新,意味着上一个ID的连通分量已经寻找完毕,接着寻找下一个cc_id
//查找连通分量
template<typename VertexType> void undirectGraph_new<VertexType>::findConnectComponent()
{
   cc_id = 0;
   initialVertexMarked_cc()//初始化VertexMarked_cc
   template map<VertexType, Vertex_new<VertexType>* >::iterator iter = VertexSet.begin();
   for(iter; iter != VertesSet.end(); iter++)
   {
      if(!isMarked_cc(iter->first))
      {
        dfs_cc(iter->first);
        cc_id++;
      }
   }
}
//判断某节点是否已经被访问
template<typename VertexType> bool undirectGraph_new<VertexType>::isMarked_cc(VertexType v)
{
    return true == VertexMarked_cc.find(v)->second;
}
//DFS算法 标记连通分量ID
template<typename VertexType> void undirectGraph_new<VertexType>::dfs_cc(VertexType v)
{
   typename map<VertexType, bool>::iterator iter = VertexMarked_cc.find(v);
   iter->second = true; //标记该节点已经被访问
   id_cc.insert(pair<int, VertexType>(cc_id, v));
   template list<Vertex_new<VertexType> *>::iterator VertexIter = VertexSet.find(v)->second->neighbours.begin();
   //这里的作用是找到一个v节点的邻居节点,方便从邻居节点开始继续递归地深度搜索,这里可能写的太复杂了,可以在GitHub里查看图的构造。
   for(VertexIter; VerterxIter != VertexSet.find(v)->second->neighbours.end(); VertexIter++)
   {
       if(!isMarked_cc((*VertexIter)->value))
       {
        //如果邻居节点还是没有被检测过,则继续搜索
        dfs_cc((*VertexIter)->value))
       }
   }
}

最后可以写一个打印函数,来展示我们找到的所有连通分量

template<typename VertexType> void undirectGraph_new<VertexType>::printConnectComponent()
{
 for(int i = 0; i < cc_id; i++)
    {
        typename multimap<int, VertexType>::iterator iter_lower =id_cc.lower_bound(i);
        typename multimap<int, VertexType>::iterator iter_upper =id_cc.upper_bound(i);
        cout << "连通分量" << i <<" 的节点:";
        for(auto iter = iter_lower; iter != iter_upper; iter++)
        {
            cout << iter->second << ", ";
        }
        cout << endl;
    }
}

这里使用multmap是为了让相同key的pair也能存在于同一个容器中。调用上面的printConnectComponent()函数的运行结果如下图所示。
(C++)通过深度优先搜索得到无向图中的所有连通分量_第2张图片
结果和测试用例中的图结构是对应的。还有一些其他的方法,比如查看连通分量的个数、判断两个节点是否属于同一个连通分量以及查看某节点连通分量的ID等,比较简单,不写出来占地方了,已经上传到GitHub。

下一篇会继续记录 图算法的学习

你可能感兴趣的:(算法)