【精选】数据结构与算法(图论系列)

您所在的位置:网站首页 邻接矩阵计算度 【精选】数据结构与算法(图论系列)

【精选】数据结构与算法(图论系列)

2023-10-20 04:07| 来源: 网络整理| 查看: 265

图 图的定义

图(Graph)G由两个集合V和G组成,记作G = (V,G)。其中V是各顶点(结点)的有穷非空集合,V中的任意两个顶点配对后作为集合E的元素,顶点偶对亦称为边。

在有向图中,E中的元素形式为,表示从顶点x到顶点y的一条有向边,有向边也称作弧,x为弧尾,y为弧头;在无向图中,E中的元素形式为(x,y),仅表示连接顶点x和顶点y的一条边,效果同(y,x)。

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

图的存储结构

由于图的任意两个顶点之间都可能存在联系,因此无法以数据元素在存储区中的物理位置来表示元素之间的关系,即图没有顺序存储结构,但我们可以用二维数组(矩阵)来表示元素之间的关系——邻接矩阵。除此之外还有链式存储结构,包括邻接表、十字链表和邻接多重表。其中邻接矩阵和邻接表最常用。

邻接矩阵 定义和性质

图的邻接矩阵存储方式是用两个数组来表示图。一个一维数组存储图中顶点信息,一个二维数组(邻接矩阵)存储图中的边或弧的信息。 设图G有n个顶点,则邻接矩阵是一个n*n的方阵,定义为: 在这里插入图片描述 看一个实例,下图左就是一个无向图: 在这里插入图片描述 从上面可以看出,无向图的边数组是一个对称矩阵。所谓对称矩阵就是n阶矩阵的元满足aij = aji。即从矩阵的左上角到右下角的主对角线为轴,右上角的元和左下角相对应的元全都是相等的。

从这个矩阵中,很容易知道图中的信息:

(1)要判断任意两顶点是否有边无边就很容易了,判断邻接矩阵中元素值a[i][j]是0是1即可; (2)要知道某个顶点的度,其实就是 这个顶点vi在邻接矩阵中第i行或(第i列)的元素之和; (3)求顶点vi的所有邻接点就是将矩阵中第i行元素扫描一遍,arc[i][j]为1就是邻接点;

而有向图讲究入度和出度,顶点vi的入度为1,正好是第i列各数之和。顶点vi的出度为2,即第i行的各数之和。 若图G是网图,有n个顶点,则邻接矩阵是一个n*n的方阵,定义为: 在这里插入图片描述

代码实现

清楚邻接矩阵的使用方法后,我们可以轻松写出以它为基础的图的结构形式。

为了避免概念的混淆,之后我会把图称作邻接矩阵图(AMGraph)。

/*邻接矩阵的结构表示*/ #include using namespace std; #define MaxInt 32767 /*表示∞(无限)*/ #define MaxVNum 100 /*表示图中最多可以包含的顶点数*/ typedef char Vextype; /*将顶点的数据类型设为字符型,如果你喜欢也可以把它设成更复杂的结构体*/ typedef int Arctype; /*将边的权值设为整型*/ typedef struct { Vextype vexs[MaxVNum]; /*用来保存顶点信息的一维数组*/ Arctype arcs[MaxVNum][MaxVNum]; /*邻接矩阵*/ int vexnum, arenum; /*记录图的顶点数和边数*/ } AMGraph; /*将结构体命名为AMGraph*/ ​

写出邻接矩阵图的结构形式后,可以开始往里面放东西了。

下面以“无向网”为例写一下邻接矩阵表示法的使用方法:

(1)数一下无向网有多少个顶点(vexnum),多少条边(arcnum),然后将数值输入。当然,你也可以凭空造一个网,在先前定义的MaxVNum内赋值就行。

(2)在一维数组vex内输入顶点信息,一个循环完成。

(3)初始化邻接矩阵,将每个权值初始化为MaxInt。为什么呢?因为矩阵里除了若干个权值外,剩余的都是MaxInt,不可能先把权值填好,再来一个个把没有权值的地方填上MaxInt,这样效率极低而且繁琐。

(4)构造邻接矩阵。依次输入每条边依附的两个顶点v1和v2以及其权值w并赋值。因为输入的v1、v2有可能不在0~MaxInt的范围内,而且矩阵和二维数组的“坐标”含义是不同的,即矩阵的(1,1)等价于二维数组中的[0,0],所以需要一个函数LocateVex()帮助判断、转化并确定“坐标”。

