数据结构——图的基本概念及其存储结构和遍历的实现(用C++语言实现)

1.介绍:

图是一种比线性表和树更为复杂的数据结构。图的应用极为广泛,已渗入诸如物理、化学、通信、计算机,以及数学等领域。图反映的是数据之间一对多的关系。

2.定义:

定义

(Graph)G由两个集合V和E组成,记为G=(V,E),其中V是顶点的有穷非空集合,E是V中顶点偶对的有穷集合,这些顶点偶对称为边。V(G)和E(G)通常分别表示图G的顶点集合和边集合,E(G)可以为空集。若E(G)为空,则图G只有顶点而没有边。
对于图G,若边集E(G)为有向边的集合,则称该图为有向图;若边集E(G)为无向边的集合,则称该图为无向图。

有向图

在有向图中,顶点对<x,y>是有序的,它称为从顶点x到顶点y的一条有向边。因此,<x,y>与<y,x>是不同的两条边。

无向图

在无向图中,顶点对(x,y)是无序的,它称为与顶点x和顶点y相关联的一条边。这条边没有特定的方向,(x,y)与(y,x)是同一条边。

为了有别于有向图,否向图的顶点对用对圆括写括起来。

3.基本术语

子图:

假设有两个图G=(V,E)和G1=(V1,E1),如果V1包含于V,E1包含于E,则称G1为G的子图。

完全图:

任意两个顶点都有一条边相连。(指的是无向图)
无向完全图和有向完全图:对于无向图,若具有n(n-1)/2条边,则称为无向完全图;对于有向图,若具有n(n-1)条弧,则称为有向完全图。

稀疏图和稠密图:

有很少条边或弧(如e<nlogn)的图称为稀疏图,反之称为稠密图

权和网:

在实际应用中,每条边可以标上具有某种含义的数值,该数值称为该边上的权,这些权可以表示从一个顶点到另一个顶点的距离或耗费。这种带权的图通常称为网。

邻接点:

对于无向图G,如果图的边(v, v1)E,则称顶点v和v1互为邻接点,即v和v1相邻接。关联(依附):边/弧与顶点之间的关系;边(v, v’)依附于顶点v和v1,或者说边(v, v1)与顶点v和v1相关联。

度:

与顶点相关联的边的数目,记为TD(v);在有向图中,顶点的度等于该顶点的入度与出度之和,顶点v的入度是以v为终点的有向边的条数,记作ID(v),顶点的出度是以v为始点的有向边的条数,记作OD(v)。

路径和路径长度:

路径:接续的边构成的顶点序列

路径长度:路径上边或弧的数目/权值之和

回路(环):

第一个顶点和最后一个顶点相同的路径

简单路径和简单回路(简单环):

简单路径:除路径起点和终点可以相同外,其余顶点均不相同的路径

简单回路(简单环):除路径起点和终点相同外,其余顶点均不相同的路径。

连通,连通图,连通分量:

连通:两个顶点之间有路径,则称这两个顶点是连通的。

连通图:对于图中任意两个顶点都是连通的,则称该图是连通图

连通分量:指的是无向图中的极大连通子图(极大连通子图意思为该子图是G的连通子图,将G的任何不在该子图中的顶点加入,子图不再连通)

强连通图和强连通分量:

强连通图:在有向图中对于任意两个顶点是连通的,则称该图是强连通图。

强连通分量:有向图的极大强连通子图

连通图的生成树:

包含无向图所有顶点的极小连通子图

有向树和生成森林

有向树:有一个顶点的入度为0,其余顶点的入度均为1的有向图称为有向树

生成森林:对于非连通图,由各个连通分量的生成树的集合

4.案例引入:

六度空间理论:你和任何一个陌生人之间所间隔的人不会超过6个,也就是说,最多通过6个中间人你就可以认识任何一个陌生人。

5.存储结构:

邻接矩阵表示法:

定义

邻接矩阵是表示顶点之间相邻关系的矩阵
通过对A[ i ][ j ]赋值为权值(网)或为 1 表示顶点 i 和 j 之间有边
若没有边则赋值为MAX_INT(网)或为 0。

图的邻接矩阵存储表示代码实现:
//----------------------------图的邻接矩阵存储表示---------------------------------
#define MaxInt 32767			//表示极大值
#define MVNum 100				//最大顶点数
typedef char VerTexType;		//假设顶点的数据结构类型为字符型
typedef int ArcType;			//假设边的权值类型为整型

typedef struct
{
	VerTexType vex[MVNum];		//顶点表
	ArcType arcs[MVNum][MVNum]; //邻接矩阵
	int vexnum, arcnum;			//顶点数和边数
}AMgraph;
采用邻接矩阵表示法创建无向网
//-------------------------采用邻接矩阵表示法创建无向网-----------------------------
// 若图G中存在顶点u,则返回该顶点在图中的位置;否则返回其它信息;
int LocateVex(OLGraph G, VertexType u) {
	int i;
	for (i = 0; i < G.vexnum; ++i)
		if (u == G.xList[i].data)
			return i;
	return -1;
}
void CreateUDN(AMgraph& G)
{
	cin >> G.vexnum >> G.arcnum;		//输入总顶点和总边数
	for (int i=0;i<G.vexnum;++i)	
		cin >> G.vex[i];				//输入顶点信息
	for (int i = 0; i < G.arcnum; i++)
		for (int j = 0; j < G.arcnum; j++)
			G.arcs[i][j] = MaxInt;		//初始化边的权值,均设置为极大值Maxint
	for (int k = 0; k < G.arcnum; k++)	//构造邻接矩阵
	{
		int v1, v2,w;			
		cin >> v1 >> v2 >> w;			//输入一条边依附的顶点及权值
		int i = LocateVex(G, v1);		//确定v1,v2的下标
		int j = LocateVex(G, v2);
		G.arcs[i][j] = G.arcs[j][i]= w; //置<v1,v2>及其对称边的权值为w
	}
}

邻接表示表示法

定义

邻接表(Adjacency List)是图的一种链式存储结构。在邻接表中,对图中每个顶点 Vi 建立一个单链表,把与v相邻接的顶点放在这个链表中。邻接表中每个单链表的第一个结点存放有郑点的信息,把这一结点看成链表的表头,其余结点存放有关边的信息,这样邻接表便由两部分组成:表头结点表和边表。
(1)表头结点表:由所有表头结点以顺序结构的形式存储,以便可以随机访问任一顶点边链表。表头结点包括数据域(data)和链域(frstarc)两部分。其中,数据域用于存储顶点u的名称或其他有关信息;链域用于指向链表中第一个结点(与顶点v邻粮第一个邻接点)。
(2)边表:由表示图中顶点间关系的n个边链表组成。边链表中边结点包括邻接(adjvex)、数据域(info)和链域(nextarc)3个部分,其中,邻接指示与顶点v邻接的点在图中的位置;数据域存储和边相关的信息,如权值等;链域指示与v邻接的下一条边的结点。

