数据结构——图
图的定义和基本术语
图:G=(V,E) Grapg = (Vertex, Edge)
V:顶点(数据元素)的有穷非空集合;
E:边的有穷集合
无向图:每条边都是无方向的
有向图:每条边都是有方向的
完全图:任意两点都有一条边相连
稀疏图:有很少边或弧的图(e < nlogn)
稠密图:有较多边或弧的图
网: 边/弧带权的图
邻接: 有边/弧相连的两个顶点之间的关系
存在(vi,vj),则称vi和vj互为邻接点;
存在<vi,vj>,则称vi邻接到vj,vj邻接于vi
关联(依附):边/弧与顶点之间的关系
顶点的度:与该顶点相关联的边的数目,记为TD(v)
在有向图中,顶点的度等于该顶点的入度与出度之和
顶点 v 的入度是以 v 为重点的有向边的条数,记作 ID(v)
顶点 v 的出度是以 v 为始点的有向边的条数,记作 OD(v)
路径:接续的边构成的顶点序列
路径长度:路径上边或弧的数目/权值之和
回路(环):第一个顶点和最后一个顶点相同的路径
简单路径:除路径起点和终点相同外,其余顶点均不相同的路径
连通图(强连通图)
有无(有)向图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的强连通分量
极大强连通子图意思是:该子图是G的强连通子图,将D的任何不在该子图中的顶点加入,子图不再是强连通的
极小连通子图:该子图是G的连通子图,在该子图中删除任何一条边,子图不再连通
生成树:包含无向图G 所有顶点的极小连通图
生成森林:对非连通图,由各个连通分量的生成树的集合
图的类型定义
图的抽象数据类型定义如下:
ADT Graph{
数据对象V:具有相同特性的数据元素的集合,称为顶点集
数据关系R:R = {VR}
VR = {<v,w>|<v,w>| v,w∈V ^ p(v,w),
<v,w>表示从v到w的弧,p(v,w)定义了弧<v,w>的信息
}
CreateGraph(&G, V, VR)
初始条件:V是图的顶点集,VR是图中弧的集合
操作结果:按V和VR的定义构造图G
DFSTraverse(G)
初始条件:图G存在
操作结果:按图进行深度优先遍历
BFSTraverse(G)
初始条件:图G存在
操作结果:对图进行广度优先遍历
}ADT Graph
图的存储结构
图的逻辑结构:多对多
数组(邻接矩阵表示法)
- 建立一个顶点表(记录各个顶点信息)和一个邻接矩阵(表示各个顶点之间关系)
- 设图 A = (V,E)有n个顶点,则
- 图的邻接矩阵是一个二维数组 A.arcs[n][n],定义为:
- 设图 A = (V,E)有n个顶点,则
分析1:无向图的邻接矩阵是对称的
分析2:顶点i的度 = 第 i 行(列)中 1 的个数
特别:完全图的邻接矩阵中,对角元素为0,其余为1
注:在有向图的邻接矩阵中,
第 i 行含义:以结点vi为尾的弧(即出度边)
第 i 列含义:以结点vi为头的弧(即入度边)
分析1:有向图的邻接矩阵可能是不对称的
分析2:顶点的出度 = 第 i 行元素之和
顶点的入度 = 第 i 列元素之和
顶点的度 = 第 i 行元素之和 + 第 i 列元素之和
网(即有权图)的邻接矩阵表示法
邻接矩阵的存储表示:用两个数组分别存储顶点表和邻接矩阵
#define MaxInt 32767 //表示极大值,即∞
#define MVNum 100 //最大顶点数
typedef char VerTexType; //设顶点的数据类型为字符型
typedef int ArcType; //假设边的权值类型为整型
typedef struct{
VerTexType vexs[MVNum]; //顶点表
ArcType arcs[MVNum][MVNum]; //邻接矩阵
int vexnum, arcnum; //图的当前点数和边数
}AMGraph; // Adjacency Matrix Graph
采用邻接矩阵表示法创建无向网
算法思想:
- 输入总顶点数和总边数
- 依次输入点的信息存入顶点表中
- 初始化邻接矩阵,使每个权值初始化为极大值
- 构造邻接矩阵
//采用邻接矩阵表示法,创建无向网G
Status CreateUDN(AMGraph &G){
cin >> G.vexnum >> G.arcnum; //输入总顶点数,总边数
for(i = 0; i < G.vexnum; ++i)
cin >> G.vexs[i]; //依次输入点的信息
for(i = 0; i < G.vexnum; ++i) //初始化邻接矩阵
for(j = 0; j < G.vexnum; ++j)
G.arcs[i][j] = MaxLnt; //边的权值均置为极大值
//构造邻接矩阵
for(k = 0; k < G.arcnum; ++k){
cin >> v1 >> v2 >> w; //输入一条边所依附的顶点及边的权值
i = LocateVex(G, v1);
j = LocateVex(G ,v2); //确定v1和v2在G中的位置
G.arcs[i][j] = w; //边<v1, v2>的权值置为w
G.arcs[j][i] = G.arcs[i][j]; //置<v1, v2>的对称边<v2, v1>的权值为w
}
return OK;
}//CreateUDN
补充算法:在图中查找顶点
//在图G中查找顶点u,存在则返回顶点表中的下标;否则返回-1
int LocateVex(AMGraph G, VertexType u){
int i;
for(i = 0; i < G.vexnum; ++i)
if(u == G.vexs[i]) return i;
return -1;
}
邻接表
邻接表表示法(链式)
- 顶点:按编号顺序将顶点数据存储在一维数组中
- 关联同一顶点的边(以顶点为尾的弧):用线性链表存储
无向图
特点:
- 邻接表不唯一
- 若无向图中有n个顶点、e条边,则其邻接表需n个头结点和2e个表结点。适宜存储稀疏图
- 无向图中顶点vi的度为第i个单链表中的结点数
有向图
特点:
- 顶点vi的出度为第 i 个单链表中的结点个数
- 顶点vi的入度为整个单链表中邻接点域值是 i - 1 的结点个数(需要遍历整个链表)
特点:
- 顶点vi的入度为第 i 个单链表中的结点个数
- 顶点vi的出度为整个单链表中邻接点域值是 i - 1 的结点个数(需要遍历整个链表)
图的邻接表存储表示
typedef struct VNode{
VerTexType data; //顶点信息
ArcNode * firstarc; //指向第一条依附该顶点的边的指针
}VNode, AdjList[MVNum] //AdjList表示邻接表类型
说明: 例如 AdjList v; 相当于:VNode v[MVNum];
#define MVNum 100 //最大顶点数
typedef struct ArcNode{ //边结点
int adjvex; //该边所指向的顶点的位置
struct ArcNode * nextarc; //指向下一条边的指针
OtherInfo info; //信息域;存储和边相关的信息
}ArcNode;
图的结构定义:
typedef struct{
AdjList vertices; //vertices--vertex的复数
int vexnum, arcnum; //图的当前顶点数和弧数
}ALGraph;
采用邻接表表示法创建无向网
算法思想:
- 输入总顶点数和总边数
- 建立顶点表
依次输入点的信息存入顶点表中
使每个表头结点的指针域初始化为NULL - 创建邻接表
依次输入每条边依附的两个顶点
确定两个顶点的序号 i 和 j,建立边结点
将此边结点分别插入到vi和vj对应的两个边链表的头部
//采用邻接表表示法,创建无向图G
Status CreateUDG(ALGraph &G){
cin >> G.vexnum >> G.arcnum; //输入总顶点数、总边数
for(i = 0; i < G.vexnum; ++i){ //输入各点,构造表头结点表
cin >> G.vertices[i].data; //输入顶点值
G.vertices[i].firstarc = NULL; //初始化表头结点的指针域
}//for
for(k = 0; k < G.arcnum; ++k){ //输入各边,构造邻接表
cin >> v1 >> v2; //输入一条边依附的两个顶点
i = LocateVex(G, v1);
j = LocateVex(G, v2);
p1 = new ArcNode; //生成一个新的边结点*p1
p1 -> adjvex = j; //邻接点序号为j
p1 -> nextarc = G.vertices[i].firstarc;
G.vertices[i]firstarc = p1; //将新结点*p1插入顶点vi的边表头部
p2 = new ArcNode; //生成另一个对称的新的边结点*p2
p2 -> adjvex = i; //邻接点序号为i
p2 -> nextarc = G.vertices[j].firstarc;
G.vertices[j].firstarc = p2; //将新结点*p2插入顶点vj的边表头部
}//for
return OK;
}
邻接矩阵与邻接表表示法的关系
- 联系:邻接表每个链表对应于邻接矩阵中的一行,链表中结点个数等于一行中非零元素的个数
- 区别:
① 对于任一确定的无向图,邻接矩阵是唯一的(行列号与顶点编号一致),但邻接表不唯一(链接次序与顶点编号无关)
② 邻接矩阵的空间复杂度为O(n2),而邻接表的空间复杂度为O(n+e) - 用途:邻接矩阵多用于稠密图;而邻接表多用于稀疏图
十字链表——用于有向图
十字链表(Orthogonal List)是有向图的另一种链式存储结构,可以将它看成有向图的邻接表和逆邻接表结合起来形成的一种链表
有向图中的每一条弧对应十字链表中的一个弧结点,同时有向图中的每个顶点在十字链表中对应有一个结点,叫做顶点结点
邻接多重表(无向图的另一种链式存储结构)
邻接表优点:容易求得顶点和边的信息
缺点:某些操作不方便(如:删除一条边需找表示此边的两个结点)
邻接多重表: