1、图的基本概念 G = (V,E)
无向完全图:n个顶点,n(n-1)/2条边; 有向完全图:n个顶点,n(n-1)条边;
邻接:(vi,vj)=>vi和vj互为邻接点; <vi,vj>=>vi邻接vj,vj邻接vi;
顶点的度:与顶点相关联的边的数目,TD(v); 有向图顶点的度 = 入度 + 出度;
路径:接续的边构成的顶点序列; 路径长度:路径上边或弧的数目/权值之和;
回路:第一个顶点和最后一个顶点相同的路径; 简单路径:除起点和终点其他结点都不相同;
连通图:无向图任意两顶点之间都存在路径; 强连通图:有向图任意两顶点之间都存在路径;
连通分量:无向图的极大连通子图(顶点数目已经最大,再加一个顶点不连通);
强连通分量:有向图的极大连通子图(顶点数目已经最大,再加一个顶点不连通);
极小连通子图:连通子图中删除任一条边子图都不再连通;
生成树:包含所有顶点的极小连通子图; 生成森林:非连通图的各个连通分量的生成树的集合;
2、图的存储结构
逻辑结构:多对多
存储结构:
没有顺序存储,可借助二维数组表示数组元素之间的关系【数组表示法:邻接矩阵】
链式存储:邻接表、邻接多重表、十字链表
(1)邻接矩阵(表示唯一)
无向图:邻接矩阵是对称矩阵;顶点 i 的度 = 第 i 行(列)中1的个数;
有向图:邻接矩阵(不对称)中,vi 出度 = 第 i 行中 1 的个数;vi入度 = 第 i 列中 1 的个数;
网(边上带权值):有边记权值,无边记
结构体定义:用两个数组分别存储顶点表和邻接矩阵;
// 邻接矩阵结构体定义
#define MaxVertexNum 100 // 顶点数目的最大值
#define MaxInt 32767 // 用于邻接矩阵初始化
typedef char VertexType // 设置顶点数据类型
typedef int EdgeType // 设置边数据类型
typedef struct {
VertexType Vex[MaxVertexNum]; // 顶点数组(一维)
EdgeType Edge[MaxVertexNum][MaxVertexNum]; // 边数组(二维)
int vexnum, arcnum; // 图的当前顶点数和弧数
}MGraph;
采用邻接矩阵表示法创建无向网、无向图、有向网、有向图(无向图+有向网)
算法思想:
输入总顶点数和总边数;
依次输入顶点信息存入顶点表中;
初始化邻接矩阵,是每个权值初始化为极大值;
构造邻接矩阵;
【注意】构造无向图:MaxInt = 0;w = 1;即可
构造有向网:去掉G.Edge[n][m] = G.Edge[m][n]; 对称属性即可
构造有向图:无向图+有向图的修改内容
// 创建无向网
void createUDN(MGraph &G){
char v1, v2; // 定义边的始终顶点
int m, n, w; // 定义始终结点在顶点表中的下标m,n; w为权重
scanf("%d %d", &G.vexnum, &G.arcnum); // 输入总顶点数和总边数
for(int i = 0; i < G.vexnum; i++)
scanf("%c", &G.Vex[i]); // 构造顶点表
for(int i = 0; i < G.vexnum; i++)
for(int j = 0; j < G.vexnum; j++)
G.Edge[i][j] = MaxInt; // 初始化邻接矩阵
for(int i = 0; i < G.arcnum; i++){ // 构造邻接矩阵
scanf("%c %c %d", &v1, &v2, &w); // 输入始终顶点和边权重
m = LocateVex(G, v1); // 定位起始顶点v1的下标
n = LocateVex(G, v2); // 定位终点顶点v2的下标
G.Edge[m][n] = w; // 对应边赋予权值w
G.Edge[n][m] = G.Edge[m][n]; // 无向网邻接矩阵对称
}
}
// 定位顶点下标
int LocateVex(MGraph G, VertexType v){
for(int i = 0; i < G.vexnum; i++){
if(G.Vertex[i] == v) {
return i;
}
}
return -1;
}
优点:检查两顶点是否存在边很容易;找一点的邻接点很容易;计算顶点的度很容易;
缺点:不便于增加和删除顶点;
浪费空间 --- 空间复杂度
,所以之和顶点数有关,适用于稠密图;浪费时间 --- 统计稀疏图一共有多少条边
(2)邻接表(表示不唯一)
无向图:n个结点e条边,邻接表需n个头结点和2e个表结点,空间复杂度O(n + 2e),适合稀疏图;
vi的度为第 i 个单链表中的结点数;
有向图:vi的出度为第 i 个单链表中的结点个数;vi的入度为单链表中邻接域值是 i - 1 的结点个数
结构体定义:顶点表(数组)+ 边表结点(ArcNode)+ 邻接表;
#define MaxVertexNum 100
typedef struct ArcNode { // 边表结点
int adjvex; // 指向的顶点下标
struct ArcNode *next; // 指向下一条弧的指针
// InfoType info; // 如果是网的话,可以记录权值
}ArcNode;
typedef struct VexNode { // 顶点结点
VertexType data; // 顶点信息
ArcNode *first; // 指向第一条弧的指针
}VexNode, AdjList[MaxVertexNum];
typedef strcut{
AdjList vertices; // 邻接表
int vexnum, arcnum; // 图的顶点数和边数
}ALGraph;
采用邻接矩阵表示法创建无向网
算法思想:
输入总顶点数和总边数;
建立顶点表
依次输入点的信息存入顶点表中
使每个表头结点的指针域初始化为NULL
创建邻接表
依次输入每条边依附的两个顶点
确定两个顶点的序号i和j,建立边结点
将此边结点分别插入到vi和vj对应的两个边链表的头部
// 创建无向网
void createUDN(ALGraph &G){
scanf("%d %d", &G.vexnum, &G.arcnum); // 输入顶点个数和边数
for(int i = 0; i < G.vexnum; i++){ // 建立顶点表
scanf("%c", &G.vertices[i].data);
G.vertices[i].first = NULL;
}
for(int i = 0; i < G.arcnum; i++){ // 构造邻接表
scanf("%c %c", &v1, &v2);
m = LocateVex(G, v1);
n = LocateVex(G, v2);
ArcNode p1; // 新建边表结点p1
p1->adjvex = n;
p1->next = G.vertices[m].first;
G.vertices[m].first = p1; // 顶点v1的first指向p1
ArcNode p2; // 新建边表结点p2
p2->adjvex = m;
p2->next = G.vertices[n].first;
G.vertices[n].first = p2; // 顶点v2的first指向p2
}
}
优点:方便找任一顶点的邻接点;
空间复杂度O(n+e);【无向图O(n+2e), 有向图O(n+e) => 数量级O(n+e)】
适用稀疏图,节约空间;
无向图计算度容易;有向图计算出度容易,计算入度需要“逆邻接表”;
缺点:不方便检查任意一对顶点之间是否存在边。
(3)十字链表(针对有向图)不唯一 空复O(n+e)
(4)邻接多重表(针对无向图)不唯一 空复O(n+e)
3、图的遍历
(1)广度优先遍历
算法:利用【邻接表 + 队列】表示图的广度优先遍历
void BFS(AlGraph G, int v){
printf("%d", v); // 访问初始顶点v
visit[v] = true; // 标记v已访问
InitQueue(Q); // 初始化队列
EnQueue(Q, v); // 将v入队
while(!isEmpty(Q)) {
DeQueue(Q, v); // 顶点v出队
for(w = FirstNeighbor(G, v); w >= 0; w = NextNeighbor(G, v, w)){ //检测所有邻接点
if(!visit[w]){
printf("%d", w); visit[w] = true; EnQueue(Q, w);
}
}
}
}
算法分析:
空间复杂度O(V);
若图利用邻接矩阵存储:时间复杂度O() 稠密图适合用邻接矩阵进行BFS
若图利用邻接表存储: 时间复杂度O(V + E) 稀疏图适合用邻接表进行BFS
(2)深度优先遍历
算法:利用【邻接矩阵 + 递归工作栈】表示图的深度优先遍历
void DFS(MGraph G, int v){ // G为邻接矩阵 , 顶点为v
printf("%d", v); //输出顶点
visit[v] = true; // 标记该顶点为true
for(int w = 0; w < G.vexnum; w++){
if(G.Edge[v][w] != 0 && !visit[w]){
DFS(G, w); // 递归实现
}
}
}
算法分析:
空间复杂度O(V);
若图利用邻接矩阵存储:时间复杂度O() 稠密图适合用邻接矩阵进行DFS
若图利用邻接表存储: 时间复杂度O(V + E) 稀疏图适合用邻接表进行DFS
4、图的应用
对应无向图
(1)最小生成树(各边权值之和最小)要会画算法流程
对应有向图
(2)最短路径(典型问题----交通网络)
Dijkstra算法【时间复杂度】
【注意】边上带有负权值时,该算法不适用
例题
Floyd算法【时间复杂度】
【注意】带有负权值的回路时,该算法不适用
例题
(3)拓扑排序(AOV)
1)依次输出入读为0的序列即可;若最后为空则为有向无环图,否则图中必有环。
2)时间复杂度:邻接表O(V + E);邻接矩阵O()。
3)若每个顶点有唯一的前驱关系,则拓扑序列唯一,否则不唯一。
(4)关键路径(AOE)
【用v表示事件开始或结束;用e表示活动】
四个量:ve(i) —— 事件最早开始事件(取Max);vl(i) —— 事件最迟开始时间(取Min);
e(i) —— 活动最早开始时间; l(i) —— 活动最迟开始时间;
最后:e(i) = ve(i);l(i) = vl(i) - w;若l(i) - e(i) = 0,则为关键活动。
例题
【注意】
1)可以通过加快关键活动缩短工期,但不能任意缩短
2)关键路径不唯一,对于有多个关键路径的网(如例题),缩短工期必须缩短包含在所有关键路径上的活动(如例题中的a4)
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。