int CreatUDN(AMGraph &G) /*UDN意为Omnidirectional Net,无向网*/ { /*上面中写了的东西下面就不再啰嗦喽*/ int i, j, w; cin>>G.vexnum>>G.arcnum; for(i = 0; i >G.vexs[i]; for(i = 0; i 每个单链表的头结点(即每个顶点) 以及 顶点数和边数}。

为了方便管理头结点,我们可以把它们放到一维数组ALs[MaxVNum]中,且该一维数组的功能包含着邻接矩阵图中vexs[MaxVNum]的功能。

*下面将头结点代表的顶点称作起始顶点。

再把注意力放到单链表上,由于起始顶点已经确定,所以与起始顶点相连的每一个顶点都分别对应着一条边,因此我们可以把单链表上的其余结点看作边结点。

边结点需要包含的信息有{边依附的另一个顶点编号(因为顶点放在一维数组ALs[MaxVNum]中,所以顶点v1的编号为0,如此类推)、指向下一个边结点的指针 以及 边上的权}。

头结点需要包含的信息有{起始顶点的信息、指向下一个边结点的指针}

依据上面的思路,我们一共需要创建三个结构体。

/*邻接表的结构表示*/ typedef struct ArcNode /*边结点*/ { int anothervex; /*另一个顶点的编号*/ ArcNode *nextarcnode; /*指向下一边结点的指针*/ int weight; /*权*/ }; typedef struct VexNode /*头结点*/ { VexType data; /*起始顶点的信息*/ ArcNode *nextarcnode; /*指向下一边结点的指针*/ }; typedef struct ALGraph /*邻接表图*/ { VexNode ALs[MaxInt]; /*头结点数组*/ int vexnum, arcnum; /*顶点数、边数*/ };

写出邻接表图的结构形式后,可以开始往里面放东西了。

下面以“无向图”为例写一下邻接表表示法的使用方法:

(1)输入无向图的顶点数和边数;

(2)在一维数组ALs[MaxVNum]中输入起始顶点的信息,并使指针域初始化为NULL,完成各单链表的初始化;

(3)构造邻接表。输入起始顶点va和相邻顶点va的值,以及(va,vb)的权w。

(4)new一个临时边结点tempnode,使tempnode中“另一顶点的编号”为vb的编号,并使其指针指向头结点指针原本指向的“空间”,再令头结点指针指向tempnode,完成tempnode的插入,即完成相邻顶点的连接。

(5)因为这个是无向图,所以我们需要对称地完成起始顶点为vb的单链表的插入。

(6)在达到边数arcnum前循环执行步骤(3)(4)(5)。

int CreatUDG(ALGraph &G) { /*下面的代码已经按照步骤分块喽*/ cin>>G.vexnum>>G.arcnum; int i, j; for(i =0; i cin>>va>>vb>>w; i = LocateVex(G, va); j = LocateVex(G, vb); tempnode1 = new ArcNode; tempnode1->anothervex = j; tempnode1->nextarcnode = G.ALs[i].nextarcnode; G.ALs[i].nextarcnode = tempnode1; tempnode2 = new ArcNode; tempnode2->anothervex = i; tempnode2->nextarcnode = G.ALs[j].nextarcnode; G.ALs[j].nextarcnode = tempnode2; } return 0; } 优缺点

1)优点:

a. 便于增加和删除顶点。

b. 便于统计边的数量,时间复杂度为O(n+e)。

c. 空间效率高。

2)缺点:

a. 不便于判断顶点间是否有联系。

b. 不便于计算有向图各个顶点的入度,需要遍历其余所有起始顶点的单链表。

总结 对于一个具有n个顶点e条边的无向图,它的邻接表表示有n个顶点表结点2e个边表结点对于一个具有n个顶点e条边的有向图,它的邻接表表示有n个顶点表结点e个边表结点如果图中边的数目远远小于n2称作稀疏图,这是用邻接表表示比用邻接矩阵表示节省空间;如果图中边的数目接近于n2,对于无向图接近于n*(n-1)称作稠密图,考虑到邻接表中要附加链域,采用邻接矩阵表示法为宜。


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3