基本概念
图由顶点集V(G)和边集E(G)组成,记为G=(V,E)。V是顶点(数据元素)的有穷非空集合(注意图是不为空的),E是边的有穷集合。无向图:每条边都是无方向的图,有向图:边是有方向的图,边称为弧。
完全图:任意两个结点都有边相连。 设完全图的结点数为n,在无向完全图中,有n(n-1)/2条边,在有向完全图中,有n(n-1)条弧。
稀疏图:有很少边或弧的图。
稠密图:有较多边或弧的图。
网:边/弧带权的图。
邻接:有边/弧相连的两个顶点之间的关系。存在(vi,vj),则称vi,vj互为邻接点;存在<vi,vj>,则称vi邻接到vj,vj邻接于vi。
关联(依附):边/弧与顶点的关系。存在(vi,vj)/<vi,vj>,则称边/弧关联于vi和vj。
顶点的度:与该顶点相关联的边数。在有向图中,顶点的度等于该顶点的入度和出度之和。
路径:接续的边构成的顶点序列。
路径长度:路径上边或弧的数目/权值之和。
回路(环):第一个顶点和最后一个顶点相同的路径。
简单路径:除路径起点和终点可以相同外,其余顶点均不相同路径。
简单回路(环):除路径起点和终点相同外,其余顶点均不相同路径。
连通图(强连通图):在无(有)向图G=(V,E)中 ,若对任意两个顶点v,u都存在从v到u的路径,则称图G为连通图(强连通图)。
子图:设有两个图G=(V,{E}),G1=(V1,{E1}),如果V1属于V,{E1}属于{E},则称G1是G的子图。
连通分量(强连通分量):无(有)向图G的极大连通子图称为G的连通分量(强连通分量)。
极大连通子图:该子图是G的连通子图,将G的任何不在该子图中的顶点加入,子图不再连通。
极小连通子图: 该子图是G的连通子图,删除该子图中任意一条边,子图不再连通。
生成树:包含无向图G所有顶点的极小连通子图。
生成森林:对非连通图,由各个连通分量的生成树的集合。
图的储存结构
数组(邻接矩阵)表示法
建立一个顶点表(记录各个顶点信息)和一个邻接矩阵(表示各个顶点间的的关系)。
设图A<V,E>有n个顶点,则:
图的邻接矩阵是一个二维数组A.arcs[n][n],定义为:
无向图的邻接矩阵表示法
(1)无向图的邻接矩阵是对称矩阵。
(2)顶点i的度=第i行(列)中1的个数。
(3)完全图的邻接矩阵中,对角元素为0,其余为1。
有向图的邻接矩阵表示法
(1)在有向图的邻接矩阵中,第i行为以vi为尾的弧(出度边), 第i列为以vi为头的弧(入度边)。
(2)有向图的邻接矩阵不一定对称。
(3)顶点的入度=第i行元素之和,顶点的出度=第i列元素之和。
网(有权图)的邻接矩阵表示法
代码表示为:
#include<stdio.h>
#include<stdlib.h>
#define MAXint 32767
#define MVNUM 100
typedef struct {
char v[MVNUM];
int Arcs[MVNUM][MVNUM];
int vex, arc;
}GRAPH;
采用邻接矩阵创建无向网
(1)输入总顶点数和总边数。
(2)依次输入点的信息存入顶点表中。
(3)初始化邻接矩阵,使每个权值初始化为最大值。
(4)构造邻接矩阵。
算法表示:
#include<stdio.h>
#include<stdlib.h>
#define MAXint 32767
#define MVNUM 100
typedef struct {
char v[MVNUM];
int Arcs[MVNUM][MVNUM];
int vexnum, arcnum;
}GRAPH;
void CreateGRAPH(GRAPH& G) {
scanf_s("%d", &G.vexnum);
scanf_s("%d", &G.arcnum);
char v1, v2, w;
int i, j;
for ( i = 0; i < G.vexnum; i++) {
scanf_s("%c", &G.v[i]);
}
for ( i = 0; i < G.vexnum; i++) {
for (int j = 0; i < G.vexnum; i++)
G.Arcs[i][j] = MAXint;
}
for (int k = 0; k < G.arcnum; k++) {
scanf_s("%c", &v1);
scanf_s("%c", &v2);
scanf_s("%d", &w);
i = LocateVex(G, v1);
j = LocateVex(G, v2);
G.Arcs[i][j] = w;
G.Arcs[j][i] = G.Arcs[i][j];
}
return;
}
int LocateVex(GRAPH& G, char c) {
int id = 0;
while (G.v[id] != c) {
id++;
}
return id;
}
有向网不一定为对称矩阵,所以不 用G.Arcs[j][i]=G.Arcs[i][j]进行赋值
邻接表
顶点:按编号顺序将顶点数据存储在一维数组中。
关联同一顶点的边(以顶点为尾的弧):用线性链表存储。
adjvex:邻接点域,存放与vi邻接的顶点在表头数组中的位置
nextarc:链域,指示下一条边或弧。
(1)邻接表不唯一
(2)若无向图有n个顶点,e条边,则其邻接表需要n个头结点和2e个表结点。
(3)无向图中点vi的度为第i个单链表中的结点数。
对于有向图:
找出度容易,找入度难
特点:
(1)顶点vi的出度为第i个单链表中的结点个数。
(2)顶点vi的入度为整个单链表中邻接点域值是i-1的结点个数。
逆邻接表
找入度容易,找出度难
特点:
(1)顶点vi的入度为第i个单链表中的结点个数。
(2)顶点vi的出度为整个单链表中邻接点域值是i-1的结点个数。
采用邻接表创建无向网
(1)输入总顶点数和边数。
(2)建立顶点表,依次输入点的信息存入顶点表中,使每个表头结点的指针域初始化为NULL。
(3)创建邻接表,依次输入每条边依附的两个顶点,确定两个顶点的i和j,建立边结点,将次边结点分别插入到vi和vj对应的两个边链表头部。
算法表示:
#include<stdio.h>
#include<stdlib.h>
#define MVNum 100
typedef struct ArcNode {
int adjvex;
struct ArcNode* nextarc;
}ArcNode;
typedef struct VNode {
char data;
ArcNode* firstarc;
}VNode,AdjList[MVNum];
typedef struct {
AdjList ver;
int vexnum, arcnum;
}ALGraph;
void CreateUDG(ALGraph& G) {
scanf_s("%d", G.vexnum);
scanf_s("%d", G.arcnum);
int i, j;
char v1, v2;
for ( i = 0; i < G.vexnum; i++) {
scanf_s("%c", &G.ver[i].data);
G.ver[i].firstarc = NULL;
}
for (int k = 0; k < G.arcnum; k++) {
scanf_s("%d",&v1 );
scanf_s("%d",&v2 );
i = LocateVex(G, v1);
j = LocateVex(G, v2);
ArcNode *p1 = (ArcNode*)malloc(sizeof(ArcNode));
p1->adjvex = j;
p1->nextarc = G.ver[i].firstarc;
G.ver[i].firstarc = p1;
ArcNode* p2 = (ArcNode*)malloc(sizeof(ArcNode));
p2->adjvex = i;
p2->nextarc = G.ver[j].firstarc;
G.ver[j].firstarc = p2;
}
return;
}
邻接表特点:
1.方便找任一顶点的所有邻接点。
2.节约稀疏图空间,需要N个头指针和2E个结点(每个结点至少两个域)。
3.对无向图,方便计算任一顶点的度,对有向图,方便计算出度。
4.不方便检查任意一对顶点间是否有边。
邻接矩阵与邻接表的关系
联系:邻接表中每个链表对应邻接矩阵中的一行,链表中结点个数等于一行中非零元素的个数。
区别:(1)对于任意确定的无向图,邻接矩阵唯一,但邻接表不唯一。
(2)邻接矩阵的空间复杂度为O(n^2),而邻接表的空间复杂度为O(n+e)。
用途:邻接矩阵多用于稠密图,邻接表多用于稀疏图。
十字链表和邻接多重表
十字链表
十字链表(Orthogonal List)是有向图的另一种链式存储结构。我们也可以把它看成是将有向图的邻接表和逆邻接表结合起来形成的一种链表。有向图中的每一条弧对应十字链表中的一一个弧结点,同时有向图中的每个顶点在十字链表中对应有一个结点,叫做顶点结点。
邻接多重表
邻接表优点:容易求得顶点和边的信息。缺点:某些操作不方便(如:删除一条边需找表示此边的两个结点)。则对无向图可有如下邻接多重表。