图论的C++实现

首先,我们先看一下什么是图,图(Graph)是由顶点的有穷非空集合和顶点之间边的集合组成,通常表示为G(V,E),G表示一个图,V是图G中顶点的集合,E是图G中边的集合。

一、相关术语

顶点(Vertex):在图中的数据元素。
边:图中,任意两顶点之间都可能有关系,顶点之间的逻辑关系用边来表示。
无向边(Edge):若顶点Vi到Vj之间的边没有方向,则称这条边为无向边,用无序偶对(Vi,Vj)来表示。
无向边:若从顶点Vi到Vj的边有方向,则称这条边为有向边,也称为弧(Arc)。记作<Vi,Vj>,比如连接顶点A到D的有向边就是弧,A是弧尾,D是弧头,<A,D>表示弧,因为他是有方向的,所以不能写成<D,A>。
无向完全图:在无向图中,如果任意两个顶点之间都存在边,则称该图为无向完全图。
有向完全图:在有向图中,如果任意两个顶点之间都存在方向上互为相反的两条弧,则称该图为有向完全图。
有很少条边或弧的图称为稀疏图,反之称为稠密图。
权(Weight):与图的边或弧相关的数叫做权。
网(Network):带权的图通常称为网。
连通图:在无向图中,如果从一个顶点到另外一个顶点有路径,则称两个顶点之间是连通的。如果对于图中任意两个顶点之间哦都是连通的,则称图为连通图。

二、图的基本实现

ADT  Graph
Data
   顶点的有穷非空集合和边的集合
Operation
   CreateGraph(*G,V,VR):按照顶点集V和边弧VR的定义构造图G。
   DestoryGraph(*G):图G若存在则销毁。
   LocateVex(G,char u):图中存在顶点u,则返回图中的位置。
   GetVex(G,int v):返回图中顶点v的值。
   PutVex(G,int v,char value):将图中顶点v赋值value。
   FirstAdjVex(G,char v):返回顶点v的一个邻接顶点,若顶点在图中无邻接顶点,返回空。
   NextAdjVex(G,char v,char w):返回顶点v相对于顶点w的下一个邻接顶点,若w是v的最有一个邻接点,则返回“空”。
   InsertVex(G,char v):在图中增加新顶点v。
   DeleteVex(G,char v):删除图中顶点v以及相关的弧。
   InsertArc(G,char v,char w):在图中增加弧<v,w>,若图为无向图,还需要增添对称弧<w,v>DeleteArc(G,char v,char w):在图中删除弧<v,w>,若图为无向图,还要删除对称弧<w,v>DFSTraverse(G):对图中进行深度优先遍历,在遍历过程中对每个顶点调用。
   BFSTraverse(G):对图中进行广度优先遍历,在遍历过程中对每个顶点调用。 

endADT

三、代码实现

接下来就以无向图以及邻接矩阵为例,进行图的相关代码的实现。

1、数据的相关定义以及初始化的过程

#include<iostream>
#include<queue>
using namespace std;

#define Maxvex 100     //记录图里面的最大顶点数 
#define Infinity 65535  //用65535来表示 ∞ 
typedef struct
{
	char Vexs[Maxvex];   //VExs数组记录顶点,为顶点表 
	int arc[Maxvex][Maxvex];   //邻接矩阵,记录顶点之间的边 
	int Vertexes,Edges;     //Vertexes记录顶点数,Edges记录边的数量 
}MGraph;

MGraph g;    //定义图的全局变量

这里我们的MGraph是一个全局变量,所以在后面的函数中就省略了形参里面G的定义。

2、图的创建

