1、floyd
核心代码只有五行
for(int k=1; k<=n; k++)
for(int i=1; i<=n; i++)
for(int j=1; j<=n; j++)
{
if(map1[i][j]>map1[i][k]+map1[k][j])
map1[i][j]=map1[i][k]+map1[k][j];
}
这种算法可以找多源最短路,想知道a点到b点最短路,只能加入中间点来缩短路径,比如a到b 加入中间点k a到k到b
那么我们可以这样判断,要知道i到j的最短路,我们只要判断e[i][j]是否大于e[i][1]+e[1][j]即可,而中间值1则要用for循环从1到n遍历一个遍,就是查找所有中间值
以下为完整代码
//floyd
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
#define inf 0x3f3f3f3f
int main()
{
int n,m,a,b,c;
int map1[100][100];
scanf("%d%d",&n,&m);
for(int i=1; i<=n; i++)
for(int j=1;j<=n;j++)
if(i==j) map1[i][j]=0;
else map1[i][j]=inf;
for(int i=1; i<=m; i++)
{
scanf("%d%d%d",&a,&b,&c);
map1[a][b]=c;
}
for(int k=1; k<=n; k++)
for(int i=1; i<=n; i++)
for(int j=1; j<=n; j++)
{
if(map1[i][j]>map1[i][k]+map1[k][j])
map1[i][j]=map1[i][k]+map1[k][j];
}
for(int i=1; i<=n; i++)
{
for(int j=1;j<=n;j++)
printf("%d ",map1[i][j]);
printf("\n");
}
return 0;
}
2、dijkstra
这个算法只能计算单元最短路,而且不能计算负权值,这个算法是贪心的思想,
dis数组用来储存起始点到其他点的最短路,但开始时却是存的起始点到其他点的初始路程。通过n-1遍的遍历找最短。
比如1到3的最短路就是比较dis[3]与dis[2]+e[2][3],如果大于的话就更新dis[3]位dis[2]+e[2][3],这个专业术语叫松弛,这种算法的核心思想就是通过边来松弛一号顶点到其他定点的路程,这也就能解释为什么要遍历n-1遍了。
book数组用来标记,被标记的是已经找过的最短路,没被标记的没有被找过的最短路,当全部找过以后算法结束,也就是说dis数组中的数是起始点到其他所有点的最短路
以下为完整代码
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
#define inf 0x3f3f3f3f
int e[100][100],dis[100],book[100];//e用来记录数组,dis用来记录初始点到各个点的位置,book用来标记,被标记的表示已经连接成最短路
int main()
{
int n,m,t1,t2,t3,u,v,min1;
scanf("%d%d",&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=1; i<=m; i++)
{
scanf("%d%d%d",&t1,&t2,&t3);
e[t1][t2]=t3;
}
for(int i=1; i<=n; i++)
dis[i]=e[1][i];
for(int i=1; i<=n; i++)
book[i]=0;
book[1]=1;
for(int i=1; i<=n-1; i++) //更新dis数组
{
min1=inf;
for(int j=1; j<=n; j++)
{
if(book[j]==0&&dis[j]<min1)//每次找最短的边
{
min1=dis[j];
u=j;
}
}
book[u]=1;
for(int v=1; v<=n; v++)
{
if(e[u][v]<inf)
if(dis[v]>dis[u]+e[u][v])//如果从起始点到j的距离大于起始点到u的距离加上u到j的距离就更新,专业术语松弛操作
dis[v]=dis[u]+e[u][v];
}
}
for(int i=1; i<=n; i++)
printf("%d ",dis[i]);
return 0;
}
下边仍为dijkstra算法,不过存地图的方式从邻接矩阵换成了邻接表,邻接表的时间复杂度更低,而且可以存两点之间的多组数据,比如1点到2点之间可能存在2条路,每条路的权值不同
先介绍邻接表,这里给出啊哈磊的博客,简单易懂,链接
http://ahalei.blog.51cto.com/4767671/1391988
个人理解first数组用来存以u[i]为顶点的最后一条边的编号,next数组存编号为i的边的上一条边,如果这个边是以u[i]为顶点的第一条边,则next[i]为-1。
创立
for(i=1;i<=n;i++)
first[i]=-1;
for(i=1;i<=m;i++)
{
scanf("%d %d %d",&u[i],&v[i],&w[i]);
next[i]=first[u[i]];//next等于编号i的上一条边
first[u[i]]=i;
}
遍历
for(i=1;i<=n;i++)
{
k=first[i];
while(k!=-1)
{
printf("%d %d %d\n",u[k],v[k],w[k]);
k=next[k];
}
}
这里给到题poj1724,dijs的变形 用了优先队列和邻接表
链接:
http://poj.org/problem?id=1724
直接上代码
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<queue>
using namespace std;
#define inf 0x3f3f3f3f
const int maxn=10005;
int num,first[maxn];
struct node
{
int id;
int len;
int val;
}node1;
struct edge
{
int id;
int len;
int val;
int next;//作用跟next数组一样
}e[maxn];
priority_queue<node>q;
bool operator <(node a,node b)
{
return a.len>b.len;
}
void add(int u,int v,int len,int val)
{
e[num].id=v;//存下一个点,以边找点
e[num].val=val;
e[num].len=len;
e[num].next=first[u];
first[u]=num;
num++;
}
int main()
{
node cur;
int ans=inf;
int k,n,r,s,d,l,t;
scanf("%d%d%d",&k,&n,&r);
memset(first,-1,sizeof(first));
while(!q.empty()) q.pop();
num=0;
for(int i=0;i<r;i++)
{
scanf("%d%d%d%d",&s,&d,&l,&t);
add(s,d,l,t);
}
node1.id=1;//起始点
node1.len=0;
node1.val=0;
q.push(node1);
while(!q.empty())
{
cur=q.top();
q.pop();
if(cur.id==n)
{
ans=cur.len;
break;
}
for(int i=first[cur.id];i!=-1;i=e[i].next)
{
if(k>=cur.val+e[i].val)//只有符合要求的点才会进入队列
{
node1.id=e[i].id;
node1.len=e[i].len+cur.len;
node1.val=e[i].val+cur.val;
q.push(node1);
}
}
}
if(ans==inf)
printf("-1\n");
else
printf("%d\n",ans);
}
3、bellman(可以计算负权值)
那么先介绍一下负权回路,如果是单向图,那么所有权值之和为负数,这是负权回路。如果是无向图只要有一个负权值,就不会存在最短路,跟负权回路一个意思。
这个算法一般用于有负权值的最短路,但时间复杂度也不高。
有负权值的最短路
有了负权值dijs这种算法就不能用了,为什么呢?
因为这种算法是贪心的思想,每次松弛的的前提是用来松弛这条边的最短路肯定是最短的。然而有负权值的时候这个前提不能得到保证,所以dijs这种算法不能成立。
这里给出一个博客,看这个很清晰。
链接:http://blog.csdn.net/baidu_31818237/article/details/50611592
bellman的核心代码只有4行
for(int k=1; k<=n-1; k++) //进行n-1次松弛
for(int i=1; i<=m; i++) //枚举每一条边
if(dis[v[i]]>dis[u[i]]+w[i])//尝试松弛每一条边
dis[v[i]]=dis[u[i]]+w[i];
这个算法也是遍历n-1遍找过所有的点,至于为什么是n-1呢。dijs算法n-1次遍历是因为有n-1个点需要遍历,这个也是因为最短路是一个不包含回路的路径,无论正负权回路都不能有,那么去掉回路,n个点任意两点之间就最多有n-1条边。但是程序可能在不到n-1次循环就已经找到了所有最短路,说明这个是最坏情况下是n-1次遍历。
dis同样是存在起始点到各个顶点的最短路,这个与dijs不同的是,dijs每次找到最近的点进行松弛操作,而这个bellman则是只要路程更短我就松弛。也是因为这样才能用来解决负权值问题。
那么怎么来看有负权值回路呢,如果有负权值回路,那最短路就不会存在,因为最短路会越来与小。那么在n-1轮松弛后,要是还能松弛就代表有负权值回路。
下边是完整代码
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
#define inf 0x3f3f3f3f
int dis[100],u[100],v[100],w[100];
int main()
{
int n,m,flag1,flag2;
while(~scanf("%d%d",&n,&m))
{
for(int i=1; i<=m; i++)
scanf("%d%d%d",&u[i],&v[i],&w[i]);
for(int i=1; i<=n; i++)
dis[i]=inf;
dis[1]=0;
for(int k=1; k<=n-1; k++)
{
flag1=0;//用于标记本轮松弛是否发生
for(int i=1; i<=m; i++)
{
if(dis[v[i]]>dis[u[i]]+w[i])
dis[v[i]]=dis[u[i]]+w[i];
flag1=1;//有更新
}
if(flag1==0)
break;//dis数组没有更新,即没有松弛,结束算法
}
flag2=0;
for(int i=1; i<=m; i++)
if(dis[v[i]]>dis[u[i]]+w[i])
flag2=1;
if(flag2==1)
printf("有负权回路\n");
else
{
for(int i=1; i<=n; i++)
printf("%d ",dis[i]);
}
printf("\n");
}
return 0;
}
其实这个算法可以在优化,因为有可能在n-1轮松弛还没结束就已经是最短路,那么用队列进行优化。
4、spfa(队列优化的bellman)
我没找到两者有什么区别,本来spfa就是队列优化而来的bellman, 我用了邻接表,反正是要优化就优化到底,下边还会给出优先队列的spfa
代码
数组模拟邻接表
#include<stdio.h>
#include<string.h>
#include<queue>
#include<algorithm>
using namespace std;
int dis[100],u[100],v[100],w[100],vis[100],first[100],next[100],c[100];
int flag;
int spfa(int s,int n)
{
queue<int>q;
memset(dis,0x3f,sizeof(dis));
dis[s]=0;
memset(vis,0,sizeof(vis));
memset(c,0,sizeof(c));
q.push(s);
vis[s]=1;
flag=0;
while(!q.empty())
{
int x;
x=q.front();
q.pop();
vis[x]=0;
//队头元素出队,并且消除标记
for(int k=first[x]; k!=0; k=next[k]) //遍历顶点x的邻接表
{
int y=v[k];
if(dis[x]+w[k]<dis[y])
{
dis[y]=dis[x]+w[k]; //松弛
if(!vis[y]) //顶点y不在队内
{
vis[y]=1; //标记
c[y]++; //统计次数
q.push(y); //入队
if(c[y]>n) //超过入队次数上限,说明有负环
return flag=0;
}
}
}
}
}
int main()
{
int n,m;
while(~scanf("%d%d",&n,&m))
{
memset(first,-1,sizeof(first));
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;
}
spfa(1,n);
if(flag)
printf("有负权回路\n");
else
{
for(int i=1;i<=n;i++)
printf("%d ",dis[i]);
}
}
}
这个是vector模拟邻接表
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<map>
#include<queue>
#include<math.h>
#include<vector>
#include<iostream>
#define inf 0x3f3f3f3f
#define ll long long
#define N 100000+10
using namespace std;
int n,m;
int x,y,z;
bool v[100];
int d[100];
struct node
{
int y,z;
};
vector<node>e[100];
void spfa(int x)
{
memset(v,0,sizeof(v));
d[x]=0;
queue<int>q;
q.push(x);
v[x]=1;
while(!q.empty())
{
int st=q.front();
q.pop();
v[st]=0;
for(int i=0;i<e[st].size();i++)
{
if(d[st]+e[st][i].z<d[e[st][i].y])
{
d[e[st][i].y]=d[st]+e[st][i].z;
if(!v[e[st][i].y])
{
q.push(e[st][i].y);
v[e[st][i].y]=1;
}
}
}
}
}
int main()
{
scanf("%d%d",&n,&m);
memset(d,inf,sizeof(d));
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&x,&y,&z);
e[x].push_back((node){y,z});
e[y].push_back((node){x,z});
//存入无向图
}
spfa(1);
for(int i=1;i<=n;i++)
{
printf("%d ",d[i]);
}
}
最后着重说一下邻接表可以用数组也可以用vector
实现
e[]这个数组,e[i][j]i为起点,j为终止点的标号,e[i][j].y就是终止点
例如
j:0 1 2 j只是标号第几个
i:1 (2,3 3,2 4,4) 括号内的东西就是终止点和权值,e[1][0].y=2(终止点),e[1][0].z=3(权值)
i:2 (1,2 3,5 4,6)
i:3 (1,2 2,4 4,5)
这样也可以实现链表,并且可以存无向图,数组实现的邻接表存无向图很麻烦
转自:https://blog.csdn.net/zezzezzez/article/details/70245548