图的邻接表存储表示代码实现:
//-----------------------------图的邻接表存储表示----------------------------------
#define MVNum 100
typedef char VerTexType;
typedef struct ArcNode      //边界点
{
	int adjvex;				//该边指向的顶点的位置(下标)
	struct ArcNode* nextarc;//指向下一条边的指针
//	Otherinfo info;         //其他信息(这里省去)
}ArcNode;
typedef struct VNode		//顶点节点
{
	VerTexType data;		//顶点信息(char类型)
	ArcNode* firstarc;		//指向顶点的第一条边
}VNode,AdjList[MVNum];		//AdjList表示邻接链表类型
typedef struct
{
	AdjList vertice;		
	int vexnum, arcnum;		//图的当前顶点数和边数
}ALGraph;
采用邻接表表示法创建无向网
//-------------------------采用邻接表表示法创建无向网-----------------------------
void CreateUDG(ALGraph& G)			//用邻接表创建无向图
{
	cin >> G.vexnum >> G.arcnum;	//输入总顶点数,总边数
	for (int i = 0; i < G.vexnum; i++)
	{
		cin >> G.vertice[i].data;	//输入顶点信息
		G.vertice[i].firstarc = NULL;//初始化
	}
	for (int k = 0; k < G.arcnum; k++)
	{
		char v1, v2;				//输入边的两个顶点
		cin >> v1 >> v2;
		int i, j;
		i = LocateVex(G, v1);		//定位v1,v2的下标
		j = LocateVex(G, v2);
									//将边处理
		G.vertice[i].firstarc = new ArcNode{j, G.vertice[i].firstarc};
		G.vertice[j].firstarc = new ArcNode{i, G.vertice[j].firstarc};		
	}
}

十字链表

定义

十字链表(Orthogonal List)是有向图的另一种链式存储结构,可以看成将有向图的邻接表和逆邻接表结合起来得到的一种链表。
在弧结点中有5个城:其中尾域(tailvex)和头域(headvex)分别指本弧尾孤头这两个顶点在图中的位置,链域hlink指向弧头相同的不条弧、而链域tlink指向远尾相同的下一条弧,info域指向该弧的相关信息。弧头相同的弧在同一链表上,弧尾相同的弧也在同一链表上它们的头结点即顶点结点,由3个域组成:其中data存储和顶点相关的信息,如顶点的名称等;firstin和firstout为两个链域,分别指向以该顶点为弧头或弧尾的第一个弧结点。

代码
//---------------------------有向图的十字链表存储表示-----------------------------
typedef string InfoType;
typedef string VertexType;
typedef struct ArcBox {
	int tailVex;				    //该弧的尾顶点的位置
	int headVex; 					//该弧的头顶点的位置
	struct ArcBox* hLink; 			//弧头相同的弧的链域
	static ArcBox* tLink; 			//弧尾相同的弧的链域
	InfoType info; 					//弧的相关信息
}ArcBox;
typedef struct VexNode {
	VertexType data; 				//顶点的数据域
	ArcBox* firstIn; 				//指向该顶点的第一条入弧
	ArcBox* firstOut; 				//指向该顶点的第一条出弧
}VexNode;
#define MAX_VERTEX_BUM 20
typedef struct {
	VexNode xList[MAX_VERTEX_BUM];  //表头向量
	int vexnum; 					//图的顶点个数
	int arcnum; 					//图的边数
}OLGraph;

邻接多重表

定义

邻接多重表的结构和十字链表类似。在邻接多重表中,每一条边用一个结点表示,其中,mark为标志域,可用以标记该条边是否被搜索过;ivex和jvex为该边依附的两个顶点在图中的位置;ilink指向下一条依附于顶点ivex的边;jlink指向下条依附于顶点jvex的边,info为指向和边相关的各种信息的指针域。当需要对边增删查改的时候,用邻接多重表就更加合适。

无向图的邻接多重表存储表示
//-------------------------无向图的邻接多重表存储表示------------------------------
#define MAX_VERTEX_NUM 20
typedef enum{unvisited,visited}VisitIf;
typedef struct Ebox
{
	VisitIf mark;					//访问标记
	int ivex, jvex;					//该边依附的两个顶点的位置
	struct Ebox* ilink, * jlink;	//分别指向依附这两个顶点的下一条边
	InfoType* info;					//该边信息指针
}Ebox;
typedef struct Vexbox
{
	VertexType data;				//指向第一条依附这两个顶点的下一条边
	Ebox* firstedge;
}Vexbox;
typedef struct
{
	Vexbox adjmulist[MAX_VERTEX_NUM];//无向图的当前顶点数和边数
	int vexnum, edgenum;
}AMLGraph;