void CreateGraph()   //创建图,建立无向表,由于已经定义了全局变量,所以就不需要写形参。
{
	int i,j,k,w;
	cin>>g.Vertexes>>g.Edges;  //首先输入顶点数和边数
	for(i=0;i<g.Vertexes;i++)  cin>>g.Vexs[i];
	for(i=0;i<g.Vertexes;i++)
	   for(j=0;j<g.Vertexes;j++)
	      g.arc[i][j]=Infinity;              //初始化
	for(k=0;k<g.Edges;k++)
	{
		cin>>i>>j>>w;
		g.arc[i][j]=w;         //w表示权重 
		g.arc[j][i]=w;         //无向图,矩阵对称 
	}

3、图的顶点以及弧的相关操作

由于这些操作比较简单,这里就直接给出代码。

void DestoryGraph(MGraph g)   //销毁图,若有图g,则销毁。 
{
	bool flag=false;
	for(int i=0;i<g.Vertexes;i++)
	   for(int j=0;j<g.Vertexes;j++)
	     if(g.arc[i][j]!=Infinity)
	     {
	     	g.arc[i][j]=Infinity;
	     	flag=true;
		 }
	if(flag)            //如果邻接矩阵有边,则说明这是一个图,就需要把他的顶点全部删除。如果邻接矩阵没有边,则不需要删除顶点。 
	  for(int i=0;i<g.Vertexes;i++)
	     g.Vexs[i]=0;
 } 

int LocateVex(char u)   //若图中存在顶点u,则返回图中的位置 
{
	bool flag=false;
	int i;
	for(i=0;i<g.Vertexes;i++)
	   if(g.Vexs[i]==u) 
	   {
	   	flag=true;
	   	break;
	   }
	if(flag) return i;
	else return 0 ;
 } 

char GetVex(int v)   //返回图中顶点v的值 
{
	return g.Vexs[v];
 } 

void PutVex(int v,char value)    //将图中顶点v赋值value 
{
	g.Vexs[v]=value;
 } 

char FirstAdjVex(char v)  //返回顶点v的一个邻接顶点,若顶点在图中无邻接顶点,返回空 
{	
	int i,j;
	bool flag=false;
	for(i=0;i<g.Vertexes;i++) if(g.Vexs[i]==v) break;
	for(j=0;j<g.Vertexes;j++)
	   if(g.arc[i][j]!=Infinity) 
	   {
	   	flag=true;
	   	break;
	   }
	if(flag) return g.Vexs[j];
	else return 0;
 } 

char NextAdjVex(char v,char w)  //返回顶点v相对于顶点w的下一个邻接顶点,若w是v的最有一个邻接点,则返回“空”。 
{
	int j,i,k;
	bool flag=false;
	for(i=0;i<g.Vertexes;i++) if(g.Vexs[i]==v) break;
	for(j=0;j<g.Vertexes;j++) if(g.Vexs[j]==w) break;
	for(k=0;k<g.Vertexes;k++)
	   if(g.arc[i][k]!=Infinity&&k!=j)
	   {
	   	return k;
	   	break;
	   }
 } 

void InsertVex(char v)   //在图中增加新顶点v
{
	g.Vexs[g.Vertexes]=v;
	for(int i=0;i<g.Vertexes;i++)
		g.arc[g.Vertexes][i]=g.arc[i][g.Vertexes]=Infinity;
	g.Vertexes++;
 } 

void DeleteVex(char v) //删除图中顶点v以及相关的弧
{
	int i;
	for(i=0;i<g.Vertexes;i++) if(g.Vexs[i]==v) break;
	for(int j=0;j<g.Vertexes;j++)
	   g.arc[j][i]=g.arc[i][j]=Infinity;
	for(int j=i;j<g.Vertexes-1;j++)
	   g.Vexs[j]=g.Vexs[j+1];
	g.Vertexes--;
 } 

void InsertArc(char v,char w)    //在图中增加弧<v,w>,若图为无向图,还需要增添对称弧<w,v> 
{
	int i,j;
	for(i=0;i<g.Vertexes;i++) if(g.Vexs[i]==v) break;
	for(j=0;j<g.Vertexes;j++) if(g.Vexs[j]==w) break;
	g.arc[i][j]=g.arc[j][i]=1;
}

void DeleteArc(char v,char w)  //在图中删除弧<v,w>,若图为无向图,还要删除对称弧<w,v>
{
	int i,j;
	for(i=0;i<g.Vertexes;i++) if(g.Vexs[i]==v) break;
	for(j=0;j<g.Vertexes;j++) if(g.Vexs[j]==w) break;
	g.arc[i][j]=g.arc[j][i]=Infinity;
 } 

4、图的深搜遍历

要对图进行深度优先搜索,首先要先知道,深度优先搜索是什么,那深度优先搜素是什么呢?
如图所示,假设你需要完成一个任务假设,要求你在如图这样的一个迷宫中,从顶点A开始要走遍所有的图顶点并作上标记,注意不是简单地看着这样的平面图走哦,而是如同现实般地在只有高墙和通道的迷宫中去完成任务。
很显然我们是需要策略的,否则在这四通八达的通道中乱窜,要想完成任务那就只能是碰运气。如果你学过深度优先遍历,这个任务就不难完成了。
首先我们从顶点A开始,做上表示走过的记号后,面前有两条路,通向B和F,我们给自己定一个原则,在没有碰到重复顶点的情况下,始终是向右手边走,于是走到了B顶点。整个行路过程,可参看右图。此时发现有三条分支,分别通向顶点C、I、G,右手通行原则,使得我们走到了C顶点。就这样,我们一直顺着右手通道走,一直走到F顶点。当我们依然选择右手通道走过去后,发现走回到顶点A了,因为在这里做了记号表示已经走过。此时我们退回到顶点F,走向从右数的第二条通道,到了G顶点,它有三条通道,发现B和D都已经是走过的,于是走到H,当我们面对通向H的两条通道D和E时,会发现都已经走过了。
此时我们是否已经遍历了所有顶点呢?没有。可能还有很多分支的顶点我们没有走到,所以我们按原路返回。在顶点H处,再无通道没走过,返回到G,也无未走过通道,返回到F,没有通道,返回到E,有一条通道通往H的通道,验证后也是走过的,再返回到顶点D,此时还有三条道未走过,一条条来,H走过了,G走过了,I,哦,这是一个新顶点,没有标记,赶快记下来。继续返回,直到返回顶点A,确认你已经完成遍历任务,找到了所有的9个顶点。
反应快的同学一定会感觉到,深度优先遍历其实就是一个递归的过程,如果再敏感一些,会发现其实转换成如右图后,就像是一棵树的前序遍历,没错,它就是。它从图中某个顶点v出发,访问此顶点,然后从v的未被访问的邻接点出发深度优先遍历图,直至图中所有和v有路径相通的顶点都被访问到。事实上,我们这里讲到的是连通图,对于非连通图,只需要对它的连通分量分别进行深度优先遍历,即在先前一个顶点进行一次深度优先遍历后,若图中尚有顶点未被访问,则另选图中一个未曾被访问的顶点作起始点,重复上述过程,直至图中所有顶点都被访问到为止。
在这里插入图片描述

void DFS(int i)
{
	isvisited[i]=true;
	cout<<g.Vexs[i]<<endl;
	for(int j=0;j<g.Vertexes;j++)
	  if(g.arc[i][j]!=Infinity&&!isvisited[j])
	       DFS(j);
 } 

void DFSTraverse()   //对图中进行深度优先遍历,在遍历过程中对每个顶点调用
{
	cout<<"DFS:"<<endl;
	for(int i=0;i<g.Vertexes;i++)
	   isvisited[i]=false;
	for(int i=0;i<g.Vertexes;i++)
	   if(!isvisited[i])
	      DFS(i);
 } 

5、广度优先遍历

那什么又是广度优先遍历呢?
如果说图的深度优先遍历类似树的前序遍历,那么图的广度优先遍历就类似于树的层序遍历了。我们将图7-5-3的第一幅图稍微变形,变形原则是顶点A放置在最上第一层,让与它有边的顶点B、F为第二层,再让与B和F有边的顶点C、I、G、E为第三层,再将这四个顶点有边的D、H放在第四层,如第二幅图所示。此时在视觉上感觉图的形状发生了变化,其实顶点和边的关系还是完全相同的。
在这里插入图片描述
那么话不多说,已经理解了原理以后我们就可以将代码写出来。

void BFSTraverse()   //对图中进行广度优先遍历,在遍历过程中对每个顶点调用
{
	queue<int>q;
	bool visited[g.Vertexes];
	cout<<endl<<endl<<"BFS:"<<endl;
	for(int i=0;i<g.Vertexes;i++) visited[i]=false;
	for(int i=0;i<g.Vertexes;i++)
	{
		if(!visited[i])
		{
			visited[i]=true;
			cout<<g.Vexs[i]<<endl;
			q.push(i);
			while(q.empty()==false)
			{
				i=q.front();
				q.pop();
				for(int j=0;j<g.Vertexes;j++)
				{
					if(g.arc[i][j]!=Infinity&&!visited[j])
					{
						visited[j]=true;
						cout<<g.Vexs[j]<<endl;
						q.push(j);
					}
				}
			}
		}
	 } 
 } 

以上是我们关于无向图的内容的整理,其中也用到了数学上的邻接矩阵。
还有下面的一组数据,用做测试。
9 15
ABCDEFGHI
0 1 1
1 2 1
2 3 1
3 4 1
4 5 1
5 8 1
8 7 1
7 3 1
7 4 1
3 6 1
2 6 1
1 6 1
0 5 1
3 8 1
1 8 1

由于是第一次写博客,多有缺陷以及不足,请多多包含。希望各位大佬给我这个菜菜的码农一点包容,有什么问题也请大家指正。

  • 5
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值