图的定义和术语
图是由顶点集合和弧的集合组成的。记作 G=(V,E).其中V(G)表示顶点集,E(G)表示弧集。顶点即为数据结构中的数据元素。<v,w>表示从 v 到 w 的一条弧,有方向的弧中,v 表示弧尾或者始点,w 表示弧头或者终点,此时的图称为有向图。(v,w)这样的无序对表示 v 和 w 之间的一条边。此时顶点之间不再强调方向性,称之为无向图。在实际应用中,图的弧或者边往往与具有一定意义的数相关,称这些数为权。带权的无向图和有向图分别称为无向网和有向网。
图中的顶点数为 n ,边或者弧的数目为 e,若不考虑顶点到其自身的弧或边,则对于无向图,边数e的范围是0到n(n-1)/2.称具有n(n-1)/2条边的无向图为无向完全图。对于有向图,弧的数目 e 的范围是0到 n(n-1) 称具有n(n-1)条弧的有向图为有向完全图。若 e<nlogn,则称为稀疏图,否则为稠密图。
如果图 G' 的顶点集合是另一个图 G 的顶点集合的子集, G' 的弧(边)集合也是 G 对应集合的子集,则成 G' 为 G 的子图。
度,入读和出度的概念:有向图中,一个顶点,有多少弧从这点出发,则这个点的出度就是多少,有多少弧到达这个点,则它的入度就是多少。出度和入度的和为度。无向图中,度的定义为与该顶点相连的边的数目。
路径和回路:从一个顶点到另一个顶点,经过许多其顶点,这些顶点加在一起的序列就是这两个顶点之间的路径。中间弧的数目就是路径长度。如果从一个顶点出发最后又回到了这个顶点,则这种路径是一个回路或环。
图的基本操作
对图的基本操作包含构造图,销毁图,查询途中顶点,对顶点赋值等等。
图的存储结构(建立)
图中的顶点可能与其他任意一个顶点之间有关系。因此图没有顺序存储表示的结构,图有两种常用的存储结构。
1,图的数组(邻接矩阵)存储表示
图的邻接矩阵用于表示图中顶点之间关系(及弧或边的权)的矩阵。如果图中顶点数为 n 则矩阵为 n 行 n 列。对于矩阵中的某个值 A[ i ][ j ], 如果顶点 Vi 和 Vj 之间有弧或者边的存在,则 A[ i ][ j ]=1否则为0.无向图的邻接矩阵一定是对称矩阵。网的邻接矩阵中,当两点之间有连接时,对应的矩阵中的元素应该赋值为对应的权值,否者赋值为∞。实际应用中的有向图的邻接矩阵多为稀疏矩阵,通常用二维数组表示,除非矩阵特别大才会用矩阵的压缩存储。
#include<stdio.h>
#include<limits.h>
const int MAX = INT_MAX; //定义最大值为∞
const int MAX_VERTEX_NUM = 20; //最大顶点个数
typedef enum { DG, DN, AG, AN } GraphKind; //图类型(有向图,有向网,无向图,无向网)
//下面的结构体定义的是二维数组(邻接矩阵)中每一个元素单独的数据信息
typedef struct ArcCell {
VRType adj;
//VRType是顶点关系类型。对无权图,用1或0表示相邻否,对带权图,则为权值类型
InfoType *info;
//指向该弧相关信息的指针
//写程序时可以将这个info指针删去
//因为作者的本意是通过利用info指针去指向弧(边)的数据信息
//而这个信息通过adj就可以进行存储
//因此将这一步删去可以简化程序设计的步骤
}ArcCell, AdjMatrix[MAX_VERTEX_NUM][MAX_VERTEX_NUM];
typedef struct {
VertexType vexs[MAX_VERTEX_NUM]; //描述顶点的数组 这里的VertexType是指顶点的数据类型
AdjMatrix arcs; //建立邻接矩阵arcs[][], 矩阵中存储的信息是顶点与顶点之间的关系,即弧(边)的数据类型
int vexnum, arcnum; //图的当前顶点数和弧(边)数
GraphKind kind; //图的种类标志
}MGraph;
以上是图的邻接矩阵存储结构的算法表示,实际应用时,可以将上面算法中的数据类型进行替换,就可以得到存储任意数据类型的图的结构定义。例如:如果一个图是无向图,每个结点的数据类型是char型,存储A,B,C...等字符,定义其邻接矩阵存储结构时可以如下定义:
const int MAX = INT_MAX; //定义最大值为∞
const int MAX_VERTEX_NUM = 20; //最大顶点个数
typedef enum { DG, DN, AG, AN } GraphKind; //图类型(有向图,有向网,无向图,无向网)
typedef struct ArcCell {
int adj; //表示顶点之间的关系 因为是无向网,所以只用0和1进行表示是否顶点之间相连
//InfoType *info; 该info指针已经删去
}ArcCell, AdjMatrix[MAX_VERTEX_NUM][MAX_VERTEX_NUM];
typedef struct {
char vexs[MAX_VERTEX_NUM]; //描述顶点的数组 顶点的数据元素为char类型
AdjMatrix arcs; //建立邻接矩阵arcs[][]
int vexnum, arcnum; //图的当前顶点数和弧(边)数
GraphKind kind; //图的种类标志
}MGraph;
以下是利用邻接矩阵(二维数组)建立无向图的完整程序。程序流程是1,输入定点和弧的数目;2,输入顶点的数据存放到顶点一维数组中;3,初始化邻接矩阵的各项为0;4,循环输入每个弧所涉及的两个顶点,以及对应的权值;5,利用定位函数得到输入的两个顶点在图中一维数组中的位置,然后在邻接矩阵中对应的地方赋值为1;
#include<iostream>
using namespace std;
const int MAX_VERTEX_NUM = 20; //最大顶点个数
typedef enum { DG, DN, AG, AN } GraphKind; //图类型(有向图,有向网,无向图,无向网)
typedef int VRType; //顶点的关系数据类型 因为是无向图,所以用0和1表示
typedef char VertexType; //顶点本身的数据类型
//下面的结构体定义的是二维数组(邻接矩阵)中每一个元素单独的数据信息
typedef struct ArcCell {
VRType adj;
//VRType是顶点关系类型。对无权图,用1或0表示相邻否,对带权图,则为权值类型
}ArcCell, AdjMatrix[MAX_VERTEX_NUM][MAX_VERTEX_NUM];
typedef struct {
VertexType vexs[MAX_VERTEX_NUM]; //建立顶点一维数组
AdjMatrix arcs; //建立邻接矩阵arcs[][], 记录顶点之间关系
int vexnum, arcnum; //图的当前顶点数和弧(边)数
GraphKind kind; //图的种类标志
}MGraph;
/****************************************************************************************************
顶点定位函数,在图G中查找顶点v的位置 若G中存在顶点v,则返回该顶点在图中位置;否则返回-1
*****************************************************************************************************/
int LocateVex(MGraph G, char v)
{
//遍历顶点数组查找顶点v
for (int i = 0; i < G.vexnum; i++)
{
if (G.vexs[i] == v) {
return i;
}
}
return -1; //否则返回-1,表示没有找到
}
/****************************************************************************************************
利用邻接矩阵(二维数组)建立无向图
*****************************************************************************************************/
void CreateUDN(MGraph &G)
{
//临时变量,从键盘接收顶点名称
VertexType v1, v2;
int k, w, i, j; //i,j 分别用于保存顶点v1和v2在图中的序号 w用于接收输入的权值 k 用于输入弧时的循环变量
cout << "请输入无向网G的顶点数:";
cin >> G.vexnum;
cout << "请输入无向网G的弧数:";
cin >> G.arcnum;
cout << "请依次输入无向网G的顶点名称,用空格隔开" << endl;
for (i = 0; i < G.vexnum; i++)
{
cin >> G.vexs[i];
}
//初始化邻接矩阵各存储单元的值为0,0表示两个顶点之间不可达
for (i = 0; i < G.vexnum; i++)
{
for (j = 0; j < G.vexnum; j++) {
G.arcs[i][j].adj = 0;
}
}
//输入弧依附的顶点和权值,填充邻接矩阵
cout << "请依次输入无向网G每条弧依附的两顶点名称及权值,输完一组按回车" << endl;
for (k = 0; k < G.arcnum; k++)
{
cin >> v1 >> v2 >> w; //输入弧依附的两顶点v1和v2 输入弧上的权值
i = LocateVex(G, v1); //确定v1,v2在G中的位置i,j
j = LocateVex(G, v2);
//保存权值到邻接矩阵的对应存储单元中
G.arcs[i][j].adj = w;
//由于构造的是无向网,所以还需要置<v1,v2>的对称弧<v2,v1>
G.arcs[j][i] = G.arcs[i][j];
}
}
void main()
{
MGraph G;
CreateUDN(G);
}
2.图的邻接表存储表示
邻接表是图的一种链式存储表示方法,它类似于树的孩子链表。
由图可见,在有向图的邻接表中,从同一顶点出发的弧链接在同一链表中,邻接表中结点的个数恰为图中弧的数目。而在无向图的邻接表中,同一条边有两个结点,分别出现在和它相关的两个顶点的链表中,因此无向图的邻接表中结点个数是边数的两倍。在邻接表中,顶点表结点的顺序取决于建立图结构时输入信息的次序。
const int MAX_VERTEX_NUM = 20;
//下面的结构体定义的是弧的数据信息
typedef struct ArcNode {
int adjvex; //该弧所指向的顶点的位置
struct ArcNode *nextarc; //指向下一条弧的指针
Infotype *info; //指向该弧相关信息的指针(用来表示弧/边上的权值) 只有是无向网或者有向网时 该指针才有意义
}ArcNode;
//下面的结构体定义的是顶点的数据信息
typedef struct VNode {
VertexType data; //顶点本身的数据类型
ArcNode *firstarc; //指向第一条依附该顶点的弧
}VNode, AdjList[MAX_VERTEX_NUM];
typedef struct {
AdjList vertices;
int vexnum, arcnum; //图的当前顶点数和弧数
int kind; //图的种类标志
}ALGraph;
对于图的邻接表存储表示,要定义三个结构体。第一个结构体表示图中的弧,包含该弧指向的顶点的位置,以及指向下一条弧的指针(对于网,会有弧的权值信息)。第二个结构体表示图中的顶点,包含顶点本身的数据信息,以及指向第一条依附该顶点的弧。第三个结构体表示图,包含一个结点一维数组,图当前的顶点数和弧数,图的种类标志。
邻接表建立图的步骤如下:1,输入图的类型,以及顶点数和边数。然后输入顶点的值(建立顶点一维数组)。2,根据图的类型不同,输入相连的顶点对(以及可能存在的弧的权值)。3,获取两个顶点在图中的位置。4,对弧结点进行赋值。赋值弧指向的顶点的位置,(弧的权值)。5,在顶点一维数组中,将弧结点插入到对应的顶点后面。采用头插法。
#include<iostream>
using namespace std;
const int MAX_VERTEX_NUM = 20;
typedef int Infotype; //对于网中边/弧上的权值,用整形数据
typedef char VertexType; //对于顶点本身的数据类型,用char类型
typedef enum { DG, DN, UDG, UDN } GraphKind; // {有向图 = 0,有向网 = 1,无向图 = 2,无向网 = 3}
//下面的结构体定义的是弧的数据信息
typedef struct ArcNode {
int adjvex; //该弧所指向的顶点的位置
struct ArcNode *nextarc; //指向下一条弧的指针
Infotype *info; //指向该弧相关信息的指针(用来表示弧/边上的权值) 只有是无向网或者有向网时 该指针才有意义
}ArcNode;
//下面的结构体定义的是顶点的数据信息
typedef struct VNode {
VertexType data; //顶点本身的数据类型
ArcNode *firstarc; //指向第一条依附该顶点的弧
}VNode, AdjList[MAX_VERTEX_NUM];
//下面的结构体定义图
typedef struct {
AdjList vertices;
int vexnum, arcnum; //图的当前顶点数和弧数
int kind; //图的种类标志
}ALGraph;
/******************************************************************************************
头插法 将弧结点插入到顶点链表中
*******************************************************************************************/
void ListInsert(ArcNode *p, ArcNode e)
{
e.nextarc = p;
p = &e;
}
/******************************************************************************************
对于指定的顶点,返回在图中的位置
*******************************************************************************************/
int LocateVex(ALGraph G, VertexType u)
{
int i;
for (i = 0; i < G.vexnum; i++)
{
if (G.vertices[i].data == u)
{
return i;
}
}
return -1; //没有在图中找到顶点,返回-1
}
/******************************************************************************************
根据用户的输入构造四种图
*******************************************************************************************/
void CreateGraph(ALGraph &G)
{
int i, j, k, w; //w是权值(只有是构造网的时候用到),i、j、k全是临时变量,循环用
VertexType va, vb; //定义va,vb用来暂存弧/边连接的两个顶点
//弧结点 此时创建的直接是一个结点e 所以不用对其进行new函数分配内存空间 但是内部的info指针需要单独分配
ArcNode e;
//确定图的类型
cout << "请输入图的类型(有向图--0,有向网--1,无向图--2,无向网--3): ";
cin >> G.kind;
//确定图的顶点数和边数
cout << "请输入图的顶点数和边数(用空格隔开): ";
cin >> G.vexnum >> G.arcnum;
//从键盘输入顶点值,构造顶点向量
cout << "请输入顶点的值(用空格隔开):";
for (i = 0; i < G.vexnum; i++)
{
//输入顶点的值
cin >> G.vertices[i].data;
//初始化与该顶点有关的出弧链表
G.vertices[i].firstarc = NULL;
}
if (G.kind % 2 != 0) //构造网 需要继续输入边的权值
cout << "请输入每条弧(边)的弧尾、弧头和权值(以空格作为间隔,输入一组后回车):" << endl;
else //构造图
cout << "请输入每条弧(边)的弧尾和弧头(以空格作为间隔,输入一组后回车):" << endl;
//构造相关弧链表
for (k = 0; k < G.arcnum; k++)
{
if (G.kind % 2 != 0) //如果构造的是网,则需要接收权值
cin >> va >> vb >> w;
else //构造的是图,不需要接收权值
cin >> va >> vb;
//先处理始点和终点部分,这是图和网都需要执行的公共操作。
i = LocateVex(G, va); //始点
j = LocateVex(G, vb); //终点
//对于弧结点 弧本身权值初始化为NULL 这样如果是构建图则保持info为NULL不变,如果构建网再分配空间赋值
e.info = NULL;
e.adjvex = j; //终点
if (G.kind % 2) //如果构造的是网,则需要将权值存放到弧的相关信息info中
{
e.info = new Infotype;
*(e.info) = w; //将权值放入弧结点中的info中
}
//将弧结点插在第i个顶点的表头
//本项操作调用的是单链表的头插法,G.vertices[i].firstarc相当于弧链表的头指针
ListInsert(G.vertices[i].firstarc, e);
//如果构造的是无向图或无向网,产生第2个表结点,并插在第j个元素(入弧)的表头
//第二个表结点与第一个结点沿主对角线(左上至右下)对称
if (G.kind >= 2)
{
//主对角线对称位置的结点权值不变,所以e.info不变,不必再赋值
//也就是说:邻接矩阵中沿主对角线对称的两个结点在邻接表中共用一块权值空间
e.adjvex = i;
//插在对角线对称位置顶点的表头,也就是第j个位置。
ListInsert(G.vertices[j].firstarc, e);
}
}
}
void main()
{
ALGraph G;
CreateGraph(G);
}
本笔记所依据的教材为严薇敏版的《数据结构及应用算法教程》
所有代码在Visual Studio 2017上均可正常运行
如有错误欢迎指出