图
图是由一个顶点集V和一个弧集R构成的数据结构,V是顶点的有穷非空集合;R是两个顶点之间的关系的集合
有向图
若<v,w> ∈ VR,则<v,w>表示从顶点v到顶点w的一条弧,并称v为弧尾或起始点, 称w为弧头或终端点。由于图中的“弧”是有方向的,因此称由顶点集和弧集构成的图为有向图(Digraph)

无向图
若<v, w> ∈ VR, 且有<w, v> ∈ VR, 即VR是对称关系, 则以无序对(v, w)代替这两个有序对, 表示v和w之间的一条边,此时由顶点集合和边集合构成的图称作无向图(Undigraph)

完全图、稀疏图与稠密图
-
对于无向图,其边数e的取值范围是0~n(n-1)/2
若图中每个顶点都和其余n-1个顶点有边相连,则有n(n-1)/2条边的无向图称为无向完全图
-
对于有向图,其弧数e的取值范围是0~n(n-1)
若图中每个顶点和其余(n-1)个顶点都有弧相连,即有(n-1)条弧的有向图为称为有向完全图
-
对于有很少条边或弧的图称为稀疏图,反之称为稠密图
网
弧或边带权的图通常称为网,又分为有向网和无向网
子图
图的一部分
邻接点
对于无向图G=(V, {E}),如果边(v, v’)∈E,则称顶点v和顶点 v’互为邻接点,即v, v’相邻接。边(v, v’)与顶点v和顶点v’相关联
对于有向图G=(V, {A}),如果弧<v, v’>∈A,则称顶点v邻接到顶点v’, 弧<v, v’>与顶点v和顶点v’相关联
度、入度和出度
对于无向图,顶点v的度是指与顶点v相关联的边的数目,记作TD(v);
对于有向图,顶点v的度有出度和入度两部分,其中:以顶点v为弧头的弧的数目称为顶点v的入度,记作ID(v),以顶点v为弧尾的弧的数目称为顶点v的出度,记作OD(v),顶点v的度为TD(v) = ID(v) + OD(v)
一般地,若图G中有n个顶点,e条边或弧,则图中顶点的度与边的关系如下

路径、回路
从顶点v到v’的顶点序列,路径的长度是指路径上经过的弧或边的数目
若表示路径的顶点序列中的顶点各不相同,则称这样的路径为简单路径。
除了第一个顶点和最后一个顶点之外,其余各顶点均不重复出现的回路称为简单回路或简单环
连通图、连通分量(无向图)
在无向图G=(V, {E})中,若从顶点v_i到顶点vj有路径相通,则称顶点v_i与v_j是连通的。如果对于图中任意两个顶点v_i、v_j ∈V,v_i和v_j都是连通的,则称该无向图G为连通图
无向图中的极大连通子图称为该无向图的连通分量
-
任何连通图的连通分量只有一个, 即其自身
-
若无向图为非连通图, 则图中各个极大连通子图称作此图的连通分量。非连通的无向图有多个连通分量

强连通图、强连通分量
有向图G=(V, {A})中, 若对于每一对顶点v_i, v_j∈V且≠v_iv_j,从v_i到v_j和从v_j到v_i都有路径,则称该有向图G为强连通图。
有向图中的各个极大强连通子图称为G的强连通分量
生成树、生成森林
一个连通图的生成树是一个极小连通子图, 它含有图中全部 n个顶点, 但是只有足以构成一棵树的(n-1)条边
-
一棵有n个顶点的生成树有且仅有(n-1)条边。
-
如果在一棵生成树上添加一条边,必定构成一个环。因为这条边使得它依附的那两个顶点之间有了第二条路径。
-
如果一个n个顶点的图有小于(n-1)条边,则是非连通图;如果它多于(n-1)条边,则一定有环。
-
非连通图中的各个连通分量的生成树的集合称为此非连通图的生成森林
-
如果一个有向图恰好有一个顶点的入度为0, 其余顶点的入度均为1, 则是一棵有向树
-
一个有向图的生成森林由若干棵有向树组成,含有图中全部顶点,但是只有足以构成若干棵不相交的有向树的弧
7.2 图的存储结构
7.2.1 数组存储表示法
用一个一维数组存储图中的顶点的信息,用一个二维数组存储图中顶点之间的关系
邻接矩阵:表示顶点之间相互关系(边或弧的信息)的矩阵,邻接矩阵用二维数组表示,需要存储n^2个弧或者边的信息。对于无向图, 它的邻接矩阵是对称矩阵, 可以只存储其下三角(或上三角)的元素即可

