有向图_十字链表存储结构_深度优先遍历_求有向图的强连通分量

目录

0.小结 基于遍历地求无向图的连通分量和求有向图的强连通分量

1.有向图的十字链表存储结构

2.有向图的正向(顺箭头)深度优先遍历:递归的退出:最后完成搜索的顶点

求有向图的强连通分量的步骤1:

3.有向图的逆向(逆箭头)深度优先遍历:求有向图的强连通分量。

求有向图的强连通分量的步骤2:

概念:在有向图G中,如果对于每一对v1,vj属于V,vi不等于vj,从vi到vj和从vj到vi都存在路径,则称G是强连通图;

有向图中的极大强连通子图称为有向图的强连通分量!

 

0.小结 基于遍历的求无向图的连通分量和求有向图的强连通分量

1.无向图的连通分量:

利用图的深度优先遍历DFS(或者是广度优先遍历BFS);

  1. 对于连通图,从图中的任一顶点出发就可以遍历所有的顶点;
  2. 对于非连通同,则需要从多个顶点出发才能遍历图中的所有顶点;而每次从一个新的起点出发进行搜索的过程中得到的顶点访问序列就是该连通分量中的顶点集合。

参考我的另一篇博文:[1]https://blog.csdn.net/m0_37357063/article/details/82053144

2.求有向图的强连通分量:(正向/逆向深度优先遍历)

求有向图的强连通分量需要对图进行两次深度优先遍历: 

  1. 第一次进行正向的深度优先遍历,并把最后完成访问的顶点(即退出DFS函数的先后顺序)的下标记录在finished数组中;
  2. 第二次利用finished数组,从finished[vexnum-1]这个最后退出DFS函数的顶点开始,对图作逆向深度优先遍历,每一次顶层调用DFS函数作逆向深度优先遍历所访问到的顶点集合就是该图的一个连通分量的顶点集合!

 

《有向图_十字链表存储结构_深度优先遍历_求有向图的强连通分量》

求有向图G的强连通分量:

从V1出发作深度优先遍历,得到finished数组中的顶点编号为{1,3,2,0};

利用finished数组,则再从顶点V1即finished[vexnum-1]出发作逆向深度优先遍历,得到两个顶点集合{V1,V3,V4}和{V2}

{V1,V3,V4}和{V2}就是有向图G 的两个强连通分量!

 

利用遍历求强连通分量的时间复杂度和遍历相同!

 

1.有向图的十字链表存储结构

十字链表(Orthogonal List):弧结点和顶点结点

《有向图_十字链表存储结构_深度优先遍历_求有向图的强连通分量》

弧结点有5个域:

  1. 尾结点域:tailvex:弧尾结点在图中的位置(下标)
  2. 头结点域:headvex: 弧头结点在图中的位置(下标)
  3. 链域:hlink指向弧头相同的下一条弧结点
  4. 链域:tlink指向弧尾相同的下一条弧结点
  5. info域:指向该条弧相关的信息

顶点结点3个域:

  1. 数据域:data:存储和顶点相关的信息
  2. 第一条出弧链域:firstin:指向该顶点的第一条出弧(即第一条以该顶点为弧尾的弧结点)
  3. 第一条入弧链域:firstout:指向该顶点的第一条入弧(即第一条以该顶点为弧头的弧结点)

《有向图_十字链表存储结构_深度优先遍历_求有向图的强连通分量》

由图可知:弧头相同的弧都在一个链表上;弧尾相同的弧也在同一个链表上。

弧结点所在的链表非循环链表,结点之间相对位置关系自然形成,不一定要按顶点序号有序。

表头结点即顶点结点,它们之间不是链接而是顺序存储。

