个人建议:先学存图再看算法
先看:
文章目录
Floyd算法
适用情况:
求任意两点间的最短路,不能处理负权回路(负权环),可处理负权边。
时间复杂度:
O(N3)
算法思想:
如果要两点a,b间的路程变短,就需要引入新的顶点k1,k2,……kn,使a—>k1—>k2—>……—>kn—>b更短。 即对a和b之间所有的其他点进行松弛。
DP:状态状态转移方程:d[k][i][j] = min(d[k-1][i][j], d[k-1][i][k]+d[k-1][k][j])
利用滚动数组优化:a[i][j]=min(a[i][j],a[i][k]+a[k][j])【动态规划:0/1背包问题】
相关文章:https://blog.csdn.net/qq_40507857/article/details/80657007
代码:
#include<bits/stdc++.h>
const int maxn=99999999;
using namespace std;
int a[100][100];
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int i,j,k,n,m,t1,t2,t3;
cin>>n>>m;
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
if(i==j)
a[i][j]=0;
else
a[i][j]=maxn;
for(i=1;i<=m;i++)
{
cin>>t1>>t2>>t3;
a[t1][t2]=t3;
}
//Floyd-Warshall算法核心
for(k=1;k<=n;k++)
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
if(a[i][j]>a[i][k]+a[k][j])
a[i][j]=a[i][k]+a[k][j];
for(i=1;i<=n;i++)
{
for(j=1;j<=n;j++)
cout<<a[i][j]<<" ";
cout<<endl;
}
return 0;
}
Dijkstra算法
适用情况:
求从一个顶点到其余各顶点的最短路径算法,不能处理负权边。
时间复杂度:
O(N2)
利用堆优化;在边数M少于N^2的稀疏图来说,可以用邻接表代替邻接矩阵。可优化为O((M+N)logN)。
算法思想:
类似BFS+贪心。通过边来松弛1号顶点到其余各点的距离。选择离起点最近的一个点,扩散出去。
选择距离1号顶点最近的一个顶点作为确定的长度(因为边权值非负,所以距离1号顶点距离最短即意味着不可能通过其他的点中转来缩短距离。然后讨论从已选的点i到其他点j的距离是否能够更短,即dis[j]=min(dis[j],dis[i]+a[i][j])。
如果book数组的值全为1,则1号点到所有的点的距离确定,即1号点到所有点的最短距离确定。
代码:
#include<bits/stdc++.h>
const int maxn=99999999;
using namespace std;
int a[100][100],dis[100],book[100];
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int i,j,n,m,t1,t2,t3,u,v,min1;
cin>>n>>m;
//初始化
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
if(i==j)
a[i][j]=0;
else
a[i][j]=maxn;
for(i=1;i<=m;i++)
{
cin>>t1>>t2>>t3;
a[t1][t2]=t3;
}
//初始化dis数组,表示1号顶点到其余各点的距离
for(i=1;i<=n;i++)
dis[i]=a[1][i];
//初始化book数组,book数组表示从1号顶点到其他顶点的距离是否确定,确定为1,不确定为0
for(i=1;i<=n;i++)
book[i]=0;
book[1]=1;
//dijkstra算法核心语句
for(i=1;i<=n;i++)
{
min1=maxn;
for(j=1;j<=n;j++)
if(book[j]==0 && dis[j]<min1)
{
min1=dis[j];
u=j;
}//找出离1号顶点最近的顶点
book[u]=1;//这个点到1号顶点的距离即是最短距离,将它标记为确定的距离。
//因为所有的边权值都非负。
//所以对于距离1号顶点距离最短的一个点,无法通过其他点的中转来获得更短的距离。
for(v=1;v<=n;v++)
if(a[u][v]<maxn)
dis[v]=min(dis[v],dis[u]+a[u][v]);
//在u->v的距离不为无穷大时,即从u到v有路时,判断是否能够通过中转来缩短距离。
}
for(i=1;i<=n;i++)
cout<<dis[i]<<" ";
return 0;
}
优化:
堆优化:
优化思想:
用堆每次弹出最小值来代替每轮进行的最短边查找。堆用小根堆,可以保证每次在堆顶的元素是最小的。可以使时间复杂度降为O(M+NlogN)。
代码:
//注:手打的代码模板,不保证正确性QAQ
#include<bits/stdc++.h>
const int inf=99999999;
using namespace std;
int vis[1000],maze[1000][1000],dis[1000];
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int n,m,i,j;
int a,b,c;
priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > > que;
//优先队列,用pair第一个值存权值,第二个值存点下标,greater是从按权值小到大排序,即小根堆。
cin>>n>>m;
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
if(i==j) maze[i][j]=0;
else maze[i][j]=inf;
for(i=1;i<=m;i++)
{
cin>>a>>b>>c;
maze[a][b]=c;
}
memset(vis,0,sizeof(vis));
for(i=1;i<=n;i++)
dis[i]=maze[1][i];//初始化dis数组
que.push(make_pair(0,1));//将第一个点放入优先队列
while(!que.empty())
{
int k;
int pre=que.top().second;//pre表示pair的第二个值,即对这个点的边进行松弛操作
que.pop();
if(vis[pre])
continue;
vis[pre]=1;
for(k=1;k<=n;k++)
if(maze[pre][k]<inf)
if(dis[k]>=dis[pre]+maze[pre][k])//如果可以进行松弛操作,就将可以改变的点放入优先队列
{
dis[k]=dis[pre]+maze[pre][k];
que.push(make_pair(dis[k],k));
}
}
for(i=1;i<=n;i++)
cout<<dis[i]<<" ";
return 0;
}
堆优化+链式前向星:
//注:手打的代码模板,不保证正确性QAQ
#include<bits/stdc++.h>
#define pii pair<int,int>
const int N=1e5+10;
const int inf=99999999;
using namespace std;
struct edge
{
int next,to,w;
}maze[N];
int head[N]={0},len=0,vis[N]={0},dis[N];
void add(int u,int v,int w)
{
maze[++len]={head[u],v,w};
head[u]=len;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int n,m,i;
cin>>n>>m;
for(i=1;i<=m;i++)
{
int u,v,w;
cin>>u>>v>>w;
add(u,v,w);
add(v,u,w);
}
for(i=1;i<=n;i++)
dis[i]=inf;
dis[1]=0;
priority_queue<pii,vector<pii>,greater<pii> >que;
que.push(make_pair(0,1));
while(!que.empty())
{
int pre=que.top().second;
que.pop();
if(vis[pre])
continue;
vis[pre]=1;
for(i=head[pre];i;i=maze[i].next)
{
int v=maze[i].to,w=maze[i].w;
if(dis[v]>dis[pre]+w)
{
dis[v]=dis[pre]+w;
que.push(make_pair(dis[v],v));
}
}
}
for(i=1;i<=n;i++)
cout<<dis[i]<<" ";
return 0;
}
Ford算法
适用情况:
可以解决带负权边的图,可以判断是否存在负权回路(负权环)。
时间复杂度:
O(NM)
优化:可以用check来判断dis数组在本轮循环中是否发生变化。如果dis数组没有发生变化,即所求已经是最短路径,即可跳出循环。
算法思想:
对所有的边进行n-1次松弛操作。
进行k次松弛后,我们可以得到1号点最多经过k条边到其余点的最短距离。
能否通过u[i]->vi的边使1号顶点到v[i]的距离缩短,即:dis[v[i]]=min(dis[v[i]],dis[u[i]]+w[i])。
对于正权回路,去掉的话,我们肯定可以得到更短的距离。
对于负权回路,每循环一次都会得到更短的距离。所以我们可以通过进行n-1次循环之后,能不能继续松弛来判断图中是否存在负权回路。
代码:
#include<bits/stdc++.h>
const int maxn=99999999;
using namespace std;
int dis[100],u[100],v[100],w[100],bak[100];
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int i,k,n,m,check,flag;
cin>>n>>m;
//读入数据,即从边u[i]->v[i]的距离w[i]
for(i=1;i<=m;i++)
cin>>u[i]>>v[i]>>w[i];
//初始化dis数组
for(i=1;i<=n;i++)
dis[i]=maxn;
dis[1]=0;
//Ford算法核心语句
for(k=1;k<=n-1;k++)
{
check=0;//记录本轮dis数组是否会更新
//进行松弛
for(i=1;i<=m;i++)
if(dis[v[i]]>dis[u[i]]+w[i]) //对于1号点到v[i]号点,是否存在u[i]点中转使距离更短
{
dis[v[i]]=dis[u[i]]+w[i];
check=1;
}
if(!check)
break;
}
//检查负权回路
flag=0;
for(i=1;i<=m;i++)
if(dis[v[i]]>dis[u[i]]+w[i]) //如果再进行n-1次松弛后,仍然能松弛,即含有负权环。
flag=1;
if(flag)
cout<<"此图含有负权回路"<<endl;
else
for(i=1;i<=n;i++)
cout<<dis[i]<<" ";
return 0;
}
SPFA算法(Ford算法的队列优化)
适用情况:
同Ford算法。
时间复杂度:
最坏情况O(NM)
算法思想:
用邻接表存图;队列操作。
取出队首,对其出边进行松弛操作,将最短路径能够发生改变的点放入队尾。用数组book标识当前那些点已在队列中。
形式上有些类似于BFS。
对于负环的判定:如果一个点进入队列的次数大于等于n-1次,那么就存在负权环。
代码:
#include<bits/stdc++.h>
const int maxn=99999999;
using namespace std;
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int n,m,i,j,k;
int u[8],v[8],w[8];//储存边u[i]->v[i]的权值w[i]
int first[6],next[8];
int dis[6]={0},book[6]={0};
int que[101]={0},head=1,tail=1;//定义队列
cin>>n>>m;
for(i=1;i<=n;i++)
dis[i]=maxn;
dis[1]=0;
for(i=1;i<=n;i++)
first[i]=-1;//初始化first数组,表示1~n顶点暂时没边
for(i=1;i<=m;i++)
{
cin>>u[i]>>v[i]>>w[i];
//建立邻接表
next[i]=first[u[i]];
first[u[i]]=i;
}
que[tail++]=1;
book[1]=1;//1号顶点入队
while(head<tail)
{
k=first[que[head]];//当前要处理的队首顶点
while(k!=-1)//扫描当前顶点所有边
{
if(dis[v[k]]>dis[u[k]]+w[k])
{
dis[v[k]]=dis[u[k]]+w[k];
if(book[v[k]]==0)
{
que[tail++]=v[k];
book[v[k]]=1;
}
}
k=next[k];
}
book[que[head]]=0;
head++;//出队
}
for(i=1;i<=n;i++)
cout<<dis[i]<<" ";
return 0;
}
用链式前向星的代码:
//手打模板,不保证正确性,QAQ.
#include<bits/stdc++.h>
const int inf=99999999;
using namespace std;
struct edge
{
int next,to,w;
}maze[1000];
int head[100],dis[100],vis[100],num[100];
int len=0,n,m;
void add(int u,int v,int w)
{
maze[++len]={head[u],v,w};
head[u]=len;
}
int spfa()
{
queue<int> que;
que.push(1);
while(!que.empty())
{
int pre=que.front(),i;
que.pop();
vis[pre]=0;
for(i=head[pre];i;i=maze[i].next)
{
int v=maze[pre].to;
if(dis[v]>dis[pre]+maze[i].w)
{
dis[v]=dis[pre]+maze[i].w;
if(!vis[v])
{
num[v]++;
if(num[v]>=n)
return 1;//如果入队次数大于n表示有负环。
vis[v]=1;
que.push(v);
}
}
}
}
return 0;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
memset(vis,0,sizeof(vis));
memset(num,0,sizeof(num));//记录一个顶点的入队次数。
memset(head,0,sizeof(head));
int i;
cin>>n>>m;
for(i=1;i<=n;i++)
dis[i]=inf;
for(i=1;i<=m;i++)
{
int a,b,c;
cin>>a>>b>>c;
add(a,b,c);
// add(b,a,c);//无向图
}
dis[1]=0;
vis[1]=1;
num[1]=1;
if(spfa())
cout<<"含负环"<<endl;
else
for(i=1;i<=n;i++)
cout<<dis[i]<<" ";
return 0;
}