6.遍历:

深度优先搜索

特点

不断访问新的顶点,并记录访问过的顶点,直到全部访问再返回原来已被访问过的顶点(一条路走到黑)直到所有顶点都被访问过,搜索结束

代码实现
采用邻接矩阵表示法图的深度优先搜索遍历
//----------------------采用邻接矩阵表示法图的深度优先搜索遍历----------------------
void DFS_AM(AMgraph G, int v)
{	//图G为邻接矩阵类型,从第v个顶点出发深度优先搜索遍历图G
	cout << G.vex[v]<<" ";						//访问第v个顶点
	visited[v] = true;							//置访问标记数组相应分量值为true
	for (int i = 0; i < G.vexnum; i++)			//依次检查邻接矩阵v所在的行
	{
		if ((G.arcs[v][i] != MaxInt) && (!visited[i]))
			DFS_AM(G, i);						//如果未被访问则递归调用DFS_AM()
	}
}
采用邻接表表示法图的深度优先搜索遍历
//-----------------采用邻接表表示法图的深度优先搜索遍历-----------------------------
void DFS_AL(ALGraph G, int v)
{	//图G为邻接表类型,从第v个顶点出发
	cout << G.vertice[v].data<<" ";			//访问第v个顶点
	visited[v] = true;						
	ArcNode* p = G.vertice[v].firstarc;		//p指向v的边链表的第一个结点
	while (p)
	{
		int w = p->adjvex;					//表示w是v的邻接点
		if (!visited[w])DFS_AL(G, w);		//如果w未被访问,则递归调用函数
		p = p->nextarc;						//p指向下一个边结点
	}
}

广度优先搜索

特点

尽可能先对横向进行搜索,先访问的顶点其邻接点先被访问

代码实现
采用邻接矩阵表示法图的深度优先搜索遍历
//----------------------采用邻接矩阵表示法图的广度优先搜索遍历----------------------
void BFS_AL(ALGraph G, int v)
{
	cout << G.vertice[v].data << " ";			//访问第v个顶点
	visited[v] = true;
	queue<int>q;
	q.push(v);
	while (q.size())
	{
		int u = q.front();
		q.pop();
		for (ArcNode* p = G.vertice[u].firstarc; p != NULL; p = p->nextarc)
		{
			int w = p->adjvex;
			if (!visited[w])
			{
				cout << G.vertice[w].data << " ";
				visited[w] = true;
				q.push(w);
			}
		}
	}
}
采用邻接表表示法图的广度优先搜索遍历
void BFS_AL(ALGraph G, int v)
{
	cout << G.vertice[v].data << " ";			//访问第v个顶点
	visited[v] = true;
	queue<int>q;
	q.push(v);
	while (q.size())
	{
		int u = q.front();
		q.pop();
		for (ArcNode* p = G.vertice[u].firstarc; p != NULL; p = p->nextarc)
		{
			int w = p->adjvex;
			if (!visited[w])
			{
				cout << G.vertice[w].data << " ";
				visited[w] = true;
				q.push(w);
			}
		}
	}
}

7.总代码

存储方式二选一输入图即可获得其深度优先和广度优先的遍历序列

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


