一、图的基本概念
-
图G由顶点集V和边集E组成,记为G=(V,E)。
-
有向图:弧记为<x,y>,强连通分量(最少n条边,即成环,点相互可达)
无向图:边记为(x,y),连通分量(最大连通子图,点相互可达)
-
顶点集不能为空,边集可以为空。
-
**完全图:**任意两个点都有一条边相连。
有向图有n(n-1)条边,无向图边数少一半。
-
任意子集不一定是子图(边所连点不在)
-
**连通图:**无向图中,两个顶点之间有路径就是连通的,任一对顶点之间都是连通的则称这个图是连通图
-
**强连通图:**有向图中,任意一对顶点vi和vj之间都存在一条从vi到vj的路径,也存在一条从vj到vi的一条路径。
-
生成树:一个连通图的最小连通子图称作该图的生成树,有n个顶点的连通图的生成树有n个顶点和n-1条边
-
简单路径:顶点不重复出现
-
路径长度
- 对于不带权的图,一条边的路径长度是指该路径上的边的条数;
- 对于带权的图,一条路径的长度是指一条路径的路径长度是指该路径上各个边权值的总和;
- 网:带权图
二、图的存储及基本操作
邻接矩阵多用于稠密图,邻接表多用于稀疏图;
邻接矩阵(数组)
- 一维数组存储顶点的信息,二维数组存储边的信息。
- 若G为图,则用0、1表示出度入度关系;
- 第i行:出度;第i列:入度
- 顶点的出度=第i行元素之和;顶点的入度=第i列元素之和;
- 无向图对称(压缩存储),有向图不可能对称;
- 完全图的对角元素为0,其余为1;
- 若G为网,连通则用边的权值表示,不连通则用∞表示。
- 优点:
- 便于判断两顶点是否相连,便于各顶点度的计算。
- 缺点:
- 不便于增删顶点,不便于统计边数。
- T(n)=O(n²),S(n)=O(n²)
邻接表(链式)
- 包括表头结点和边结点。
- 邻接表不唯一。
- 无向图(n个顶点,e条边):n个头结点,2e个边结点,存储空间O(n+2e)
- 优点:
- 便于增删顶点,便于统计边数。
- 空间效率高,S(n)=O(n+e)
- 缺点:
- 不便于判断两顶点之间是否有边,不便于计算有向图各顶点的度。
- 有向图的邻接表求入度较困难,有向图逆邻接表求出度较困难。
十字链表(有向图)
- 便于求得顶点的入度和出度;
- 用于有向图,顶点结点之间是顺序存储的;
- 有出边和入边两指针;
- 十字链表表示不唯一,但可确定一个图;
邻接多重表(无向图)
- 用于无向图,每条边都要存储两遍;
- 边节点记录两端值,并被编到两个链表中;
- 同一条边在邻接表中用两个结点表示,在邻接多重表中只有一个结点。
三、图的遍历
时间空间复杂度均相同
S(n)=O(n),借用了堆栈或队列;
时间复杂度与结构有关。邻接矩阵存储时,T(n)=O(n²);邻接表存储时,T(n)=O(n+e)
给定图的邻接矩阵存储是唯一的,故其广度优先生成树也是唯一的。邻接表存储不唯一,生成树也不唯一。
图的遍历算法可以判图的连通性。
广度优先遍历-BFS
- 类似于树的层次遍历;
- 遍历过程:
(1)从图中某个顶点v出发,访问v
(2)依次访问v的各个未曾访问过的邻接点
(3)分别从这些邻接点出发依次访问它们的邻接点,并使“先被访问过的邻接点”先于“后被访问过的邻接点”被访问。
(4)重复步骤三,直至图中所有已被访问的顶点的邻接点都被访问过,搜索结束。
- 算法步骤(连通图):
(1)从图中某个顶点v出发,访问v,并置visited[v]的值为true,然后将v进队。
(2)只要队列不空,则重复下述操作:
——依次检查v的所有邻接点w,如果visited[w]的值为false,则访问w,并置visited[w]的值为true,然后将w进队。
- 算法描述(连通图):
void BFS(Graph G,int v){
cout<<v; //访问第v个顶点
visited[v]=true;
InitQueue(Q); //复制队列初始化
EnQueue(Q,v); //v进队
while(!QueueEmpty(Q)){
DeQueue(Q,u); //队头元素出队并置为u
for(w=FirstAdjVex(G,v);w>=0;w=NextAdjVex(G,v,w))
//FirstAdjVex(G,v)表示v的第一个邻接点
//NextAdjVex(G,v,w)表示v相对于w的下一个邻接点
if(!visited[w]){
cout<<w;
visited[w]=true;
EnQueue(Q,w); //w进队
}//if
}//while
}
深度优先遍历-DFS
- 是树的先序遍历的推广。
- 遍历过程:
(1)从图中的某个顶点v出发,访问v;
(2)找出刚访问过的顶点的第一个未被访问的邻接点,访问该顶点。以该顶点为新顶点,重复此步骤,直至刚访问过的顶点没有未被访问过的邻接点为止。
(3)返回前一个访问过的仍有未被访问的邻接点的顶点,找出该顶点的下一个未被访问的邻接点,访问该顶点。(设访问数组visited[n],初值为“false”,被访问后重置为“true”)
(4)重复步骤二、三,直至图中所有顶点都被访问过,搜索结束。
- 算法步骤(连通图):
(1)从图中某个顶点v出发,访问v,并置visited[v]的值为true。
(2)依次检查v的所有邻接点w,如果visited[w]的值为false,再从w出发进行递归遍历,直到图中所有顶点都被访问过。
- 算法描述(连通图):
bool visited[MVNum];
void DFS(Graph G,int v){
cout<<v; //访问第v个顶点
visited[v]=true;
for(w=FirstAdjVex(G,v);w>=0;w=NextAdjVex(G,v,w))
//FirstAdjVex(G,v)表示v的第一个邻接点
//NextAdjVex(G,v,w)表示v相对于w的下一个邻接点
if(!visited[w])
DFS(G,w);
}
四、树的应用
最小生成树
生成树:没有回路的极小连通子图
最小生成树:代价唯一,
Prim算法
”加点法“,T(n)=O(n²),多用于稠密图
算法执行过程
Kruskal算法
”加边法“,T(n)=O(elog₂e),多用于稀疏图
算法执行过程
最短路径
Dijkstra算法
- 求单源最短路径问题
- 初始化—>选择—>更新
- T(n)=O(n³)
Floyd算法
- T(n)=O(n³)
拓扑排序
- AOV网:用顶点表示活动,用弧表示活动间的优先关系的有向无环图(DAG)。
- 深度优先遍历也可实现拓扑排序(逆拓扑排序)。
- 拓扑排序的过程:(可用于检测AOV中是否存在环)
- 在有向图中选一个无前驱的顶点并输出它;
- 从图中删除该顶点和所有以它为尾的弧;
- 重复1、2步骤,直至不存在无前驱的顶点;
- 若此时输出的顶点数小于有向图中的顶点数,则存在环,否则输出序列为拓扑序列
- **逆拓扑排序:**选择无后继(出度为0)的顶点输出,删除以它为终点的有向边,重复上述操作至空。
关键路径
-
AOE网:顶点表事件,弧表活动,权表活动持续时间。通常用于估算工程的完成时间。
-
源点:入度为0;汇点:出度为0;
**关键路径:**一条源点到汇点的带权路径长度最长的路径;关键路径可能不止一条。
关键活动:关键路径上的活动。
-
求事件的最早发生时间——取多个入边的最大
求事件的最迟发生时间——取多个出边的最小
求活动的最早开始时间——触发事件的最早发生时间
求活动的最迟开始时间——终点事件的最迟发生时间 - 活动时间
-
关键路径求解的过程:
(1)对图中的顶点进行排序,在排序过程中按拓扑序列求出每个事件的最早发生时间ve(i);
(2)按逆拓扑序列求出每个事件的最迟发生时间vl(i);
(3)求出每个活动ai的最早开始时间e(i);
(4)求出每个活动ai的最晚开始时间l(i);
(5)找出e(i)=l(i)的活动ai,即为关键活动,由关键活动形成的由源点到汇点的每一条路径就是关键路径。
-
辅助空间:
- 一维数组ve[i],事件vi的最早发生时间;
- 一维数组vl[i],事件vi的最迟发生时间;
- 一维数组topo[i],记录拓扑序列的顶点序号。