图论_最短路算法:
1. Floyd:
·时间复杂度:O(n3)
·使用范围:无负权回路的稠密图,应用于数据较小的题目,可以求出任意两点之间的最短路
memset(d,0x3f,sizeof d);//初始化
for(int k=1; k<=n; k++){//枚举中间点
for(int i=1; i<=n; i++){
if(i!=k)
for(int j=1; j<=n; j++){
if(i!=j&&j!=k)
d[i][j]=min(d[i][j], d[i][k]+d[k][j]);
}
}
}
https://www.luogu.com.cn/problem/P1119
https://www.luogu.com.cn/problem/P1522
拓展:
1. floyd求最小环
memset(d, 0x3f ,sizeof(d));//d[i][j]表示i到j的最小值,a[i][j]为矩阵存图
int ans=0x3f3f3f3f;
for(int k=1; k<=n; k++){
for(int i=1; i<k; i++)
for(int j=i+1; j<k; j++)
ans=min(ans, d[i][j]+a[i][k]+a[k][j]);//求最小环
for(int i=1; i<=n; i++)
for(int j=1; j<=n; j++)
d[i][j]=min(d[i][j], a[i][k]+a[k][j]);//更新最短路
}
2. 求最小环路径
int d[105][105],a[105][105], pos[105][105],ans=1e9;
vector<int> path;
void dfs(int i,int j){
int k=pos[i][j];
if(k==0) return;
dfs(i, k);
path.emplace_back(k);
dfs(k,j);
}
void get_p(int i,int j,int k){
path.clear();
path.emplace_back(k);
path.emplace_back(i);
dfs(i, j);
path.emplace_back(j);
}
void floyd_path(){
for(int k=1; k<=n; k++){
for(int i=1; i<k; i++)
for(int j=i+1; j<k; j++)
if(ans > (d[i][j]+a[i][k]+a[k][j]) ){
ans = (d[i][j]+a[i][k]+a[k][j]);
get_p(i,j,k);//通过递归寻找i,j之间的点,并记录下来
}
for(int i=1; i<=n; i++)
for(int j=1; j<=n; j++)
if(d[i][j] > d[i][k]+d[k][j]){
d[i][j] = d[i][k]+d[k][j];
pos[i][j]=k;
}
}
}
2. Dijkstra算法:
朴素Dijkstra
时间复杂度:n^2
使用范围:稠密图
#include <bits/stdc++.h>
using namespace std;
const int N=1e3+10;
int a[N][N],dis[N],vis[N],n,m,s;
void dijkstra(){
memset(dis,0x3f,sizeof dis);
dis[s]=0;
for(int i=1;i<n;i++){
int x=0;
for(int j=1;j<=n;j++)
if(!vis[j]&&(x==0||dis[j]<dis[x]))x=j;//找出最小的点
vis[x]=1;
for(int j=1;j<=n;j++){
dis[j]=min(dis[j],dis[x]+a[x][j]);//用最小点更新其他点
}
}
}
int main(){
cin>>n>>m>>s;
memset(a,0x3f,sizeof a);
for(int i=1;i<=m;i++){
int u,v,w;
scanf("%d %d %d",&u,&v,&w);
a[u][v]=min(w,a[u][v]);//处理重边
}
dijkstra();
for(int i=1;i<=n;i++)
cout<<dis[i]<<" ";
return 0;
}
·时间复杂度:nlog(n)
·使用范围:稀疏图,不能处理负边权
const int INF=(1<<30);
int n,m,head[MAXN],cnt=0;
struct node1{
int v,next,w;
}edge[MAXN];
struct node{
int l,id;
node(){
}
node(int l,int id):l(l),id(id){
}
bool operator <(const node &a)const{
return l>a.l;
}
};
bool in[MAXN]={0};
long long dis[MAXN],ans=0;//dis[i]表示第i个点到开始点的最短路径,
//找到新的点后将他的(dis[i],i)加入优先队列 ,从优先队列中的顶部也就是
//到开始点距离最短的点开始遍历他的儿子,并且将还未进入队列的点处理后入队
void add(int u,int v,int w){
edge[++cnt].v=v;
edge[cnt].next=head[u];
edge[cnt].w=w;
head[u]=cnt;
}
void Dijkstra(){
for(int i=1;i<=n;i++){
dis[i]=INF;
}
dis[1]=0;
priority_queue<node> q;
q.push(node(0,1));
while(!q.empty()){
node x=q.top();q.pop();
//if(x是要找的点)break;因为再处理下去路径只会越来越长,不是最短路
if(in[x.id])continue;
in[x.id]=1;
for(int i=head[x.id];i;i=edge[i].next){
int v=edge[i].v,w=edge[i].w;
if(x.l+w<dis[v])
{dis[v]=x.l+w;
/*假如需要得到最短路径,则需要加上from[v]=u;
再调用递归函数
void out(int u){
if(u==0)return;
out(from[u]);
cout<<u<<””;
}
*/
q.push({dis[v],v});
}
}
}
}
模板题:https://www.luogu.com.cn/problem/P3371
模板题:https://www.luogu.com.cn/problem/P4779
- 还可以用于有条件的最短路:https://www.luogu.com.cn/problem/P1266
- dijkstra算法是可以解决多源多汇的问题:求集合A中的点到集合B中的点的距离的最小值,保证集合A、B没有相交的点,我们把集合A中的点(也就是与超级源点S相连的点)全部塞到堆中,同时在dis数组中把对应的值置为0,再新开一个数组标记集合B中的点(也就是与超级汇点相连的点)为true,那么在跑dijkstra算法的过程中,第一次访问到了标记为true的点,就得到了我们想要的答案。对于无向图来说,我们只需跑一次dijkstra,即集合A或集合B做起始点都可以;对于有向图来说,我们需要跑两次dijkstra,第一次A为源点B为汇点,第二次A为汇点B为源点。HDU 6166
- 正反跑一次dijkstra,染色:https://www.luogu.com.cn/record/75634396
3.Bellman-Ford算法:
·时间复杂度:O(nm)
·适用范围:作用于负权边无环图求最短路
const int MAXN=10001;
int dis[MAXN],v[MAXN],u[MAXN],w[MAXN],n,m;//dis[i]表示 点i到s的最短距离
int main(){
cin>>n>>m>>s>>t;//s表示起点,t表示终点
for(int i=1;i<=m;i++){
cin>>u[i]>>v[i]>>w[i];
}
memset(dis,0x7f,sizeof(dis));
dis[s]=0;
for(int i=1;i<=n-1;i++){
for(int j=1;j<=m;j++){
if(dis[v[j]]>dis[u[j]]+w[j])
dis[v[j]]=dis[u[j]]+w[j];
}
}
cout<<dis[t];
}
4.SPFA
·时间复杂度 :O(nlog(n))
·适用范围:当给定的图存在负 权边,而Bellman-Ford算法的复杂度又过高,SPFA算法便成了解题利器。因为原理和Bellman-Ford相同,所以依然可以判断负环是否存在。唯一不足是会被网格图卡
queue<int> q;
bool inque[N];//记录是否在队列中 避免重复
int cnt[N];//记录入队次数 判断负环
bool SPFA(int s){
for(int i=1;i<=n;i++)inque[i]=0,cnt[i]=0,d[i]=INF;
d[s]=0;cnt[s]=1;
q.push(s);
while(!q.empty()){
int u=q.front();q.pop();
inque[u]=false;
for(int i=head[u];i;i=E[i].next){
int v=E[i].v,w=E[i].w;
if(d[v]>d[u]+w){
d[v]=d[u]+w;
if(!inque[v]){
inque[v]=true;
if(++cnt>n)return false;//判断负环
q.push(v);
}
}
}
}
return true;
}
5. Johnson 全源最短路
·时间复杂度: O(nmlog m)
·适用范围:求出无负环图上任意两点间最短路径的算法。用 Bellman-Ford 算法求出从 0 号点到其他所有点的最短路,记为 hi。假如存在一条从u点到v点,边权为 w 的边,则我们将该边的边权重新设置为w+hu-hv。接下来以每个点为起点,跑 n轮 Dijkstra 算法即可求出任意两点间的最短路了。
[https://www.luogu.com.cn/problem/solution/P5905]
const int inf=1e9;
int n,m,cnt=0,head[5010],dis[5010],h[3010],t[3010];
bool vis[3010]={0},in[3010]={0};
struct node1{
int v,next,w;
}edge[10010];
struct node{
int l,id;
node(){
}
node(int l,int id):l(l),id(id){
}
bool operator <(const node &a)const{
return l>a.l;
}
};
inline void add(int u,int v,int w){
edge[++cnt].v=v;
edge[cnt].w=w;
edge[cnt].next=head[u];
head[u]=cnt;
}
bool spfa(int s){
queue<int>q;
memset(h,63,sizeof(h));
h[s]=0,vis[s]=1;
q.push(s);
while(!q.empty()){
int x=q.front();
q.pop();
vis[x]=0;
for(int i=head[x];i;i=edge[i].next){
int v=edge[i].v;
if(h[v]>h[x]+edge[i].w){
h[v]=h[x]+edge[i].w;
if(!vis[v]){
vis[v]=1;
q.push(v);
t[v]++;
if(t[v]==n+1)
return 0;
}
}
}
}
return 1;
}
void Dijkstra(int s){
memset(in,0,sizeof(in));
for(int i=1;i<=n;i++)
dis[i]=inf;
dis[s]=0;
priority_queue<node> q;
q.push(node(0,s));
while(!q.empty()){
node x=q.top();q.pop();
if(in[x.id])continue;
in[x.id]=1;
for(int i=head[x.id];i;i=edge[i].next){
int v=edge[i].v,w=edge[i].w;
if(dis[v]>x.l+w)
dis[v]=x.l+w,q.push(node(dis[v],v));
}
}
}
signed main(){
cin>>n>>m;
for(int i=1,u,v,w;i<=m;i++){
cin>>u>>v>>w;
add(u,v,w);
}
for(int i=1;i<=n;i++)
add(0,i,0);
if(!spfa(0))
{cout<<"-1"<<endl;
return 0;
}
for(int u=1;u<=n;u++)
for(int i=head[u];i;i=edge[i].next)
edge[i].w+=h[u]-h[edge[i].v];
for(int i=1;i<=n;i++){
Dijkstra(i);
int ans=0;
for(int j=1;j<=n;j++)
{if(dis[j]==inf)
ans+=j*inf;
else
ans+=j*(dis[j]+h[j]-h[i]);
}
cout<<ans<<endl;
}
}