本篇进入图的学习,继前篇树之后,将学习比树更加复杂的结构。有向图无向图或者是否闭合,都有广泛的应用。关于图在一些实际应用中的例子,本篇会给几个例子。(插图较多)
看完本篇,你将了解到:
(1)什么是图?什么是网?图的常用术语有哪些?
(2)图的相关操作(类比于树和线性表)
(3)图的表示方法(同样也有数组和链表两种),本篇将新介绍一个邻接表
(4)将重点讨论有向图和无向图的表示方法
(5)重点!!!图的遍历(常听到的深度优先搜索DFS及广度优先搜索BFS)
在线性表中,数据元素为一对一的关系,在图中,任意两个数据元素都可能相关
(1)图G由顶点集V和关系集VR组成的二元组,记为:G=(V,VR)
(2)V(具有相同特征的数据元素的集合):图中的数据元素我们通常称为顶点,因此V也称为顶点(元素)的有穷非空集
(3)VR:两个顶点之间关系的集合
(1)若图G任意两顶点a,b之间的关系为有序对,即∈VR,则称为从a到b的一条弧/有向边
(2)其中:a是的弧尾(初始点)
b是的弧尾(终止点)
(3)有向图:由顶点集合弧的集合组成的图
如果顶点间的关系是无序对,ab之间用()表示,称无序对表示顶点a与b的一条边
(1)无向图:由顶点集合与边的集合组成的图
(2)图中若a、b间有边,则称(a,b)表示a、b互为邻接点,(a,b)依附于a和b,(a,b)与a和b相关联
例:G2={V,VR}
V={V1,V2,V3,V4,V5,V6}
VR={(V1,V3),(V1,V5),(V3,V5),(V4,V6)}
(1)n:顶点的数目
(2)e:边或者弧的数目。
取值范围:0–n(n-1)/2 (若任意两个顶点间都有边,则e取最大值)
(3)有n个顶点和n(n-1)/2条边的无向图称为完全图
有n个顶点和n(n-1)条弧的有向图
边(弧)上加权的图,分为有向网和无向网
故有4种类型的图:有向图、无向图、有向网、无向网
(1)子图:对图G=(V,VR)和G1=(V1,VR1),若V1是V的子集且VR1是VR的子集,则称G1是G的一个子图
G1,G2,G3均为G的子图,但G4不是
(2)度:无向图中与顶点v相关联的边(x,y)的数目,称为v的度
记作TD(v)或D(v)
无向图某顶点的度表示:该顶点有多少个邻接顶点
①出度OD(v):以顶点v为弧尾的弧的数目
②入度ID(v):以顶点v为弧头的弧的数目
例:OD(A)=1
OD(B)=2
OD(C)=0
ID(A)=1
ID(B)=1
ID(C)=1
顶点的度:
TD(A)=OD(A)+ID(A)=2
TD(B)=OD(B)+ID(B)=3
TD(C)=OD(C)+ID(C)=1
(3)连通性的术语
顶点vi到vj有路径:存在一个顶点序列vi,vi1,vi2,…,vim,vj
其中(vi,vi1),(vi1,vi2),…,(vim,vj)是图的边或弧
(4)连通图及其分量(无向图G)
①若从顶点vi到vj有路径,则称二者是连通的
②连通图:图G中任意两顶点是连通的
③连通分量:无向图的极大连通子图
注:连通图的连通分量是自己,非连通图会有几个连通分量
(5)强连通图及强连通分量(有向图G)
①强连通图:图G中每对顶点vi,vj之间,从vi到vj,陈vj到vi都存在路径
②强连通分量:有向图的极大强连通子图
注:强连通图的强连通分量是自己
(6)生成树
连通图的极小连通子图 ,包含图中所有顶点和n-1条边
(1)CreateCraph(&G,V,VR):根据顶点集V和关系集VR生成图
(2)DestroyCraph(&G):销毁图
(3)Locate(G,v):查找顶点u的位置
(4)GetVex(G,v):读取顶点v的信息
(5)PutVex(&G,v,value):给顶点v赋值
(6)FirstAdjVex(G,v):读v的第一个邻接点
(7)NextAdjVex(G,v,w):读v(相当于w)的下一个邻接顶点
(8)InsertVex(&G,v):插入顶点
(9)DeleteVex(&G,v):删除顶点
(10)InsertArc(&G,v,w):插入弧
(11)DeleteArc(&G,v,w):删除弧
(12)DFSTraverse(G,visit()):深度优先遍历图
(13)BFSTraverse(G,visit()):宽度优先遍历图
…
(1)数组表示法:用两个数组分别存储数据元素(顶点)的信息和数据元素之间的关系
(2)顶点数组:用一维数组存储顶点(元素 )
(3)邻接矩阵:用二维数组存储顶点(元素)之间的关系(边或弧)
①将4个顶点依次存放到顶点数组,保存所有顶点的信息
顶点数组vexs v1 v2 v3 v4
0 1 2 3
②用邻接矩阵(arcs)表示二者之间的关系:1表示顶点之间有关系,0表示顶点之间无关系
位置序号分别为i、j,若顶点vi和vj之间有边,则矩阵中aij和aji均赋值为1,否则为0
注:结论:
①无向图的邻接矩阵是对称矩阵
②易求顶点vi的度:第i行或第i列上的数字之和
①将3个顶点依次存放到顶点数组,保存所有顶点的信息
顶点数组vexs A B C
0 1 2
②用邻接矩阵(arcs)表示二者之间的关系:1表示顶点之间有关系,0表示顶点之间无关系
位置序号分别为i、j,若顶点vi和vj之间有弧,则矩阵中aij和aji均赋值为1,否则为0
注:结论:
①有向图的邻接矩阵不一定是对称矩阵
②求顶点vi的度:累加第i行元素,得初度OD(vi)
累加第i列元素,得入度OD(vi)
度 = 初度+入度
(4)网
1)对于有向网和无向网,需要在邻接矩阵中加上关系的权值,如果两个顶点有邻接关系,
就用权值代替原来的1,否则就用∞代替0
例:如图
求度时,统计第i行(列)上非无穷大的数的个数
(5)数据类型定义
①约定图中可出现的顶点数目最大值(考虑到数组大小要合适)
②存放顶点数组、邻接矩阵、顶点数、图的类型、弧(边)数
//数据类型定义
#define MAX_VERTEX_NUM 20
typedef enum
{
DG,DN,UDG,UDN
}GraphKind;
typedef struct
{
VertexType vexs[MAX_VERTEX_NUM];
VRType arcs[MAX_VERTEX_NUM][MAX_VERTEX_NUM];
int vexnum,arcnum;
GraghKind kind;
}Mgraph;
顺序+链式的物理存储结构,通过头结点数组保存顶点信息,用单链表保存顶点之间的关系
(1)无向图的邻接表
①为图G的每个顶点建立一个单链表,第i个单链表中的结点表示依附于顶点vi的边
②头结点数组,保存每一个顶点:(2个部分)顶点值+单链表头指针 (指向1个由所有邻接顶点的序号构成的单链表)
③若无向图G有n个顶点和c条边,需n个表头结点和2e个表结点
④无向图G的邻接表,顶点vi的度=第i个单链表的长度
(2)有向图的邻接表
①第i个单链表中的表结点的值为j,表示以顶点vi为尾的一条弧(vi,vj)
②若有向图G有n个顶点和e条弧,需n个表头结点和e个表结点
③顶点vi的入度:遍历全部单链表,统计结点值为i的结点数
④顶点vi的出度=第i个单链表的长度
(3)有向网的邻接表
表结点表示边或弧,就对表结点扩充一个属性域,至少包含:顶点序号、权值、下一表结点指针
(4)有向网的逆邻接表
①若vi到vj有一条弧,使第j个单链表中的表结点的值为j,表示以顶点vi为尾的一条弧
②若有向图G有n个顶点和e条弧,需n个表头结点和e个表结点
③顶点vi的出度:遍历全部单链表,统计结点值为i的结点数
④点vi的入度=第i个单链表的长度
数据类型定义
//邻接表表示法的数据类型定义
#define 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;//顶点数、弧数
GraphKind kind;//图的类型
}ALGraph;
(1)每条弧有一个弧结点,若vi到vj有弧,则在结点中包括
弧结点: tailvex headvex hlink tlink
其中:tailvex:弧尾的位置
headvex:弧头的位置
hlink:指向下一条弧头相同(vj)的弧
tlink:指向下一条弧尾相同(vi)的弧
(2)每个顶点有一个顶点结点(通过顶点的结点数组保存有向图的顶点信息)
顶点结点: data firstin firstout
其中:data:顶点信息
firstin:指向以该顶点为弧头的第一条弧
firstout:指向以该顶点为弧尾的第一条弧
//有向图十字链表存储表示的数据类型定义
#define MAX_VERTEXT_NUM 20
typedef struct ArcBox
{
int tailvex,headvex;//该弧的尾和头顶点的位置
struct ArcBox *hlink,*tlink;//分别为弧头相同和弧尾相同的弧的链域
}ArcBox;
typedef struct VexNode
{
VertexType data;
ArcBox *firstin,*firstout;//分别指向该顶点的第一条入弧和出弧
}VexNode;
typedef struct
{
VexNode xlist[MAX_VERTEXT_NUM];//表头向量
int vexnum,arcnum;//有向图的当前顶点数和弧数
}OLGraph;
①以邻接表为基础,扩展结点属性成起止结点序号
②再添加逆邻接表信息
(1)每个顶点有一个头结点
头结点: data firstedge
其中:data:顶点信息
firstedge:指向第一条依附于该顶点的边
(2)每一条边有一个表结点
表结点:mark ivex jvex ilink jlink
其中:mark:标志域,可用以标记该条边是否被搜索过
ivex、jvex:该条边依附的两个顶点在顶点数组的位置
ilink:指向下一条依附于顶点vi的边
jlink:指向下一条依附于顶点vj的边
//(无向图)邻接多重表
#define MAX_VERTEXT_NUM 20
typedef enum {
unvisited,visited} visitedlf;
typedef struct EBox
{
visitedlf mark;//访问标记
int jvex,jvex;//该边依附的两个顶点的位置
struct EBox *ilink,*jlink;//分别指向依附于这两个顶点的下一条边
}EBox;
typedef struct VexBox
{
VertexType data;
EBox *firstedge;//指向第一条依附于该顶点的边
}VexBox;
typedef struct
{
VexBox adjmullist [MAX_VERTEXT_NUM];
int vexnum,edgenum;//无向图的当前顶点数和边数
}AMLGraph;
(1)
eg:以A开头,访问其一个邻接结点E,依次访问到H,发现H没有未被访问的邻接结点
则回退到F,F也没有,一路回退至A,从A继续访问B,一路访问至C,再一路回退到A
此时全部访问完毕
本次访问序列为:AEGFHBDC
注:由于改图是连通图,故一次DFS即可访问完,否则需深度优先搜索多次
//深度优先搜索遍历算法代码(假定结点序号从0开始)
boolean visited[MAX];
void DFSTraverse(Graph G,Status (*visit()))
{
for (int v=0;v<G.vexnum;v++)//初始化各顶点未访问状态
{
visited[v] = false;
}
for (int v=0;v<G.vexnum;v++)
{
if (!visited[v])//从一个未访问的顶点开始
DFS(G,v,visit);
}
}
void DFS(Graph G,int v,Status (*visit()))//递归算法
{
visited[v] = true;
visit(v);
for (w=FirstAdjVex(G,v),w>=0;w=NextAdjVex(G,v,x))
{
if (!visited[w])//处理所有未访问的邻接顶点
DFS(G,w,visit);
}
}
(2)该算法的效率与图的存储结构有关
邻接矩阵:T(n)=O(n^2)
邻接表: T(n)=O(n+e)(找邻接点需O(e))
eg:从A出发,访问其所有相邻结点EFB,按这个次序访问E的所有未被访问的结点G,其次是FB
序列:AEFBGHDC
//广度优先搜索遍历算法
void BFSTtaverse(Graph G,Status (*visit()))
{
for (v=0;v<G.vexnum;v++)
{
visited[v]=false;//初始化visited数组为false
}
InitQueue(Q);
for (v=0;v<G.vexnum;v++)//按订单位置序号依次选择顶点
{
if (!visited[v])//遇到为访问过的顶点开始遍历
{
visited[v] = true;//首先访问并标记起点v
visit(v);
EnQueue(Q,v);//将v加入到q中
while (!QueueEmpty(Q))//若队列非空
{
DeQueue(Q,u);//出队
for (w=FirstAdjVex(G,u),w>=0;w=NextAdjVex(G,u,w))//对u的所有相邻结点分析
{
if (!visited[w])//若某个相邻结点未被访问
{
visited[w] = true;//则访问并标记
visit(w);
EnQueue(Q,w);//加入到q中
}
}
}
}
}
}
(2)该算法的效率与图的存储结构有关
1.图的相关术语定义比较复杂,重点在有向图和无向图,下篇中会详细讲到其应用。
2.图的操作可类比于树和线性表去记忆,大体结构相同。
3.掌握基本操作后,图的存储结构,针对有向图和无向图,我们采取了不同的方法来节省空间和时间。
4.图的遍历,深度优先搜索DFS和广度优先搜索BFS采取不同的结构有很多实现方法,这里仅给出其中一种。这两种算法是经典且常用的算法,建议多看看不同的代码。
5.尤其关注连通图这个概念。
如有错误,欢迎指正!
代码非原创。
(ps:一不小心又咕咕了半个多月)