/* 节点类,图的节点数据类型可以任意给定 */
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
本篇所用的图结构如图上所示(算法书上的图例子),通过一个txt文件可以很容易生成这个图,很明显这里有三个连通分量。
我们的目的就是通过深度搜索,找到图中的这三个连通分量
在搜索之前,声明一个变量cc_id
,代表连通分量的ID,图中的每个节点,都有属于自己的连通分量ID。在搜索前,将cc_id
置为0,代表第一个连通分量ID为0。
map
前一个参数VertexType代表某节点的值, 第二个bool类型参数代表节点是否被访问。在搜索之前,我们需要初始化一下这个数据结构,使所有节点的访问状态都置为Falsetemplate<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的邻居)
}
}
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()
函数的运行结果如下图所示。
结果和测试用例中的图结构是对应的。还有一些其他的方法,比如查看连通分量的个数、判断两个节点是否属于同一个连通分量以及查看某节点连通分量的ID等,比较简单,不写出来占地方了,已经上传到GitHub。
下一篇会继续记录 图算法的学习