最短路径及图的最小生成树汇总与区别
图的最小生成树
- Kruskal(克鲁斯卡尔)
- 总结:首先按照边的权值进行从小到大排序,每次从剩余的边中选择权值较小且边的两个顶点不在同一个集合内的边(就是不会产生回路的边),加入到生成树中,直到加入了n-1条边为止。
- Kruskal算法核心代码:
for(i=1;i<=m;i++)//开始从小到大枚举每一条边
{
if(merge(e[i].u,e[i].v))//判断一条边的两个顶点是否已经连通,即判断是否已在同一个集合中
{
count++;//如果目前尚未不连通,则选用这条边
sum=sum+e[i].w;
}
if(count==n-1 ) break;//直到选用了n-1条边之后退出循环
}
- Prim(普里姆)
- 要用n-1条边将n个顶点连接起来,每个顶点都必须至少有一条边与它相连(否则这个点就是一个孤立的点)。可以随便选一个点开始(最终每个点都要被选到),看看这个顶点有哪些边与其他顶点相连,然后在这些边中找一个最短的边连接另一个顶点。
- 这个方法就像是一棵“生成树”在慢慢长大,从一个顶点长到了n个顶点。
- 算法总结:将所有的顶点分为两类:树顶点(已经被选入生成树的顶点)和非树顶点(还未被选入生成树的顶点)。首先选择任意一个顶点加入生成树(可以理解为生成树的根)。接下来要找出一条边添加到生成树,这需要枚举每一个树顶点到每一个非树顶点所有的边,然后找到最短边加入到生成树。照此方法,重复操作n-1次,直到将所有顶点都加入生成树中。
- 代码实现:
#include"stdio.h"
main()
{
int n,m,i,j,k,min,t1,t2,t3;
int e[7][7],dis[7],book[7]={0};//用数组book来记录该顶点是否已经加入生成树
int inf=99999999;//正无穷
int count=0,sum=0;//count用于记录生成树中顶点个数,sum用来存储路径之和
scanf("%d %d",&n,&m);//n代表顶点个数,m,代表边数
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
if(i==j) e[i][j]=0;//从自己到自己记为0
else e[i][j]=inf;
for(i=1;i<=m;i++)
{
scanf("%d %d %d",&t1,&t2,&t3);//无向图
e[t1][t2]=t3;
e[t2][t1]=t3;
}
for(i=1;i<=n;i++)
dis[i]=e[1][i];//1号顶点到各个顶点的初始距离,因为当前生成树中只有1号顶点
book[1]=1;
count++;
while(count<n)//如果所有顶点都加入了生成树,则退出循环
{
min=inf;//找到未加入生成树距离任意已经加入生成树顶点最近的一个顶点
for(i=1;i<=n;i++)
{
if(book[i]==0&&dis[i]<min)
{
min=dis[i];j=i;
}
}
book[j]=1;count++;sum=sum+dis[j];//将找到的顶点加入生成树
for(k=1;k<=n;k++)//扫描当前加入的顶点j所有的边,再以j为中间点,更新生成树到每一个非数顶点的距离
{
if(book[k]==0&&dis[k]>e[j][k])
dis[k]=e[j][k];
}
}
printf("%d",sum);//输出最短路径
}
Kruskal算法和Prim算法的区别:Kruskal算法是一步步将森林中树进行合并,而Prim算法则是通过每次增加一条边来建立一棵树。Kruskal按照从小到大的顺序去考察每一条边,每次都是先给最小边机会。Prim每次都是选择距离生成树最近的边。这两种算法每一次的选择都是局部最优解。
最短路径
- Dijkstra(狄克斯特拉)
- 此算法用于指定一个点(源点)到其余各个顶点的最短路径,也叫做“单源最短路径”。
- 算法总结:每次找到离源点最近的一个顶点,然后以该顶点为中心进行扩展,最终得到源点到其余所有点的最短路径。
- 扩展过程中,以顶点的边来进行扩展,看从源点到这个顶点是否能通过别的点与之相连的边更短,如果是的话该点到源点的距离就要更新记录,这个过程也称之为“松弛”。
- 算法实现:
将所有顶点分成两部分:已知最短路径的顶点集合P,未知最短路径的顶点集合Q。最开始,集合P中只有源点。用一个数组book来记录哪些点已经在集合P中。
将源点s到自己的最短路径设为0即dis[s]=0。若存在源点能直接到达的顶点i,则把dis[i]设为e[s][i]。把其他(源点不能直接到达的顶点)的最短路径设为正无穷。
在集合Q的所有顶点中选择一个离源点s最近的顶点u(即dis[u]最小)加入到集合P。并考察所有以点u为起点的边,对每一条边进行松弛操作。
重复上一步,当集合Q为空,算法结束。最终dis数组中的值就是源点到所有顶点的最短路径。
- 代码如下:
#include"stdio.h"
main()
{
int e[10][10],dis[10],book[10];
int i,j,n,m,t1,t2,t3;
int u,v,min;
int inf=99999999;
scanf("%d %d",&n,&m);//n代表顶点数,m代表边数
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
if(i==j) e[i][j]=0;
else e[i][j]=inf;
for(i=1;i<=m;i++)
{
scanf("%d %d %d",&t1,&t2,&t3);
e[t1][t2]=t3;
}
for(i=1;i<=n;i++)
dis[i]=e[1][i];
for(i=1;i<=n;i++)
book[i]=0;
book[1]=1;
//Dijkstra核心语句
for(i=1;i<=n-1;i++)
{
min=inf;
for(j=1;j<=n;j++)
{
if(book[j]==0&&dis[j]<min)
{
min=dis[j];
u=j;
}
}
book[u]=1;
for(v=1;v<=n;v++)
{
if(e[u][v]<inf)
{
if(dis[v]>dis[u]+e[u][v])
dis[v]=dis[u]+e[u][v];
}
}
}
for(i=1;i<=n;i++)
printf("%d ",dis[i]);
}
- Floyd(弗洛伊德)
- 求任意两点之间的最短路径,这个问题也被称为“多源最短路径”问题。
- 通过找到中间节点找出从起点单向到达终点的最短路径。
- Floyd算法又称插点法,是一种利用动态规划的思想寻找给定的加权图中多源点之间最短路径的算法。(百度)
- 代码实现:
从任意一条单边路径开始。所有两点之间的距离是边的权,如果两点之间没有边相连,则权为无穷大。
对于每一对顶点u和v,看看是否存在一个顶点w使得从u到w再到v比已知的路径更短。如果是更新它。
- 代码实现:
#include"stdio.h"
main()
{
int e[10][10],k,i,j,n,m,t1,t2,t3;
int inf=99999999;
scanf("%d %d",&n,&m);//n代表顶点个数,m代表边数
for(i=1;i<=n;i++)//初始化
for(j=1;j<=n;j++)
if(i==j) e[i][j]=0;
else e[i][j]=inf;
for(i=1;i<=m;i++)
{
scanf("%d %d %d",&t1,&t2,&t3);
e[t1][t2]=t3;//单向
}
//Floyd-Warshall算法核心语句
for(k=1;k<=n;k++)//允许中转经过的点
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
if(e[i][j]>e[i][k]+e[k][j]) e[i][j]=e[i][k]+e[k][j];
//输出结果
for(i=1;i<=n;i++)
{
for(j=1;j<=n;j++)
{
printf("%10d",e[i][j]);
}
printf("\n");
}
}