图的数组(邻接矩阵)存储表示
1 #define MAX_NAME 5 // 顶点名称——字符串的最大长度+1 2 typedef int VRType;//VRType是顶点之间的关系(边或弧的信息)类型 3 typedef char InfoType; 4 typedef char VertexType[MAX_NAME];//一维字符数组类型名VertexType 5 #define INFINITY INT_MAX //用整型最大值INT_MAX代替∞ 6 #define MAX_VERTEX_NUM 20 //设最大顶点个数为20 7 typedef enum{DG, DN, UDG, UDN} GraphKind; //有向图、有向网、无向图、无向网 8 9 typedef struct ArcCell //弧或边信息 10 { VRType adj; //VRType是顶点之间关系类型。 11 //对无权图, 用1或0表示相邻否; 对带权图, 则为权值类型。 12 InfoType *info;//“谓词 P(v, w) ”该弧相关信息的指针 (可无) 13 }ArcCell, AdjMatrix[MAX_VERTEX_NUM] [MAX_VERTEX_NUM]; 14 //二维数组的元素(弧或边) 的类型名ArcCell, 二维数组类型名AdjMatrix 15 16 typedef struct { 17 VertexType vexs[MAX_VERTEX_NUM]; //一维数组(顶点信息是字符串) 18 AdjMatrix arcs; // 二维数组arcs, (弧或边的) 19 int vexnum, arcnum; //图的当前的顶点数和当前的弧数 20 GraphKind kind;//图的种类标志 21 } MGraph;
图的创建
1 int LocateVex(MGraph G, VertexType u) 2 { //初始条件: 图G存在, u和G中顶点有相同特征 3 //操作结果: 若G中存在顶点u, 则返回该顶点在图G中的位置; 否则返回-1 4 int i; 5 for(i=0; i<G.vexnum; ++i) if(strcmp(u, G.vexs[i])==0) return i; 6 return -1; 7 } 8 9 Status CreateGraph( MGraph & G ) 10 { cin>>G.kind ; 11 switch(G.kind) 12 { case DG: return CreateDG(G); //构造有向图G 13 case DN: return CreateDN(G); //构造有向网G 14 case UDG: return CreateUDG(G); //构造无向图G 15 case UDN: return CreateUDN(G); //构造无向网G 16 default: return ERROR; 17 } 18 } 19 20 Status CreateUDN ( MGraph & G ) //采用数组表示法, 构造无向网G 21 { cin>>G.vexnum >>G.arcnum>>IncInfo;//IncInfo为0则各弧不含其他信息 22 for(i=0; i<G.vexnum; ++i) cin>>G.vexs[i] ;//输入顶点信息 for(i=0; i<G.vexnum; ++i) //二维数组初始化 23 for(j=0; j<G.vexnum; ++j) G.arcs[i][j] = {INFINITY,NULL}; 24 for(k=0; k<G. arcnum; ++k)//输入各条边的信息 25 { cin>>v1>>v2 >>w;//输入该条边所依附的顶点 及权值 26 i=LocateVex(G, v1); j=LocateVex(G, v2);//找到v1和v2的位置 27 G.arcs[i][j].adj=w;//边(v1, v2)的权值 28 if(IncInfo) Input(*G.arcs[i][j].info);//若边含相关信息,则创建和输入 29 G.arcs[j][i]=G.arcs[i][j];//置<v1,v2>的对称弧<v2,v1> 30 } 31 return OK; 32 }
缺点:对于稀疏图来说比较浪费空间
优点:便于判定图中任意两个顶点之间是否有边(弧)相连;便于求各个顶点的度;便于找顶点的邻接点等
7.2.2 邻接矩阵存储表示法
无向图的邻接表
先构建一个无向图的顶点结点的一维数组(顺序表),之后对无向图中的每个顶点结点(单链表头结点)建立一个单链表,第i个单链表中的结点用于表示与顶点V_i(单链表头结点)相关联的边

有向图的邻接表
第i个单链表中的结点表示以顶点V_i为弧尾的弧

逆邻接表
引入原因:邻接表若要求得第i个顶点的入度(入弧个数),则需要搜索所有的单链表,比较麻烦,解决方法
第i个单链表中的结点表示以顶点V_i为弧头的入弧

邻接表的单链表的出弧结构
1 //邻接链表结点(出弧结点) 2 typedef struct ArcNode 3 { int adjvex; //该出弧的弧头顶点的位置 4 struct ArcNode *nextarc; //指向下一条出弧的指针 5 InfoType *info; //该出弧由“谓词” 定义的信息的指针 6 } ArcNode; //出弧结点类型 7 //顶点结点 8 typedef struct VNode 9 { VertexType data; //顶点信息 10 ArcNode *firstarc; // 存第1条出弧结点地址 11 } VNode, AdjList[MAX_VERTEX_NUM]; 12 //图 13 typedef struct 14 { AdjList vertices; //一维数组变量vertices 15 int vexnum, arcnum; //图的当前顶点数和弧数 16 int kind; //图的种类标志 17 } ALGraph;

7.2.3 有向图的十字链表存储表示法
是将有向图的邻接表和逆邻接表结合起来的一种链表
弧结点的结构

1 typedef struct ArcBox //弧结点的结构 2 {int tailvex, headvex; //该弧的弧尾和弧头的顶点位置 3 struct ArcBox *hlink, *tlink; //含义见上面框内 4 InfoType *info; //该弧相关信息的指针(可无) 5 } ArcBox ; //弧结点的类型名: ArcBox
顶点结点的结构

1 typedef struct VexNode //顶点结点的结构 2 { VertexType data; //顶点信息 3 ArcBox *firstin, *firstout; 4 //分别指向该顶点的第一条入弧和出弧 5 } VexNode; //顶点结点的类型名: VexNode
有向图的十字链表表示

1 typedef struct { 2 VexNode xlist[MAX_VERTEX_NUM]; //顶点结点(表头向量) 3 int vexnum, arcnum; //有向图的当前顶点数和弧数 4 } OLGraph;
逆位序构建十字链表存储


1 Status CreateDG( OLGraph &G ) 2 { //采用十字链表存储表示,构造有向图G(G.kind=DG)。 3 int IncInfo; ArcBox * p; VertexType v1, v2; 4 cin>>G.vexnum>>G.arcnum>>IncInfo ; // IncInfo为0则各弧不含其他信息 5 for(i=0; i<G.vexnum; ++i) //构造表头向量 6 { cin>>G.xlist[i].data; //输入各个顶点名字字符串 7 G.xlist[i].firstin=NULL; 8 G.xlist[i].firstout=NULL; //初始化指针 9 } 10 ---------------------------------------------------------------- 11 for(k=0; k<G.arcnum; ++k) //输入各条出弧,并构造十字链表 12 { cin>>v1>>v2; //输入一条出弧的弧尾和弧头 13 i=LocateVex(G, v1); j=LocateVex(G, v2); //确定v1和v2在G中位置 14 p= new ArcBox;//创建弧结点 15 p->tailvex=i; p->headvex=j; //对弧结点的关联顶点的位置赋值 16 p->hlink=G.xlist[j].firstin; //分别完成入弧和出弧在链表表头的插入 17 p->tlink =G.xlist[i].firstout; 18 G.xlist[j].firstin=G.xlist[i].firstout=p; 19 if(IncInfo) Input(*p->info); //若弧含有相关信息,则输入 20 } 21 return OK; 22 }
7.2.4 无向图的邻接多重表存储表示法
边结点的存储结构

-
mark为标志域, 可用以标记该条边是否被搜索过;
-
ivex和jvex为该边依附的两个顶点在顶点结点数组的位置;
-
ilink用于指向下一条依附于顶点ivex 的边;
-
jlink用于指向下一条依附于顶点jvex 的边;
-
info为指向与边相关的谓词信息的指针域。
1 typedef struct EBox 2 { VisitIf mark; //访问标记 3 int ivex, jvex; //该边依附的两个顶点在顶点结点数组的位置 4 struct EBox * ilink, *jlink; //分别指向依附这两个顶点的下一条边 5 InfoType *info; //该边的谓词信息的指针 6 } EBox ;
顶点结点的结构表示

data域存储顶点的信息;firstedge用于指向第一条依附于该顶点的边。
1 typedef struct VexBox 2 { VertexType data; //用于存储顶点的名字 3 EBox * firstedge; //用于指向第一条依附该顶点的边 4 }VexBox;
邻接多重表存储表示

1 typedef struct 2 { VexBox adjmulist[ MAX_VERTEX_NUM ]; 3 int vexnum, edgenum; //无向图的当前顶点数和边数 4 } AMLGraph ;
7.3 图的遍历
7.3.1 深度优先搜索算法
基本思想(类似于树的先根遍历算法)
-
从图的某一个顶点v_0出发,访问此顶点v_0
-
然后依次从v_0的未被访问的邻接点出发深度优先搜索遍历图,直至图中所有的与v_0有路径相通的顶点都被访问到
-
若此时图中还有顶点未被访问,则另选图中一个未曾被访问的顶点作为起始点,重复上述深度优先搜索过程;直到图中所有顶点都被访问到为止
1 void DFS(Graph G, int v) 2 { //连通图,从第v个顶点出发,深度优先搜索遍历图G。 3 int w; 4 VisitFunc( G.vexs[v] ); //访问第v个顶点 5 visited[v]=TRUE; //设置第v个顶点的访问标志为TRUE(已访问) 6 for(w=FirstAdjVex(G, v); w>=0; w=NextAdjVex(G, v, w); 7 if(!visited[w]) DFS(G, w); //对v的尚未访问的序号为w的邻接顶点递归调用DFS 8 } 9
7.3.2 广度优先搜索算法
基本思想(类似于树的层次遍历)
-
从图中某个顶点v_0出发,首先访问此顶点v_0。
-
然后依次访问v_0的所有各个未曾被访问的邻接点。
-
分别从这些邻接点出发,依次访问它们的各个未被访问的邻接点。访问时应保证:如果v_i和v_k为v_0的邻接点,v_i在v_k之前被访问, 则v_i的所有未被访问的邻接点应在v_k的所有未被访问的邻接点之前访问。
-
重复3,直到所有结点均没有未被访问的邻接点。
-
若此时图中尚有顶点未被访问,则另选图中一个未曾被访问的顶点作起始点,重复上述过程。直到图中所有顶点都被访问到为止

1 void BFSTraverse(MGraph G, Status(*Visit)(VertexType)) 2 { //初始条件: 图G存在, Visit是指向函数的指针变量。 3 //操作结果: 从第1个顶点起,按广度优先非递归遍历图G,并对每个顶点调用函数 4 //Visit一次且仅一次。一旦Visit()失败, 则操作失败。 5 //使用辅助队列Q 和 访问标志数组visited 6 int v, u, w; LinkQueue Q; 7 for(v=0; v<G.vexnum; v++) visited[v]=FALSE; //访问标志数组置0 8 InitQueue(&Q); //将辅助队列Q 置空 9 for(v=0; v<G.vexnum; v++) 10 if (!visited[v]) 11 { Visit(G.vexs[v]); visited[v]=TRUE; EnQueue( &Q, v ); 12 ----------------- 13 while( !QueueEmpty(Q) ) 14 15 { DeQueue(&Q, &u); //队头元素出队并存放到u中 16 17 for(w=FirstAdjVex( G, u); w>=0; w=NextAdjVex(G, u, w)) 18 if(!visited[w]) { Visit(G.vexs[w]); visited[w]=TRUE; EnQueue(&Q, w); } 19 } 20 ------------------ 21 } 22 }
7.4 构造最小生成树
各边的权值之和最小
7.4.1 普里姆算法
基本思想
取有n个顶点的图中的任意一个顶点 v 作为生成树的根,之后往生成树上添加新的顶点 w。然后继续往生成树上添加顶点,直至生成树上含有 n-1 条边为止;所添加的顶点应该满足的条件:
在生成树的构造过程中,图中 n 个顶点分属两个集合:已落在生成树上的顶点集 U 和尚未落在生成树上的顶点集V-U 。应在所有连通U中顶点和V-U中顶点的边中选取权值最小的边。

7.4.2 克鲁斯卡尔算法
基本思想
假设有连通网N=(V,{E})
-
首先构造一个初始状态为只含n个顶点而无边的非连通图ST=(V,{ }), ST中每个顶点自成一个连通分量
-
然后从权值最小的边开始,在{E}中选取权值最小的边,若它的添加不使ST中产生回路,则在 ST上加上这条边,如此重复,直至加上 n-1 条边(此时ST中所有顶点都在同一连通分量上)为止


浙公网安备 33010602011771号