//-----------图的邻接矩阵存储表示---------
#define MaxInt 32767			//表示极大值
#define MVNum 100				//最大顶点数
typedef string VerTexType;		//假设顶点的数据结构类型为string型
typedef int ArcType;			//假设边的权值类型为整型
typedef struct
{
	VerTexType vex[MVNum];		//顶点表
	ArcType arcs[MVNum][MVNum]; //邻接矩阵
	int vexnum, arcnum;			//顶点数和边数
}AMgraph;
int LocateVex(AMgraph G,VerTexType v)
{
	int i;
	for (i = 0; i < G.vexnum; i++)
		if (G.vex[i] == v)return i;
	return -1;
}
void CreateUDN(AMgraph& G)						//采用邻接矩阵表示法,创建无向图G
{	
	cout << "请输入总顶点和总边数:";
	cin >> G.vexnum >> G.arcnum;				//输入总顶点和总边数
	for (int i=0;i<G.vexnum;++i)
	{
		cout<<"请输入顶点信息:";
		cin >> G.vex[i];						//输入顶点信息
	}
	for (int i = 0; i < G.arcnum; i++)
		for (int j = 0; j < G.arcnum; j++)
			G.arcs[i][j] = MaxInt;				//初始化边的权值,均设置为极大值MaxInt
	for (int k = 0; k < G.arcnum; k++)			//构造邻接矩阵
	{
		int w;
		string v1, v2;
		cout << "请输入一条边依附的顶点及这条边的权值:";
		cin >> v1 >> v2;					    //输入一条边依附的顶点
		cin >> w;								//及权值
		int i = LocateVex(G, v1);				//确定v1,v2的下标
		int j = LocateVex(G, v2);
		G.arcs[i][j] = G.arcs[j][i]= w;			//置<v1,v2>及其对称边的权值为w
	}
}
bool visited[MVNum] = {0};						//标记辅助数组,标记顶点是否访问过
void DFS_AM(AMgraph G, int v)
{	//图G为邻接矩阵类型,从第v个顶点出发深度优先搜索遍历图G
	cout << G.vex[v]<<" ";						//访问第v个顶点
	visited[v] = true;							//置访问标记数组相应分量值为true
	for (int i = 0; i < G.vexnum; i++)			//依次检查邻接矩阵v所在的行
	{
		if ((G.arcs[v][i] != MaxInt) && (!visited[i]))
			DFS_AM(G, i);						//如果未被访问则递归调用DFS_AM()
	}
}
void BFS_AM(AMgraph G, int v)
{
	cout << G.vex[v] << " ";
	visited[v] = true;
	queue<int>q;
	q.push(v);
	while (q.size())
	{
		int u = q.front();
		q.pop();
		for (int w=0;w<G.vexnum;w++)
		{
			if ((!visited[w]) && (G.arcs[u][w] != MaxInt))
			{
				cout << G.vex[w]<< " ";
				visited[w] = true;
				q.push(w);
			}
		}
	}
 }
