最小生成树(最小代价树)
对于一个带权连通无向图G=(V,E),生成树不同,每棵树的权(即树中所有边上的权值之和)也可能不同。设R为G的所有生成树的集合,若T为R中边的权值之和最小的生成树,则T称为G的最小生成树(Minimum-Spanning-Tree, MST)。
- 最小生成树可能有多个,但边的权值之和总是唯一且最小的
- 最小生成树的边数=顶点数一1。砍掉一条则不连通,增加一条边则会出现回路
- 如果一个连通图本身就是―棵树,则其最小生成树就是它本身
- 只有连通图才有生成树,非连通图只有生成森林
Prim算法(普里姆)
Prim 算法(普里姆):
从某一个顶点开始构建生成树;
每次将代价最小的新顶点纳入生成树,
直到所有顶点都纳入为止。
3+5+1+4+2=15
时间复杂度:O(|V|2次方)
适合用于边稠密图
Kruskal算法(克鲁斯卡尔)
Kruskal算法(克鲁斯卡尔)
每次选择一条权值最小的边,使这条边的两头连通(原本已经连通的就不选)
直到所有结点都连通
时间复杂度:O(|E|log|E|)
适合用于边稀疏图
Prim算法的实现思想
更新还没加入的各个顶点的lowCast值
第二轮:
更新还没加入的各个顶点的lowCast值
第3轮:
第4轮:
第5轮:
Kruskal算法的实现思想
初始︰将各条边按权值排序
第1轮:检查第1条边的两个顶点是否连通(是否属于同一个集合)
第2轮︰检查第2条边的两个顶点是否连通(是否属于同一个集合)
第3轮︰检查第3条边的两个顶点是否连通(是否属于同一个集合)
第4轮︰检查第4条边的两个顶点是否连通(是否属于同一个集合)
第5轮︰检查第5条边的两个顶点是否连通(是否属于同一个集合)
…
每轮判断两个顶点是否属于同一集合
最短路径问题
“G港”是个物流集散中心,经常需要往各个城市运东西,怎么运送距离最近?
–单源最短路径问题
各个城市之间也需要互相往来,相互之间怎么走距离最近?
–每对顶点间的最短路径
单源最短路径–BFS算法(无权图)、Dijkstra算法(带权图、无权图)
各顶点间的最短路径–Floyd算法(带权图、无权图)
BFS求无权图的单源最短路径
bool visited[MAX_VERTEX_NUM];//访问标记数组
//广度优先遍历
void BFS(Graph G,int v){//从顶点v出发,广度优先遍历图G
visit(v); //访问初始顶点v
visited[v]=TRUE; //对v做已访问标记
Enqueue(Q,v); //顶点v入队列Q
while(!isEmpty(Q)){
DeQueue(Q,v); //顶点v出队列
for(w=FirstNeighbor(G,v);w>=0;w=NextNeighbor(G,v,w))
//检测v所有邻接点
if(!visited[w]){ //w为v的尚未访问的邻接顶点
visit(w); //访问顶点w
visited[w]=TRUE;//对w做已访问标记
EnQueue(Q,w); //顶点w入队列
}
}
}
//求顶点u到其他顶点的最短路径
void BFS_MIN_Distance(Graph G,int v){//从顶点v出发,广度优先遍历图G
//d[i]表示从v到i结点的最短路径
for(i=0;i<G.vexnum;++i){
d[i]=∞; //初始化路径长度
path[i]=-1;//最短路径从哪个顶点过来
}
d[u]=0;
visited[v]=TRUE; //对v做已访问标记
Enqueue(Q,v); //顶点v入队列Q
while(!isEmpty(Q)){
DeQueue(Q,v); //顶点v出队列
for(w=FirstNeighbor(G,v);w>=0;w=NextNeighbor(G,v,w))
//检测v所有邻接点
if(!visited[w]){ //w为v的尚未访问的邻接顶点
d[w]=d[v]+1; //路径长度加1
path[w]=u; //最短路径应从v到w
visited[w]=TRUE;//对w做已访问标记
EnQueue(Q,w); //顶点w入队列
}
}
}
最短路径问题-Dijkstra算法
Dijkstra算法
检查所有邻接自Vi的顶点,若其final值为false,则更新dist和path信息
检查所有邻接自Vi的顶点,若其final值为false,则更新dist和path信息
V0到V2的最短(带权)路径长度为:dist[2]=9
通过path[ ]可知,V0到V2的最短(带权)路径:V0->V4->V1->V2
Dijkstra算法不适用于有负权值的带权图
最短路径-Floyd算法
Floyd算法:求出每一对顶点之间的最短路径
#0:若允许在V0中转,最短路径是?
#1:若允许在V0、V1中转,最短路径是?
#2:若允许在V0、V1、V2中转,最短路径是?
Floyd算法核心代码
//....准备工作,根据图的信息初始化矩阵A和path
for(int k=0;k<n;k++){ //考虑以Vk作为中转点
for(int i=0;i<n;i++){ //遍历整个矩阵,i为行号,j为列号
for(int j=0;j<n;j++){
if(A[i][j]>A[i][k]+A[k][j]){//以Vk为中转点的路径更短
A[i][j]=A[i][k]+A[k][j];//更新最短路径长度
path[i][j]=k; //中转点
}、
}
}
}
Floyd算法不能解决带有“负权回路”的图(有负权值的边组成回路),这种图有可能没有最短路径
有向无环图描述表达式
有向无环图:若一个有向图中不存在环,则称为有向无环图,简称DAG图
Step4:从底向上逐层检查同层的运算符是否可以合体
拓扑排序
AOV网
AOV网(Activity On Vertex NetWork,用顶点表示活动的网)
用DAG网(有向无环图)表示一个工程。顶点表示活动,有向边<Vi,Vj>表示活动Vi必须先语活动Vj进行
拓扑排序
拓扑排序的实现:
1:从AOV网中选择一个没有前驱(入度为0)的顶点并输出
2:从网中删除该顶点和所有以它为起点的有向边
3:重复1和2直到当前的AOV网为空或当前网中不存在无前驱的顶点为止
#define MaxVertexNum 100 //图中顶点数目的最大值
typedef struct ArcNode{ //边表结点
int adjvex; //该弧所指向的顶点的位置
struct ArcNode *nextarc; //指向下一条弧的指针
}ArcNode;
typedef struct VNode{ //顶点表结点
VertexType data; //顶点信息
ArcNode *firstarc; //指向第一条依附该顶点的弧的指针
}VNode,AdjList[MaxVertexNum];
typedef struct{
AdjList vertices; //邻接表
int vexnum,arcnum; //图的顶点数和弧数
}Graph; //Graph是以邻接表存储的图类型
bool TopologicalSort(Graph G){
InitStack(S); //初始化栈,存储入度为0的顶点
for(int i=0;i<G.vexnum;i++)
if(indegree[i]==0)
Push(S,i); //将所有入度为0的顶点进栈
int count=0; //计数,记录当前已经输出的顶点数
while(!isEmpty(S)){ //栈不空,则存在入度为0的顶点
Pop(S,i); //栈顶元素出栈
print[count++]=i; //输出顶点i
for(p=G.vertices[i].firstarc;p;p=p->nextarc){
//将所有i指向的顶点的入度减1,并且将入度减为0的顶点压入栈s
v=p->adjvex;
if(!(--indegree[v]))
Push(S,v); //入度为0,则入栈
}
}
if(count<G.vexnum)
return false; //排序失败,有向图中有回路
else
return true; //拓扑排序成功
}
逆拓扑排序的实现(DFS算法)
void DFSTraverse(Graph G){ //对图G进行深度优先遍历
for(v=0;v<G.vexnum;++v)
visited[v]=FALSE; //初始化已访问标记数据
for(v=0;v<G.vexnum;++v) //本代码中是从v=0开始遍历
if(!visited[v])
DFS(G,v);
}
void DFS(Graph G,int v){//从顶点v出发,深度优先遍历图G
visited[v]=TRUE; //设已访问标记
for(w=FirstNeighbor(G,v);w>=0;w=NextNeighor(G,v,w))
if(!visited[w]){ //w为u的尚未访问的邻接顶点
DFS(G,w);
}
print(v);//输出顶点
}
关键路径
AOE网
在带权有向图中,以顶点表示事件,以有向边表示活动,以边上的权值表示完成该活动的开销(如完成活动所需的时间),称之为用边表示活动的网络,简称AOE网(Activity On Edge NetWork)
AOE网具有以下两个性质:
1:只有在某顶点所代表的事件发生后,从该顶点出发的各有向边所代表的活动才能开始
2:只有在进入某顶点的各有向边所代表的活动都已结束时,该顶点所代表的事件才能发生。
另外,有些活动是可以并行进行的。
从源点到汇点的有向路径可能有多条,所有路径中,具有最大路径长度的路径称为关键路径,而把关键路径上的活动称为关键活动
完成整个工程的最短时间就是关键路径的长度,若关键活动不能按时完成,则整个工程的完成时间就会延长
求关键路径的步骤
求所有事件的最早发生时间
求所有事件的最迟发生时间
求所有活动的最早发生时间
求所有活动的最迟发生时间
求所有活动的时间余量
关键活动:a2、a5、a7
关键路径:V1->V3->V4->V6
关键活动、关键路径的特性
若关键活动耗时增加,则整个过程的工期将增长
缩短关键活动的时间,可以缩短整个工程的工期
当缩短到一定程度时,关键活动可能会变成非关键活动
可能有多条关键路径,只提高一条关键路径上的关键活动速度并不能缩短整个工程的工期,只有加快那些包括在所有关键路径上的关键活动 才能达到缩短工期的目的