图的逻辑结构定义
因为图的结构比较复杂,任意两个点之间都可能存在联系, 因此无法以数据元素在内存中的物理位置来表示元素之间的关系, 也就是说, 图不可能用简单的顺序结构来表示。 下面是图的物种不同的存储结构
1 邻接矩阵
顶点不分大小,主次, 所以用一个一位数组来存储顶点是不错的选择。
而边或弧是由于顶点和顶点之间的关系确定, 一维数组搞不定, 所以考虑用一个二维数组来存储, 于是我们的邻接矩阵的方案就诞生了。
1.1 无向图例
举个例子: 下图就是一个无向图,
顶点数组为 vertex[4]={v0,v1,v2,v3} , 边数组 arc[4][4],
对于矩阵的主对角线的值, 即 arc[0][0], arc[1][1], arc[2][2], arc[3][3], 全为0, 是因为不存在顶点到自身的边。
根据这个矩阵, 我们可以很容易的知道图中的信息,
1 判断任意两顶点是否有边无边就很容易了
2 我们要知道某个顶点的度, 其实就是这个顶点 Vi 在邻接矩阵中第 i 行(或者第i列)的元素之和, 比如顶点 V1 的度就是 1 + 0 + 1 + 0 = 2
3 求顶点Vi 的所有的邻接点, 就是将矩阵中第i 行元素扫描一遍, arc[i][j] 为 1 就是邻接点
1.2 有向图例
有向图讲究出度和入度, 顶点V1 的入度为1, 整好是 V1 列各数之和, 顶点V1 的出度为2, 即为第V1 行的各数之和
判断顶点是否存在弧, 只需要查找 arc[i][j] 是否为1 即可。若要求Vi 的所有邻接点, 那就把矩阵第 i 行元素都扫描一遍, 查找 arc[i][j] 为1 的顶点
1.3 网
网: 每条边上带有权的图叫做网, 那么这些全值需要保存下来, 如何处理这个矩阵需求呢 ?
把 arc 矩阵中的 1 替换成 权重即可
如下图就是一个有向图网, 右图是它的邻接矩阵。
大于0 的数字是权重,
2 邻接表
邻接矩阵的缺点: 当变数相对于顶点较少的图, 用邻接矩阵会造成较大的空间浪费, 如下图, 邻接矩阵中除了 arc[1][0] 有权值外, 没有其他弧, 这些存储空间都被浪费掉了。
我们可以用数组和线性表结合起来,用于存储图, 这种方法就叫做邻接表。
邻接表的处理办法:
1 图中顶点用一个一位数组进行存储, 对于顶点数组中, 每个数据元素还需要存储指向第一个邻接点的指针, 以便于查找该顶点的边信息
2 图中每个顶点Vi 的所有邻接点构成一个线性表, 由于邻接点的个数不定, 所以用单链表存储,, 无向图称为顶点Vi的边表, 有向图则称为顶点 Vi 作为弧尾的出边表
2.1 无向图邻接表
顶点表的各个结点, 由data 和firstedge两个域表示 , data是数据域, 存储顶点的信息, firstedge是指针域, 指向边表的第一个结点, 即此结点的第一个邻接点 。 边表结点又 adjvex 和 next 两个域组成, adjvex是邻接域, 存储某顶点的邻接点在顶点表中的下标, next则存储指向边表下一个结点的指针。 比如V1 顶点和V0 V2 互为邻接点, 则再V1 的边表中, adjvex分别为V0 的0 和 V2 的 2
2.2 有向图邻接表
有向图由于有方向, 所以我们以顶点为尾弧来存储边表, 这样容易得出每个顶点的出度, 但也有时为了便于确定顶点的入度或以顶点为弧头的弧, 我们可以建立一个有向图的逆序邻接表,
逆序邻接表
通过邻接表, 我们很容易判断出某个顶点的入度或出度为多少, 判断两顶点是否存在弧也很容易
对于带权值的网图, 可以在边表结点定义中, 再增加一个weight的数据域, 存储权值信息即可
3 十字链表
对于有向图来说, 邻接表是有缺陷的, 关心了入度问题, 想要了解出度就需要遍历整个图 , 关心了出度问题, 想要了解入度就需要遍历整个图。 我们可以通过十字表来解决这个问题
我们重新定义顶点表结构入下所示, 其中firstin 表示入边表头指针, firstout表示出边表头指针, 指向该顶点的出边表中的第一个结点,
重新定义的边表结点结构如下所示:
tailvex 表示弧七点在顶点表的下标, headvex是指弧终点在顶点表中的下标, headlink是指入边表指针域, 指向终点相同的下一条边, taillink是指边表指针域, 指向七点相同的下一条边, 如果是网, 还可以再增加一个weight来存储权值。
顶点依然存入一个一位数组, 以顶点V0 来说, firstout指向的是出边表中第一个结点V3, 所以V0边表结点的 headvex=3, 而tailvex其实就是当前顶点V0的下标0, 用于V0只有一个出边顶点, 所以 headlink 和taillink都是空
0 3 表示从 V0 到V3 的边, 所以顶点0的 firstout 指向了这条边的指针,
而 V3 的firstInt , 也指向了这条边()的指针(在图中就是虚线⑤)
其他顶点的firstin firstout均可这么理解
,
接下来解释虚线箭头的含义, 它其实就是此图的逆序邻接表的表示, 对于V0lduo , 它有两个顶点,
V1 V2的入边, 因此 V0 的firstin 指向顶点V1 的边表结点中 headvex 为0的结点, 接着由入边 结点的headlink指向下一个入边顶点V2的边表结点中 headvex为1的结点, 如图中的③, 顶点V2 和V3 也是同样有一个入边顶点, 如图中④和⑤
4 邻接多重表
重新定义的边表结点结构如下所示:
其中ivex 和jvex是与某条边依附的两个顶点在 顶点表 中的下标,
ilink 指向依附顶点ivex的下一条边,jlink指向依附顶点jvex的下一条边。
如下图,总共四个顶点五条边, 因为是无向图, 所以ivex 是0, jvex是1 还是反过来都可以, 不过为了绘图方便, 都将ivex的值设置得和旁边顶点的下标一致
接下来,我们开始把他们链接起来,
1.首先连线的 ①②③④ 就是将顶点的firstedge指向一条边, 顶点下标要与ivex的值相同
2.由于顶点V0 的(V0,V1)的边有邻边(V0,V2), (V0,V3);
所以 ⑤指向的就是 (V0,V3)这个邻边, 它依附于顶点 V0, 对应的是ivex=0 时候的ilink
⑥指向的是 (V0,V3)这个边的邻边, 只是这时候 V0 在 jvex的位置
⑦指的就是(V1,V0) 这条边
⑧指的就是(V1,V2) 这条边
⑨指的就是(V0,V2) 这条边
5 边集数组
边集数组由两个一维数组构成, 一个是存储顶点的信息, 另一个存储边的信息, 这个边数组每个数据元素由一条边的起点下标 begin, 终点下标 end 和权 weight组成。很明显, 边集数组关注的是边的集合, 在边集数组中要查询一个顶点的度需要扫描整个边数组, 效率并不高。