//----------图的邻接表存储表示---------------
typedef struct ArcNode		//边界点
{
	int adjvex;				//该边指向的顶点的位置(下标)
	struct ArcNode* nextarc;//指向下一条边的指针
}ArcNode;
typedef struct VNode		//顶点节点
{
	VerTexType data;		//顶点信息(char类型)
	ArcNode* firstarc;		//指向顶点的第一条边
}VNode,AdjList[MVNum];		//AdjList表示邻接链表类型
typedef struct
{
	AdjList vertice;		
	int vexnum, arcnum;		//图的当前顶点数和边数
}ALGraph;
int LocateVex_(ALGraph G, VerTexType v)
{
	for (int i = 0; i < G.vexnum; i++)
	{
		if (G.vertice[i].data == v)
			return i;
	}
	return -1;
}
void CreateUDG(ALGraph& G)			//用邻接表创建无向图
{
	cout << "请输入总顶点和总边数:";
	cin >> G.vexnum >> G.arcnum;	//输入总顶点数,总边数
	for (int i = 0; i < G.vexnum; i++)
	{
		cout << "请输入顶点信息:";
		cin >> G.vertice[i].data;	//输入顶点信息
		G.vertice[i].firstarc = NULL;//初始化
	}
	for (int k = 0; k < G.arcnum; k++)
	{
		string v1, v2;				//输入边的两个顶点
		cout << "请输入一条边依附的顶点:";
		cin >> v1 >> v2;
		int i, j;
		i = LocateVex_(G, v1);		//定位v1,v2的下标
		j = LocateVex_(G, v2);
		//将边处理
		ArcNode* p1 = new ArcNode;
		p1->adjvex = j;
		p1->nextarc = G.vertice[i].firstarc;
		G.vertice[i].firstarc = p1;
		ArcNode* p2 = new ArcNode;
		p2->adjvex = i;
		p2->nextarc = G.vertice[j].firstarc;
		G.vertice[j].firstarc = p2;

	}
}
void DFS_AL(ALGraph G, int v)
{	//图G为邻接表类型,从第v个顶点出发
	cout << G.vertice[v].data<<" ";			//访问第v个顶点
	visited[v] = true;						
	ArcNode* p = G.vertice[v].firstarc;		//p指向v的边链表的第一个结点
	while (p)
	{
		int w = p->adjvex;					//表示w是v的邻接点
		if (!visited[w])DFS_AL(G, w);		//如果w未被访问,则递归调用函数
		p = p->nextarc;						//p指向下一个边结点
	}
}
void BFS_AL(ALGraph G, int v)
{
	cout << G.vertice[v].data << " ";			//访问第v个顶点
	visited[v] = true;
	queue<int>q;
	q.push(v);
	while (q.size())
	{
		int u = q.front();
		q.pop();
		for (ArcNode* p = G.vertice[u].firstarc; p != NULL; p = p->nextarc)
		{
			int w = p->adjvex;
			if (!visited[w])
			{
				cout << G.vertice[w].data << " ";
				visited[w] = true;
				q.push(w);
			}
		}
	}
}
void InitVis()	//由于此辅助数组需多次使用,则每次遍历完后要重新进行初始化
{
	for (int i = 0; i < MVNum; i++)
		visited[i] = false;
	
}
int main()
{
	AMgraph G1;
	cout << "采用邻接矩阵表示法创建图:\n";
	CreateUDN(G1);
	cout << "从第一个顶点开始其深度后的深度优先树为:";
	DFS_AM(G1,0);
	cout << endl;
	InitVis();
	cout << "从第一个顶点开始其深度后的广度优先树为:";
	BFS_AM(G1, 0); 
	/*ALGraph G2;
	cout << "采用邻接表表示法创建图:\n";
	CreateUDG(G2);
	cout << "从第一个顶点开始其深度后的深度优先树为:";
	DFS_AL(G2,0);
	cout << endl;
	InitVis();
	cout << "从第一个顶点开始其深度后的广度优先树为:";
	BFS_AL(G2, 0);*/
	return 0;
}



  • 18
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
(Graph)是由顶点的有穷非空集合和顶点之间边的集合组成,通常表示为:G(V,E),其中,G表示一个,V是G中顶点的集合,E是G中边的集合。在中的数据元素,我们称之为顶点(Vertex),顶点集合有穷非空。在中,任意两个顶点之间都可能有关系,顶点之间的逻辑关系用边来表示,边集可以是空的。 按照边的有无方向分为无向和有向。无向由顶点和边组成,有向由顶点和弧构成。弧有弧尾和弧头之分,带箭头一端为弧头。 按照边或弧的多少分稀疏和稠密。如果中的任意两个顶点之间都存在边叫做完全,有向的叫有向完全。若无重复的边或顶点到自身的边则叫简单中顶点之间有邻接点、依附的概念。无向顶点的边数叫做度。有向顶点分为入度和出度。 上的边或弧带有权则称为网。 中顶点间存在路径,两顶点存在路径则说明是连通的,如果路径最终回到起始点则称为环,当中不重复的叫简单路径。若任意两顶点都是连通的,则就是连通,有向则称为强连通中有子,若子极大连通则就是连通分量,有向的则称为强连通分量。 无向中连通且n个顶点n-1条边称为生成树。有向中一顶点入度为0其余顶点入度为1的叫有向树。一个有向由若干棵有向树构成生成森林。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值