最短路径
Floyd
Floyd算法可以处理带有负权边的图,但不能处理带有负权回路的图
算法思想:通过引入中间边来松弛两点之间的距离
算法核心语句:
//floyd算法:求图上任意两点之间的最短路径
for(int k =1;k<=n;++k){
for(int i=1;i<=n;++i){
for(int j=1;j<=n;++j){
if(e[i][j]>e[i][k]+e[k][j]){
e[i][j] = e[i][k]+e[k][j];
}
}
}
}
示例代码:
#include<cstdio>
#include<iostream>
using namespace std;
int e[10][10];
#define inf 99999
int main()
{
int m,n; //m:边的条数 n:顶点个数
cin>>m>>n;
//初始化边
for(int i=1;i<=n;++i){
for(int j=1;j<=n;++j){
if(i==j){
e[i][j]=0;
}else{
e[i][j]=inf;
}
}
}
//设置边
int t1,t2,val;
for(int i=0;i<m;++i){
cin>>t1>>t2>>val;
e[t1][t2] = val;
}
//floyd算法:求图上任意两点之间的最短路径
for(int k =1;k<=n;++k){
for(int i=1;i<=n;++i){
for(int 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(int i=1;i<=n;++i){
for(int j=1;j<=n;++j){
printf("%10d",e[i][j]);
}
printf("\n");
}
return 0;
}
Dijkstra
Dijkstra算法:单源最短路径(不能处理带有负权边的图)
算法思想:维护一个dis数组,记录着源点到其他顶点的路径。每一次从顶点中选出到源点最短的点,然后更新dis数 组里面的值。直到所有顶点被选完。(贪婪算法)
算法核心语句:
//算法核心语句
for(int i=1;i<n;++i){
int min = inf;
int u;
//寻找剩余顶点中值最小的顶点
for(int j=1;j<=n;++j){
if(book[j]==0&&dis[j]<inf){
if(dis[j]<min){
min = dis[j];
u = j;
}
}
}
book[u] = 1;
for(int i=1;i<=n;++i){
if(e[u][i]<inf){
if(dis[i]>dis[u]+e[u][i]){
dis[i] = dis[u]+e[u][i];
}
}
}
}
示例程序:
#include<iostream>
#include<cstdio>
using namespace std;
int e[10][10],book[10],dis[10];
#define inf 99999
int main(){
int n,m; //n:顶点个数 m:边的条数
cin>>n>>m;
//初始化
for(int i=1;i<=n;++i){
for(int j=1;j<=n;++j){
if(i==j){
e[i][j] = 0;
}
else{
e[i][j] = inf;
}
}
}
//读入边
for(int i=0;i<m;++i){
int t1,t2,val;
cin>>t1>>t2>>val;
e[t1][t2] = val;
}
int source;
cout<<"请输入源点:"<<endl;
cin>>source;
//初始化dis数组和book数组
for(int i=1;i<=n;++i)
dis[i] = e[source][i];
for(int i=1;i<=n;++i)
book[i]=0;
book[source]=1;
//算法核心语句
for(int i=1;i<n;++i){
int min = inf;
int u;
//寻找剩余顶点中值最小的顶点
for(int j=1;j<=n;++j){
if(book[j]==0&&dis[j]<inf){
if(dis[j]<min){
min = dis[j];
u = j;
}
}
}
book[u] = 1;
for(int i=1;i<=n;++i){
if(e[u][i]<inf){
if(dis[i]>dis[u]+e[u][i]){
dis[i] = dis[u]+e[u][i];
}
}
}
}
//输出
for(int i=1;i<=n;++i){
printf("%5d",dis[i]);
}
return 0;
}
// 6 9
// 1 2 1
// 1 3 12
// 2 3 9
// 2 4 3
// 3 5 5
// 4 3 4
// 4 5 13
// 4 6 15
// 5 6 4
//数组实现邻接表存储图:适用于稀疏图
int n,m;
int u[6],v[6],w[6]; //用于存储起点、终点、权值,大小要比边数m大
int first[5],next[6]; //first用来存储起始点相同的边的第一条边 ,next用来保存起点相同的边的下一条边的编号
for(int i=1;i<=n;++i)
first[i] = -1;
for(int i=1;i<=m;++i){
scanf("%d %d %d",&u[i],&v[i],&w[i]); //读入每一条边
next[i] = first[u[i]];
first[u[i]] = i;
}
//遍历邻接表
for(int i=1;i<=n;++i){
int k = first[i];
while(k!=-1){
printf("%d %d %d\n",u[k],v[k],w[k]);
k = next[k];
}
}
Bellman—Ford
Bellman-Ford算法:可以处理带有负权边的图
算法思想:对所有边遍历k次,得到的是源点最多经过k条边到达其他路径的最短路径长度。因此总的只需要进行k-1 轮
算法核心语句:
//算法核心语句
for(int k=1;k<=n-1;++k){
for(int i=1;i<=m;++i){
if(dis[v[i]]>dis[u[i]]+w[i])
dis[v[i]] = dis[u[i]]+w[i];
}
}
示例程序:
#include<iostream>
#include<cstdio>
#define inf 99999
using namespace std;
int main(){
int dis[10],u[10],v[10],w[10]; //u[i]➡v[i]:w[i]
int n,m; //n个顶点m条边
int source;
cin>>n>>m;
//读入边
for(int i=1;i<=m;++i){
cin>>u[i]>>v[i]>>w[i];
}
//初始化dis数组
for(int i=1;i<=n;++i){
dis[i] = inf;
}
cout<<"请输入源点:"<<endl;
cin>>source;
dis[source] = 0;
//算法核心语句
for(int k=1;k<=n-1;++k){
for(int i=1;i<=m;++i){
if(dis[v[i]]>dis[u[i]]+w[i])
dis[v[i]] = dis[u[i]]+w[i];
}
}
for(int i=1;i<=n;++i){
printf("%d ",dis[i]);
}
return 0;
}
// 5 5
// 2 3 2
// 1 2 -3
// 1 5 5
// 4 5 2
// 3 4 3
说明:
-
判断是否有负权回路:在执行n-1次遍历后,再进行一次遍历,如果有值发生改变,则有负权回路
-
大多数时候,再n-1次循环之前就已经得到了最终结果,可以通过引入标志位提前终止循环。
-
在每一次实施松弛操作后,会有一些顶点的最短路径的值将不再改变,可以对此进行优化,优化的思路:
采用队列,每次选取队首顶点u,对顶点u的所有出边进行松弛,例如有一条u➡v的边,如果这条边使得源点 到v的最短路程变短,且顶点v不在队列中,就将v放入队尾。如果顶点v已经在队列中,就不在放入(故需要 一个标志数组加以判断),当对顶点的所有出边松弛完毕后,顶点u出队。循环直到队列为空。