下面先来看看《数据结构C语言版(第三版)》严的书上P165页是怎么实现又向图的十字链表的,然后再看用C++具体的实现方式。(结点的定义大致相同,但严的书上对于弧头相同的弧都在一个链表上;弧尾相同的弧也在同一个链表上。即弧结点的的这个链域不方便实现:比如,我输入V1->V2的0->1这条弧的时候,我是没办法对hlink和tlink这两个指针域进行赋值的!

#define MAX_VERTEX_NUM 20
typedef struct InfoType{
	int data;
};

typedef struct ArcBox{
	int tailvex, headvex;//弧头和弧尾的顶点位置(下标)
	struct ArcBox *hlink, *tlink;//分别为弧头相同和弧尾相同的弧的链域
	InfoType *info;//弧相关信息指针
}ArcBox;

typedef struct VexNode{
	int data;
	ArcBox *firstin, *firstout;//指向该顶点的第一条入弧和出弧
}VexNode;

typedef struct{
	VexNode xlist[MAX_VERTEX_NUM];//表头向量
	int vexnum, arcnum;//图的顶点个数和弧数
}OLGraph;

基于以上对弧结点和顶点结点的定义,创建十字链表的有向图存储结构:


/*输入n个顶点和e条弧的信息,建立有向图的十字链表存储结构!*/
bool CreateDG(OLGraph& G)
{
	scanf(&G.vexnum, &G.arcnum, &IncInfo);//IncInfo为0则各条弧不含其他信息
	for (int i = 0; i < G.vexnum; i++)//构造表头向量
	{
		scanf(&G.xlist[i].data);//输入弧的两个端点的顶点值
		G.xlist[i].firstin = NULL;//初始化指针
		G.xlist[i].firstout = NULL;
	}
	for (int k = 0; k < G.arcnum; k++)//输入各条弧的信息并构造十字链表
	{
		int v1, v2;
		scanf(&v1, &v2);//输入一条弧的起点和终点(尾、头)
		int i = LocateVex(v1);//确定v1、v2在G中的位置
		int j = LocateVex(v2);

		ArcBox* p = (ArcBox*)malloc(sizeof(ArcBox));
		//这里的赋值是有问题的,实际实现的时候不是这样实现的!
		*p = { i, j, G.xlist[j].firstin, G.xlist[i].firstout, NULL };//对弧结点赋值

		G.xlist[j].firstin = G.xlist[i].firstout = p;//完成在出弧和入弧链表头的插入
		
		if (IncInfo) Input(*p->info);//若弧含有相关信息,则输入。
	}
}//CreateDG

其中:对弧结点赋值的这条语句是有问题的:

*p = { i, j, G.xlist[j].firstin, G.xlist[i].firstout, NULL };//对弧结点赋值

第一次输入0->1这个弧结点信息时,G.xlist[j].firstin=NULL, G.xlist[i].firstout =NULL,后面也没有再对该弧结点进行修改操作!

下面是我的实现方式:

将与每个顶点相关的出弧Out和入弧In的结点指针就存储在该顶点的数据结构里!

这里对弧链表采用了向量来存储其结点指针,vector push_back 使其自然增长!直接用取下标[]访问元素,较方便,而没有采用链表list的存储形式。

 

typedef struct ArcBox{
	int tailvex, headvex;//该弧的尾头节点位置(下标)
	struct ArcBox *hlink, *tlink;	//指向弧头相同和弧尾相同的弧线的链域
	//InfoType *info;
}ArcBox;

typedef struct VexNode{
	int No;
	//ArcBox *firstin, *firstout;
	vector<ArcBox*> In;    //与该结点相关的入弧结点指针向量
	vector<ArcBox*> Out;    //与该结点相关的出弧结点指针向量
	int visited;            //结点访问标志位
}VexNode;

typedef struct{
	vector<VexNode*> headNodeVec;//表头指针向量
	int vexnum, arcnum;//顶点数量、弧数量
	vector<int> finished;    //正向深度优先遍历时,记录最后完成搜索的顶点编号的辅助向量,
                            //finished用于逆向深度优先遍历时求有向图的强连通分量!
}OLGraph;

创建有向图的十字链表存储结构时需要构造两种实体:顶点实例和弧结点实例

下面的CreateDG()函数,输入n个顶点的信息和e条弧的信息,建立该有向图的十字链表存储结构!

bool CreateDG(OLGraph& G)
{//采用十字链表存储结构,构造有向图
	cin >> G.vexnum;
	cin >> G.arcnum;
	G.finished.resize(G.vexnum);
	for (int i = 0; i < G.vexnum; i++)
	{
		VexNode* vexNodePtr = new VexNode;//构造结点实体
		G.headNodeVec.push_back(vexNodePtr);

		G.headNodeVec[i]->No = i;//顶点编号,从0计起
		G.headNodeVec[i]->visited = 0;
		//G.headNodeVec[i]->firstin = nullptr;
		//G.headNodeVec[i]->firstout = nullptr;
	}
	for (int k = 0; k < G.arcnum; k++)
	{//输入各弧的信息,并构造十字链表
		int start, end;
		cin >> start;//弧尾:起点
		cin >> end;//弧头:终点
		int i = start - 1;//tail   tail->head : i->j
		int j = end - 1;//head 结点编号下标从0计起

		ArcBox* arcBoxPtr = new ArcBox;//构造弧线结点实体
		*arcBoxPtr = { i, j };
		G.arcPtrVec.push_back(arcBoxPtr);//存一下弧结点的指针信息!
		//*arcBoxPtr = { i, j, G.headNodeVec[j]->firstout, G.headNodeVec[i]->firstin };
		////{tailvex,headvex,hlink,tlink,info}
		//G.headNodeVec[j]->firstin = G.headNodeVec[i]->firstout = arcBoxPtr;

		G.headNodeVec[i]->Out.push_back(arcBoxPtr);
		G.headNodeVec[j]->In.push_back(arcBoxPtr);

	}
	return true;
}

下面是删除十字链表存储的有向图G:

bool destoryDG(OLGraph& G)
{
	//先删除弧线结点!
	//对于每一个顶点,删除其出弧、删除其入弧,但注意不要重复删除,所以要判断指针非空不!
	//for (int v = 0; v < G.vexnum; v++)
	//{
	//	for (int j = 0; j < G.headNodeVec[v]->Out.size();j++)
	//	{ 
	//		if (G.headNodeVec[v]->Out[j] != nullptr)
	//		{
	//			delete G.headNodeVec[v]->Out[j];
	//			G.headNodeVec[v]->Out[j] = nullptr;
	//		}
	//	}
	//	for (int j = 0; j < G.headNodeVec[v]->In.size(); j++)
	//	{
	//		if (G.headNodeVec[v]->In[j] != nullptr)
	//		{
	//			delete G.headNodeVec[v]->In[j];
	//			G.headNodeVec[v]->In[j] = nullptr;
	//		}
	//	}
	//}
	/*以上方式不对!,因为每一条弧,在不同的顶点中被多个不同的指针指向了!所有不能通过判断指针为空来删除!
	还是将弧结点的指针信息统一存一下吧!*/
	//删除弧结点!
	for (int i = 0; i < G.arcnum; i++)
	{
		delete G.arcPtrVec[i];
		G.arcPtrVec[i] = nullptr;
	}
	/*还是有内存漏洞!弧结点在上面的循环中被删除了!,但是对于各个顶点内的出入弧指针向量里存从指针还没赋值nullptr!
	这期间就不能通过这些指针去访问弧结点了!
	这里埋下一个坑吧!
	C++的内存管理真是头疼!
	小程序跑跑就算了,在大的场景下,服务器端时不允许存在这些内存泄漏的!
	*/
	//再删除顶点结点
	for (int v = 0; v < G.vexnum; v++)
	{
		delete G.headNodeVec[v];//删除指针所指对象;
		G.headNodeVec[v] = nullptr;//将该指针值为空
	}
	G.finished.clear();
	return true;
}

 

2.有向图的正向(顺箭头)深度优先遍历:递归的退出:最后完成搜索的顶点

求有向图的强连通分量的步骤1

深度优先正向遍历,在退出DFS函数前得到最后完成搜索的的顶点记录辅助向量finished:

在有向图上,从某个顶点出发沿着以该顶点为尾巴的弧进行正向深度优先遍历,并按其所有邻接点的搜索都完成
(即退出DFS函数)的顺序将顶点排列起来,得到最后完成访问的顶点的排列顺序,即finished向量:{1,3,2,0}
1号顶点V2是最先完成访问并退出DFS函数的 ,其次是3号顶点V4,然后是2号顶点V3,最后是0号顶点V1完成访问并退出DFS函数的;

finished向量存储的就是最先完成访问该顶点,即在该顶点处不在继续调用DFS递归下去的顶点,即完成访问退出DFS函数的顶点。有向图G的深度优先递归访问,从V1进入DFS函数,第一个是V2继续调用DFS进行递归访问完了最先退出DFS函数的!

然后回退到V1,V1还有邻接顶点,继续用V3调用DFS,然后到V4调用DFS,完成顺序相反:V4退出DFS,然后V3退出DFS

最后是V1退出DFS;

所有finished存从优先级值为:{1,3,2,0}

而dfsTraverse向量里存了深度优先遍历的顶点下标编号{0  1  2  3}

void DFS(OLGraph& G, int v, vector<int>& dfsTraverse, vector<int>& finished)
{
	G.headNodeVec[v]->visited = 1;
	dfsTraverse.push_back(v);
	for (int w = 0; w < G.headNodeVec[v]->Out.size(); w++)
	{
		int NodeNo = G.headNodeVec[v]->Out[w]->headvex;
		if (G.headNodeVec[NodeNo]->visited == 0)
		{
			DFS(G, NodeNo, dfsTraverse, finished);
		}
	}

	finished.push_back(v);
}

void DFSTraverse(OLGraph& G, vector<int>& dfsTraverse, vector<int>& finished)
{
	for (int v = 0; v < G.vexnum; v++)
	{
		if (G.headNodeVec[v]->visited == 0)
		{
			DFS(G, v, dfsTraverse, finished);
		}
	}
}

 

3.有向图的逆向(逆箭头)深度优先遍历:求有向图的强连通分量。

 

求有向图的强连通分量的步骤2

在有向图G上,从正向深度优先遍历中最后完成访问的顶点(即finished[vexnum-1]下标的顶点)出发,沿着以该顶点为头的弧作逆向深度优先遍历,若此次遍历不能访问到图中的所有顶点,则从余下的顶点中最后完成访问的那个顶点继续再次出发,继续作深度优先遍历,以此类推,直到图中的所有顶点都访问完为止。

由此,每一个在顶层调用DFS函数作逆向深度优先遍历所访问到的顶点集合就是该有向图的一个强连通分量!

这里需要修改DFSTraverse()函数为->ReverseDFSTraverseGetStronglyConnectedComponent()函数

相应地修改DFS函数,参数重载。

void DFS(OLGraph& G, int v,vector<int>& CC)
{
	G.headNodeVec[v]->visited = 1;
	CC.push_back(v+1);
	for (int w = 0; w < G.headNodeVec[v]->In.size(); w++)
	{
		int NodeNo = G.headNodeVec[v]->In[w]->tailvex;//逆向深度优先遍历
		if (G.headNodeVec[NodeNo]->visited == 0)
		{
			DFS(G, NodeNo,CC);
		}
	}
}
void ReverseDFSTraverseGetStronglyConnectedComponent(OLGraph& G, vector<vector<int>>& SCC,vector<int> & finished)
{//根据正向遍历的finished数组再逆向遍历一边,得到强连通分量
	for (int i = finished.size() - 1; i >= 0; i--)
	{
		int v = finished[i];//v从finished[vexnum-1]到finished[0];
		if (G.headNodeVec[v]->visited == 0)
		{
			vector<int> CC;//ConnectedComponent
			//SCC.push_back(CC);//这里要是push进去的话,push 进去的是一个空向量!
//在DFS函数里的改变并不会将最终结果push到SCC里面
			DFS(G, v, CC);
			SCC.push_back(CC);
		}
	}
	//清除G的内部状态
	for (int v = 0; v < G.vexnum; v++)
	{
		//G.finished[v] = 0;
		G.headNodeVec[v]->visited = 0;
	}
}

存每个顶点结点有没有被访问过的visited标志,需要在每个完成遍历退出函数前清零一下该标志,

因为遍历函数传的是引用,所以会改变图G 的内部状态。

要是传值,就会改变其状态了,退出后各个顶点的visited标志还是0;

这里有个地方不太统一:就是记录最后完成访问的顶点下标的finished数组,要么是将其存成图的内部状态,要么令设向量,通过函数参数的方式传引用进去!

测试例子:

#include "stdafx.h"
#include<iostream>
#include<vector>

using namespace std;


int _tmain(int argc, _TCHAR* argv[])
{
	OLGraph G;
	CreateDG(G);

	vector<int> dfsTreverse;
	vector<int> finished;
	DFSTraverse(G, dfsTreverse, finished);
	cout << "深度优先遍历图G:" << endl;
	for (auto ele : dfsTreverse)
	{
		cout << ele << " ";
	}
	cout << endl;
	cout << "finished数组:" << endl;
	for (auto ele : finished)
	{
		cout << ele << " ";
	}
	cout << endl;
	/*
	深度优先遍历图G:
	0 1 2 3
	finished数组:
	1 3 2 0
	请按任意键继续. . .
	*/
	vector<vector<int>> SCC;//二维向量,用来存储强连通分量的顶点下标,从1计算,V1...
	ReverseDFSTraverseGetStronglyConnectedComponent(G, SCC, finished);
	cout << "图G的强连通分量为:" << endl;
	for (int i = 0; i < SCC.size(); i++)
	{
		cout << "第"<< i + 1 <<"个连通分量:" <<endl;
		for (int j = 0; j < SCC[i].size(); j++)
		{
			cout << SCC[i][j] << " ";
		}
		cout << endl;
	}

	destoryDG(G);
	system("pause");
	return 0;
}

输入输出:

/*
有向图G:
V1->V2
V1->V3
V3->V1
V3->V4
V4->V1
V4->V3
V4->V2

G有两个强连通分量:{V1,V3,V4},{V2}

输入输出:
4 7
1 2
1 3
3 1
3 4
4 1
4 2
4 3
深度优先遍历图G:
1 2 3 4
finished数组:
1 3 2 0
图G的强连通分量为:
第1个连通分量:
1 3 4
第2个连通分量:
2
请按任意键继续. . .
*/

 

 

 

完整代码如下:

// OLGraph.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include<iostream>
#include<vector>

using namespace std;

typedef struct ArcBox{
	int tailvex, headvex;//该弧的尾头节点位置(下标)
	struct ArcBox *hlink, *tlink;	//指向弧头相同和弧尾相同的弧线的链域
	//InfoType *info;
}ArcBox;

typedef struct VexNode{
	int No;
	//ArcBox *firstin, *firstout;
	vector<ArcBox*> In; //与该结点相关的入弧结点指针向量
	vector<ArcBox*> Out;	//与该结点相关的出弧结点指针向量
	int visited;	//结点访问标志位
}VexNode;

typedef struct{
	vector<VexNode*> headNodeVec;//表头指针向量
	vector<ArcBox*> arcPtrVec;
	int vexnum, arcnum;//顶点数量、弧数量
	vector<int> finished;//正向深度优先遍历时,记录最后完成搜索的顶点编号的辅助向量
	//finished用于逆向深度优先遍历时求有向图的强连通分量!
}OLGraph;

bool CreateDG(OLGraph& G)
{//采用十字链表存储结构,构造有向图
	cin >> G.vexnum;
	cin >> G.arcnum;
	G.finished.resize(G.vexnum);
	for (int i = 0; i < G.vexnum; i++)
	{
		VexNode* vexNodePtr = new VexNode;//构造结点实体
		G.headNodeVec.push_back(vexNodePtr);

		G.headNodeVec[i]->No = i;//顶点编号,从0计起
		G.headNodeVec[i]->visited = 0;
		//G.headNodeVec[i]->firstin = nullptr;
		//G.headNodeVec[i]->firstout = nullptr;
	}
	for (int k = 0; k < G.arcnum; k++)
	{//输入各弧的信息,并构造十字链表
		int start, end;
		cin >> start;//弧尾:起点
		cin >> end;//弧头:终点
		int i = start - 1;//tail   tail->head : i->j
		int j = end - 1;//head 结点编号下标从0计起

		ArcBox* arcBoxPtr = new ArcBox;//构造弧线结点实体
		*arcBoxPtr = { i, j };
		G.arcPtrVec.push_back(arcBoxPtr);//存一下弧结点的指针信息!
		//*arcBoxPtr = { i, j, G.headNodeVec[j]->firstout, G.headNodeVec[i]->firstin };
		////{tailvex,headvex,hlink,tlink,info}
		//G.headNodeVec[j]->firstin = G.headNodeVec[i]->firstout = arcBoxPtr;

		G.headNodeVec[i]->Out.push_back(arcBoxPtr);
		G.headNodeVec[j]->In.push_back(arcBoxPtr);

	}
	return true;
}


bool destoryDG(OLGraph& G)
{
	//先删除弧线结点!
	//对于每一个顶点,删除其出弧、删除其入弧,但注意不要重复删除,所以要判断指针非空不!
	//for (int v = 0; v < G.vexnum; v++)
	//{
	//	for (int j = 0; j < G.headNodeVec[v]->Out.size();j++)
	//	{ 
	//		if (G.headNodeVec[v]->Out[j] != nullptr)
	//		{
	//			delete G.headNodeVec[v]->Out[j];
	//			G.headNodeVec[v]->Out[j] = nullptr;
	//		}
	//	}
	//	for (int j = 0; j < G.headNodeVec[v]->In.size(); j++)
	//	{
	//		if (G.headNodeVec[v]->In[j] != nullptr)
	//		{
	//			delete G.headNodeVec[v]->In[j];
	//			G.headNodeVec[v]->In[j] = nullptr;
	//		}
	//	}
	//}
	/*以上方式不对!,因为每一条弧,在不同的顶点中被多个不同的指针指向了!所有不能通过判断指针为空来删除!
	还是将弧结点的指针信息统一存一下吧!*/
	//删除弧结点!
	for (int i = 0; i < G.arcnum; i++)
	{
		delete G.arcPtrVec[i];
		G.arcPtrVec[i] = nullptr;
	}
	/*还是有内存漏洞!弧结点在上面的循环中被删除了!,但是对于各个顶点内的出入弧指针向量里存从指针还没赋值nullptr!
	这期间就不能通过这些指针去访问弧结点了!
	这里埋下一个坑吧!
	C++的内存管理真是头疼!
	小程序跑跑就算了,在大的场景下,服务器端时不允许存在这些内存泄漏的!
	*/
	//再删除顶点结点
	for (int v = 0; v < G.vexnum; v++)
	{
		delete G.headNodeVec[v];//删除指针所指对象;
		G.headNodeVec[v] = nullptr;//将该指针值为空
	}
	G.finished.clear();
	return true;
}

void func(OLGraph G, int v)
{
	cout << G.headNodeVec[v]->No +1<< " ";
}



void DFS(OLGraph& G, int v, void(*Visit)(OLGraph G, int v),int& count)
{
	G.headNodeVec[v]->visited = 1;
	Visit(G, v);
	for (int w = 0; w < G.headNodeVec[v]->Out.size(); w++)
	{
		int NodeNo = G.headNodeVec[v]->Out[w]->headvex;
		if (G.headNodeVec[NodeNo]->visited == 0)
		{
			DFS(G, NodeNo, Visit,count);
		}
	}

	G.finished[count++] = v;
}


/*
在有向图上,从某个顶点出发沿着以该顶点为尾巴的弧进行正向深度优先遍历,并按其所有邻接点的搜索都完成
(即退出DFS函数)的顺序将顶点排列起来,得到最后完成访问的顶点的排列顺序,即finished向量:{1,3,2,0}
1号顶点V2是最先完成访问并退出DFS函数的 ,其次是3号顶点V4,然后是2号顶点V3,最后是0号顶点V1完成访问并退出DFS函数的;


有向图G:
V1->V2
V1->V3
V3->V1
V3->V4
V4->V1
V4->V3
V4->V2

G有两个强连通分量:{V1,V3,V4},{V2}

输入输出:
4 7
1 2
1 3
3 1
3 4
4 1
4 2
4 3
深度优先遍历图G:
1 2 3 4
finished数组:
1 3 2 0
图G的强连通分量为:
第1个连通分量:
1 3 4
第2个连通分量:
2

*/
void DFS(OLGraph& G, int v, vector<int>& dfsTraverse, vector<int>& finished)
{
	G.headNodeVec[v]->visited = 1;
	dfsTraverse.push_back(v);
	for (int w = 0; w < G.headNodeVec[v]->Out.size(); w++)
	{
		int NodeNo = G.headNodeVec[v]->Out[w]->headvex;
		if (G.headNodeVec[NodeNo]->visited == 0)
		{
			DFS(G, NodeNo, dfsTraverse, finished);
		}
	}

	finished.push_back(v);
}


void DFSTraverse(OLGraph& G, vector<int>& dfsTraverse, vector<int>& finished)
{
	for (int v = 0; v < G.vexnum; v++)
	{
		if (G.headNodeVec[v]->visited == 0)
		{
			DFS(G, v, dfsTraverse, finished);
		}
	}
	for (int i = 0; i < G.vexnum; i++)
	{
		G.headNodeVec[i]->visited = 0;
	}
}



void DFS(OLGraph& G, int v,vector<int>& CC)
{
	G.headNodeVec[v]->visited = 1;
	CC.push_back(v+1);
	for (int w = 0; w < G.headNodeVec[v]->In.size(); w++)
	{
		int NodeNo = G.headNodeVec[v]->In[w]->tailvex;//逆向深度优先遍历
		if (G.headNodeVec[NodeNo]->visited == 0)
		{
			DFS(G, NodeNo,CC);
		}
	}
}


void DFSTraverse(OLGraph& G, void(*Visit)(OLGraph G, int v))
{
	int count = 0;
	for (int v = 0; v < G.vexnum; v++)
	{
		if (G.headNodeVec[v]->visited == 0)
		{
			DFS(G, v,Visit,count);
		}
	}
	cout << endl;
	cout << "finished数组:"<<endl;
	for (auto ele : G.finished)
	{
		cout << ele << " ";
	}
	cout << endl;

	//清除G的内部状态
	for (int v = 0; v < G.vexnum; v++)
	{
		//G.finished[v] = 0;//这里的finished状态不要清,下面求强连通分量时候会用到。
		G.headNodeVec[v]->visited = 0;
	}
}

void ReverseDFSTraverseGetStronglyConnectedComponent(OLGraph& G, vector<vector<int>>& SCC)
{//根据正向遍历的finished数组再逆向遍历一边,得到强连通分量
	for (int i = G.finished.size()-1; i>=0; i--)
	{
		int v = G.finished[i];//v从finished[vexnum-1]到finished[0];
		if (G.headNodeVec[v]->visited == 0)
		{
			vector<int> CC;//ConnectedComponent
			//SCC.push_back(CC);//这里要是push进去的话,push 进去的是一个空向量!在DFS函数里的改变并不会将最终结果push到SCC里面
			DFS(G, v,CC);
			SCC.push_back(CC);
		}
	}

	//清除G的内部状态
	for (int v = 0; v < G.vexnum; v++)
	{
		G.finished[v] = 0;
		G.headNodeVec[v]->visited = 0;
	}
}


void ReverseDFSTraverseGetStronglyConnectedComponent(OLGraph& G, vector<vector<int>>& SCC,vector<int> & finished)
{//根据正向遍历的finished数组再逆向遍历一边,得到强连通分量
	for (int i = finished.size() - 1; i >= 0; i--)
	{
		int v = finished[i];//v从finished[vexnum-1]到finished[0];
		if (G.headNodeVec[v]->visited == 0)
		{
			vector<int> CC;//ConnectedComponent
			//SCC.push_back(CC);//这里要是push进去的话,push 进去的是一个空向量!在DFS函数里的改变并不会将最终结果push到SCC里面
			DFS(G, v, CC);
			SCC.push_back(CC);
		}
	}
	//清除G的内部状态
	for (int v = 0; v < G.vexnum; v++)
	{
		//G.finished[v] = 0;
		G.headNodeVec[v]->visited = 0;
	}
}





int _tmain(int argc, _TCHAR* argv[])
{
	OLGraph G;
	CreateDG(G);
	//深度优先遍历图G
	//cout << "深度优先遍历图G:" << endl;
	//DFSTraverse(G, func);
	//vector<vector<int>> SCC;//二维向量,用来存储强连通分量的顶点下标,从1计算,V1...
	//ReverseDFSTraverseGetStronglyConnectedComponent(G, SCC);

	//cout << "图G的强连通分量为:" << endl;
	//for (int i = 0; i < SCC.size(); i++)
	//{
	//	cout << "第"<< i + 1 <<"个连通分量:" <<endl;
	//	for (int j = 0; j < SCC[i].size(); j++)
	//	{
	//		cout << SCC[i][j] << " ";
	//	}
	//	cout << endl;
	//}

	vector<int> dfsTreverse;
	vector<int> finished;
	DFSTraverse(G, dfsTreverse, finished);
	cout << "深度优先遍历图G:" << endl;
	for (auto ele : dfsTreverse)
	{
		cout << ele << " ";
	}
	cout << endl;
	cout << "finished数组:" << endl;
	for (auto ele : finished)
	{
		cout << ele << " ";
	}
	cout << endl;
	/*
	深度优先遍历图G:
	0 1 2 3
	finished数组:
	1 3 2 0
	请按任意键继续. . .
	*/


	vector<vector<int>> SCC;//二维向量,用来存储强连通分量的顶点下标,从1计算,V1...
	ReverseDFSTraverseGetStronglyConnectedComponent(G, SCC, finished);

	cout << "图G的强连通分量为:" << endl;
	for (int i = 0; i < SCC.size(); i++)
	{
		cout << "第"<< i + 1 <<"个连通分量:" <<endl;
		for (int j = 0; j < SCC[i].size(); j++)
		{
			cout << SCC[i][j] << " ";
		}
		cout << endl;
	}


	//for (auto ele : G.finished)
	//{
	//	cout << ele << " ";
	//}
	//cout << endl;

	destoryDG(G);

	system("pause");
	return 0;
}



/*
有向图G:
V1->V2
V1->V3
V3->V1
V3->V4
V4->V1
V4->V3
V4->V2

G有两个强连通分量:{V1,V3,V4},{V2}

输入输出:
4 7
1 2
1 3
3 1
3 4
4 1
4 2
4 3
深度优先遍历图G:
1 2 3 4
finished数组:
1 3 2 0
图G的强连通分量为:
第1个连通分量:
1 3 4
第2个连通分量:
2
请按任意键继续. . .
*/




//#define MAX_VERTEX_NUM 20
//typedef struct InfoType{
//	int data;
//};
//
//typedef struct ArcBox{
//	int tailvex, headvex;//弧头和弧尾的顶点位置(下标)
//	struct ArcBox *hlink, *tlink;//分别为弧头相同和弧尾相同的弧的链域
//	InfoType *info;//弧相关信息指针
//}ArcBox;
//
//typedef struct VexNode{
//	int data;
//	ArcBox *firstin, *firstout;//指向该顶点的第一条入弧和出弧
//}VexNode;
//
//typedef struct{
//	VexNode xlist[MAX_VERTEX_NUM];//表头向量
//	int vexnum, arcnum;//图的顶点个数和弧数
//}OLGraph;
//
///*输入n个顶点和e条弧的信息,建立有向图的十字链表存储结构!*/
//bool CreateDG(OLGraph& G)
//{
//	scanf(&G.vexnum, &G.arcnum, &IncInfo);//IncInfo为0则各条弧不含其他信息
//	for (int i = 0; i < G.vexnum; i++)//构造表头向量
//	{
//		scanf(&G.xlist[i].data);//输入弧的两个端点的顶点值
//		G.xlist[i].firstin = NULL;//初始化指针
//		G.xlist[i].firstout = NULL;
//	}
//	for (int k = 0; k < G.arcnum; k++)//输入各条弧的信息并构造十字链表
//	{
//		int v1, v2;
//		scanf(&v1, &v2);//输入一条弧的起点和终点(尾、头)
//		int i = LocateVex(v1);//确定v1、v2在G中的位置
//		int j = LocateVex(v2);
//
//		ArcBox* p = (ArcBox*)malloc(sizeof(ArcBox));
//		//这里的赋值是有问题的,实际实现的时候不是这样实现的!
//		*p = { i, j, G.xlist[j].firstin, G.xlist[i].firstout, NULL };//对弧结点赋值
//
//		G.xlist[j].firstin = G.xlist[i].firstout = p;//完成在出弧和入弧链表头的插入
//		
//		if (IncInfo) Input(*p->info);//若弧含有相关信息,则输入。
//	}
//}//CreateDG

 

目录

0.小结 基于遍历地求无向图的连通分量和求有向图的强连通分量

1.有向图的十字链表存储结构

2.有向图的正向(顺箭头)深度优先遍历:递归的退出:最后完成搜索的顶点

求有向图的强连通分量的步骤1:

3.有向图的逆向(逆箭头)深度优先遍历:求有向图的强连通分量。

求有向图的强连通分量的步骤2:

数据结构 图

    原文作者:数据结构之图
    原文地址: https://blog.csdn.net/m0_37357063/article/details/82112408
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