图
图的基本概念
图的定义
- 图G由顶点集V和边集E构成。
|V|
:顶点个数,也称图的阶- 线性表可以是空表,树可以是空树,但图不可以是空,(
V一定是非空集
,E可以为空)
- 有向图
- 有向边(弧):顶点的有序对。
- <v,w> 弧尾:v;弧头:w。
<v, w> ≠ <w, v>
- <v,w> :从v到w的弧 / v邻接到w
- 无向图
- 无向边(边):无序对:(v,w)或(w,v)
(v, w) = (w, v)
- (v,w):w和v互为邻接点 / 边(w,v)依附于w和v / 边(v,w)和v,w相关联
- 简单图、多重图
简单图
:
① 不存在重复边;
② 不存在顶点到自身的边
多重图
:
图G中某两个结点之间的边数多于一条,又允许顶点通过同一条边和自己关联,则G为多重图
- 度
无向图:
无向图
:
- 顶点v的度是指依附于该顶点的边的条数,记为TD(v)
- 无向图的全部顶点的度的和等于边数的2倍
有向图:
有向图
:
- 入度是以顶点v为终点的有向边的数目,记为ID(v);
- 出度是以顶点v为起点的有向边的数目,记为OD(v)。
- 顶点v的度等于其入度和出度之和,即TD(v) = ID(v) + OD(v)。
- 顶点-顶点关系描述
路径
:顶点vp 到顶点vq之间的一条路径是指顶点序列回路
:第一个顶点和最后一个顶点相同的路径称为回路或环简单路径
:顶点不重复出现的路径称为简单路径路径长度
:路径上边的数目无向图
中,若从顶点v到顶点w有路径存在,则称v和w是连通的有向图
中,若从顶点v到顶点w和从顶点w到顶点v之间都有路径,则称这两个顶点是强连通的
- 子图
子图
:部分顶点和边
生成子图
:包含所有顶点
-
连通分量:
无向图
中的极大连通子图称为连通分量
有向图
中的极大强连通子图称为有向图的强连通分量
-
生成树
连通图的生成树是包含图中全部顶点
的一个极小连通子图
(边尽可能的少,但要保持连通)
|E|大于n-1,一定有回路
-
生成森林
在非连通图中,连通分量的生成树构成了非连通图的生成森林
。
边的权
:在一个图中,每条边都可以标上具有某种含义的数值,该数值称为该边的权值带权图/网
:边上带有权值的图称为带权图,也称网带权路径长度
:一条路径上所有边的权值之和
无向完全图
无向图中任意两个顶点之间都存在边
( 若无向图的顶点数|V|=n,则|E| ∈ [0,𝐶n2]=[0,n(n–1)/2
] )
有向完全图
有向图中任意两个顶点之间都存在方向相反的两条弧
( 若有向图的顶点数|V|=n,则|E| ∈ [0,2𝐶n2]=[0,n(n–1)
] )
4.
一、 图的存储及其操作
邻接矩阵法
邻接矩阵法
:
- 一维数组存储顶点的信息
- 二维数组(邻接矩阵)存储图中边的信息
- 第i个结点的
出度
=第i行
的非零个数- 第i个结点的
入度
=第i列
的非零元素个数- 第三个结点的
度
=第i行、第i列
的非零元素个数
邻接矩阵法存带权图(网)
邻接矩阵法的性能分析
空间复杂度
:O(|V|2) 只和顶点数相关,和实际的边数无关,适用于存储稠密图- 无向图的邻接矩阵是对称矩阵,可以压缩存储(只存储上三角区/下三角区)
- An 的元素An[i][j]等于由顶点i到顶点j的长度为n的路径的数目
- 表示方式唯一。
邻接表法
顺序+链式存储
(类似树的孩子表示法)
//“边/弧”
typedef struct ArcNode{
int adjvex;//边/弧指向哪个顶点
struct ArcNode *next;//指向下一条弧的指针
//InfoType. info; / /边权值
}
//“顶点”
typedef struct VNode{
VertexType data;//顶点信息
ArcNode *first;//第一条边/弧
}VNode,AdjList[MaxVertexNum];
typedef struct {
AdjList vertices;//邻接表
int vernum,arcnum;//顶点数和弧数
}
存储无向图
- 边结点数量:2|E|,整体空间复杂度:O( |V|+2|E| )
- 度:结点5的度为:2(边链表存地即为结点5相邻的边)
存储有向图- 边结点数量:|E|,整体空间复杂度:O( |V|+|E| )
- 出度:结点5的出度为:2
入度:遍历所有边链表
总结
十字链表法
只能存储有向图
十字链表法是有向图的一种链式存储结构
十字链表法压缩矩阵的存储
存有向图
图的十字链表法表示不唯一,但一个十字链表法只能表示确定一个图。
空间复杂度:O(|V|+|E|)
邻接多重表
只能存无向图
是无向图的另一种链式存储结构
空间复杂度:O(|E|+|V|)
总结
图的基本操作
-
Adjacent(G,x,y):
判断图G是否存在边<x, y>或(x, y)
-
Neighbors(G,x):
列出图G中与结点x邻接的边
-
InsertVertex(G,x):
在图G中插入顶点x
-
DeleteVertex(G,x):
从图G中删除顶点x
-
AddEdge(G,x,y):
若无向边(x, y)或有向边<x, y>不存在,则向图G中添加该边
-
RemoveEdge(G,x,y):
若无向边(x, y)或有向边<x, y>存在,则从图G中删除该边
-
三、图的遍历
BFS广度优先遍历
队列
bool visited[MAX_VERTEX_NUM];
void BFSTraverse(Graph G){
for(int i=0;i<G.vertextnum;i++)
visited[i]=false;
InitQueue(Q);
for(i=0;i<G.verxnum;++i){
if(!visited[i])//每个连通分量调用依次BFS
BFS(G,i);
}
}
void BFS(Graph G,int v){
visit(v);
visited[v] = true;
Enqueue(Q,v);//入队列
while(!isEmpty(Q)){
DeQueue(Q,v);//顶点v出队列
for(w = FirstNeigbor(G,v);w>=0;w = NextNeigbor(G,v,w)){
if(visited[w]){
visit(w);//访问
visited[w]=true;
EnQueue(Q,w);
}//if
}//for
}//while
}
类似树的层次遍历
- 同⼀个图的
邻接矩阵
表示⽅式唯⼀
,因此⼴度优先遍历序列唯⼀
- 同⼀个图
邻接表
表示⽅式不唯⼀
,因此⼴度优先遍历序列不唯⼀
BSF算法性能分析
- 空间复杂度:最坏情况,辅助队列⼤⼩为 O(|V|) (一个顶点和所有点相邻)
- 开销在于访问顶点和找邻边:
邻接表存储方式:
无向图:O(|V|+2|E|)
邻接矩阵:
广度优先生成树
广度优先搜索过程中可以得到一颗遍历树,称
广度优先搜索树
- 由于邻接表的表示⽅式不唯⼀,因此基于邻接表的⼴度优先⽣成树也不
唯⼀
- 邻接矩阵存储表示唯一,其广度优先生成树也是
唯一
的- 对⾮连通图的⼴度优先遍历,可得到⼴度优先⽣成森林
DFS深度优先遍历
类似树的先根遍历
bool visited[MAX_VERTEX_NUM]
void DFSTravel(Graph G){
int v;
for(v=0;v<G.vernum;v++) visited[v] = false;
for(v=0;v<G.vernum;v++){
if(!visited[v]) DFS(G,v);
}
}
void DFS(Graph G,int v){
visit(v);
visited[v] = true;
for(int w = FirstNeibor(G,v);w>=0;w = NextNeighbor(G,v,w)){
if(!visited[w]) DFS(G,w);
}
}
对于同一个图:
- 基于邻接矩阵的遍历得到的DFS序列和BFS序列是
唯一
的。- 基于邻接表的遍历得到的DFS序列和BFS序列是
不唯一
的。
复杂度分析
- 空间复杂度
- 时间复杂度 = 访问各结点所需时间+探索各条边所需时间
邻接矩阵
:
邻接表
深度优先搜索树
对连通图调用DFS才会产生深度优先生成树
否则生成深度优先生成森林
基于邻接表存储的生成树不唯一
图的遍历和图的连通性
- 对⽆向图进⾏BFS/DFS遍历调⽤BFS/DFS
函数的次数=连通分量数
- 对有向图进⾏BFS/DFS遍历调⽤BFS/DFS函数的次数要具体问题·具体分析`
最小生成树
连通图的
生成树
是包含图中全部顶点的一个极小连通子图
最小生成树(最小代价树)
性质:
- 最小生成树不唯一;
- 最小生成树的边的权值之和总是唯一的
- 最小生成树的边数为顶点数-1
Prim和Kruskal都基于贪心算法
Prim算法
寻找与当前生成树最近的结点
Kruskal算法
每次选择一条权值最小的边
时间复杂度分析
- Prim算法:O(|V|2)
适用于边稠密图 - Kruskal算法:O(|E|log2|E|)
适用于边稀疏图
思想
Prim
1.
松弛算法,更新其他未加入树的结点到新结点的距离
2.
3.
时间复杂度分析:
从V 0 开始,总共需要 n-1 轮处理(加入n-1个顶点) O(n-1)
每⼀轮处理:循环遍历所有个结点,找到lowCost最低的,且还没加⼊树的顶点。O(n)
再次循环遍历,更新还没加⼊的各个顶点的lowCost值 O(n)
总时间复杂度:O[(n-1)*2n]=O(|V|2)
Kruskal算法:
0.
1.
2.
3.
4.
5.
时间复杂度分析:
共执行e轮,每轮判断两个顶点是否属于同⼀集合,需要 O(log 2 e)
总时间复杂度 O(elog2e)
最短路径
BFS算法(无权图)
void Best_Min_BFS(Graph G,int v){
d[i] = ∞;//距离
path[i] = -1;//前驱
d[v] = 0;
vis[v] = true;
EnQueue(v);
while(!isEmpty(Q)){
DeQueue(Q,v);
for(w = FirstNeigbor(G,v);w>=0;w = NextNeigbor(G,v,w)){
if(!vis[w]){
vis[w] = true;
d[w] = d[v]+1;
path[w] = v;
EnQueue(w);
}//if
}
}
}
Dijkstra算法
①、初始化 →②、FindMin→③、松弛→④是否还有结点(回到2)
1.
松弛算法
2.
3.
4.
时间复杂度:==O(n2)==既O(|V|2)
Dijkstra 算法不适⽤于有负权值的带权图
Floyd算法
可解决带“负权图”,不能解决带负权回路图
for(int k = 0;k<n;k++){
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
if(A[i][j]>A[i][k]+A[k][j]){
A[i][j] = A[i][k]+A[k][j];
path[i][j] = k;
}
}
}
}
时间复杂度:O(n3)
总结
有向无环图
有向⽆环图:若⼀个
有向图
中不存在环
,则称为有向⽆环图,简称DAG图
DGA描述表达式
用有向无环图表示可以实现对相同子式的共享,从而节约存储空间
解题方法:
1.
2.
3.
4.
拓扑排序
AOV网
:⽤==DAG图(有向⽆环图)==表示⼀个⼯程。
AOV网不存在回路
拓扑排序:找到做事的先后顺序
每个AOV⽹都有⼀个或多个
拓扑排序序列。
bool TopoLogicalSort(Graph G){
InitStack(S);
for(int i=0;i<G.vexnum;i++){
if(indgree[i]==0) Push(S,i);//将所有入度为0的顶点进栈
}
int count = 0;
while(!S.isEmpty()){
Pop(S,i);
print[count++]=i;//输出顶点i
for(p=G.vertices[i].firstarc;p!=NULL;p=p->nextarc){
//将所有i指向的顶点的入度减1,并且将入度减为0的顶点压入栈S
v = p->adjvex;//指向的顶点
if((--indegree[v])==0) Push(S,v);//入度为0,入栈
}//while
if(count<G.vernum) return false;//排序失败,有向图中有回路
return true;
}
}
时间复杂度:
O(|V|+|E|)
邻接矩阵是三角矩阵,则存在拓扑序列。反之未必
逆拓扑排序
DSF实现逆拓扑排序
关键路径
AOE网
在带权有向图中,以顶点表示事件
,以有向边表示活动
,以边上的权值表示完成该活动的开销
(如完成活动所需的时间),称之为⽤边表示活动的⽹络,简称AOE⽹
性质
① 只有在某顶点所代表的事件发⽣后,从该顶点出发的各有向边所代表的活动才能开始;
② 只有在进⼊某顶点的各有向边所代表的活动都已结束时,该顶点所代表的事件才能发⽣。
另外,有些活动是可以并⾏进⾏的
关键路径
从源点到汇点的有向路径可能有多条,所有路径中,具有
最⼤路径⻓度
的路径称为关键路径,⽽把关键路径上的活动称为关键活动
- 事件vk的
最早发生时间
ve(k)
ve(源点) = 0
2. 活动ai的最早开始时间
e(i)
3. 事件vk的最迟发生时间vl(k)
4. 活动ai的最迟开始时间l(i)