三种常用最短路的分析优化

8 篇文章 0 订阅
6 篇文章 0 订阅

前言

OO最短路分三种算法,分别是Floyd,dijkstra,spfa。
不同于其他算法的事,最短路的三种算法全都要求掌握。

OOFloyd是多源最短路算法,时间复杂度O(n^3)。
OOdijkstra与spfa都是单源最短路算法,但dijkstra不支持负权,spfa容易被卡常数。另外再提一句,没有堆优化的spfa不如dijkstra。

问题基本类型

OO求解最短路经常会碰到两种题型,分别为单源最短路和多源最短路求解。

OO在某些情况下可能会碰到类似于给定m个特定的点,求经过这些节点的最短路(径)。更有甚者,还会给出一些特定的顺序,此处就不提这么多了。但是具体选择Floyd或者dijkstra,主要还是要看图的大小。

算法解析

Floyd

OO该算法虽然不怎么用,但也是一个比较有用的算法。有关于Floyd的题目也可以非常难其实也就是省选难度啦,比如说 POI 2012 FES-Festival。但是在这里作为一个栗子,做一做典型的 寻宝之路 就好了。以下是鄙人的代码
OO

#include<bits/stdc++.h>
using namespace std;
int n,m,ans=0,d[121][121],dis[121][121],way[10050];

int main () {
	cin >> n >> m;
	memset(dis,0x1,sizeof dis);
	for (int i=1; i <= m; i++) cin >> way[i];
	for (int i=1; i <= n; i++) 
		for (int j=1; j <= n; j++) cin >> d[i][j],dis[i][j]=d[i][j];
	for (int k=1; k <= n; k++) 
		for (int i=1; i <= n; i++) 
			for (int j=1; j <= n; j++) 
				dis[i][j]=dis[i][j]>dis[i][k]+dis[k][j]?dis[i][k]+dis[k][j]:dis[i][j];
	for (int i=1; i < m; i++) ans+=dis[way[i]][way[i+1]];
	cout << ans;
}

OO
OO
OO据说是可以Ac的,有 网站 为证。

OO

Dijkstra

OO

定义

Dijkstra(迪杰斯特拉)算法是典型的单源最短路径算法,用于计算一个节点到其他所有节点的最短路径。主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止。Dijkstra算法是很有代表性的最短路径算法,在很多专业课程中都作为基本内容有详细的介绍,如数据结构,图论,运筹学等等。Dijkstra一般的表述通常有两种方式,一种用永久和临时标号方式,一种是用OPEN, CLOSE表的方式,这里均采用永久和临时标号的方式。注意该算法要求图中不存在负权边。

运行原理

Dijkstra算法在运行时,首先要定义起点s,也就是源。初始化将d[i]赋值为∞,d[s]=0。每一次向外扩展时,询问所有未标记点,找出源路径最短的点并标记,同时从这个点出发更新所有相邻的未标记点。进行n-1次循环以使得计算出所有点的最短源路径。

OO

模板题code

OO此处讲的是经典模板弱化版,众所周知,没有优化想打出ac的标准版是不可能的

OO是鄙人的代码

#include <bits/stdc++.h>
using namespace std;
int head[500050],to[500050],nxt[500050],val[500050],
cnt=0,vis[10050],dis[10050];

void add(int x,int y,int z) {
	nxt[++cnt]=head[x];
	head[x]=cnt;
	to[cnt]=y;
	val[cnt]=z;
}

int main () {
	 int n,m,s,x,y,v;
	 scanf("%d%d%d",&n,&m,&s);
	 for (int i=1; i <= m; i++) scanf("%d%d%d",&x,&y,&v),add(x,y,v);
	 for (int i=1; i <= n; i++) dis[i]=2147483647;
	 dis[s]=0;
	 for (int i=1; i <= n; i++) {
	 	int minn=2147483647,t=-1;
	 	for (int j=1; j <= n; j++) 
	 		if (!vis[j]&&dis[j]<minn) {
	 			minn=dis[j];
	 			t=j;
			 }
		if(t == -1) break;
		vis[t]=1;
		for (int j=head[t];j;j=nxt[j]) {
            if(!vis[to[j]]&&dis[to[j]]>val[j]+dis[t])
                dis[to[j]]=val[j]+dis[t];
		}
	 }for (int i=1; i <= n; i++) 
	 	printf("%d ",dis[i]);
}

堆优化code

OO我觉得这应该是找不到 模板题 的吧(雾)

OO还是鄙人的代码

#include<stdio.h>
#include<queue>
#include<algorithm>
#include<iostream>
using namespace std;
int nex[400001];
int to[400001];
int val[400001];
int head[200001];
int f[200001];
int idx,x,y,z;
int inf=2147483647;
bool vis[200001];
struct Point
{
    int number,dis;
    inline bool operator < (const Point &a) const
    {
        return dis>a.dis;
    }
};
priority_queue <Point> q;
void addedge(int a,int b,int c)
{
    nex[++idx]=head[a];
    head[a]=idx;
    to[idx]=b;
    val[idx]=c;
}
int n,m,s;
int main()
{
    Point tmp;
    scanf("%d%d%d",&n,&m,&s);
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d%d",&x,&y,&z);
        addedge(x,y,z);
    }
    for(int i=1;i<=n;i++)
        f[i]=inf;
    tmp.number=s,tmp.dis=0;
    f[tmp.number]=0;  
    q.push(tmp);
    while(!q.empty())
    {
        int here=q.top().number;
        q.pop();
        if(vis[here])
            continue;
        vis[here]=1;
        for(int j=head[here];j;j=nex[j])
            if(f[to[j]]>val[j]+f[here]&&(!vis[to[j]]))
            {
                f[to[j]]=f[here]+val[j];
                tmp.number=to[j];
                tmp.dis=f[to[j]];
                q.push(tmp);
            }
    }
    for(int i=1;i<=n;i++)
        printf("%d ",f[i]);
}

OOemmmm
OO问题不大

SPFA

OO

适用范围

当图中含有负边,这是dijkstra就没有用武之地,SPFA的作用就体现出来了。SPFA适用于存在负边权的图,包括求最短路或判负环(边权和为负数的环)。

OO

运行原理

建立一个队列,初始时队列里只有起始点,再建立一个二维记录起始点到所有点的最短路径(出事赋∞,每个点到自己的距离为0)。然后使用队列里的点作为起始点去刷新到其他点的最短路,若刷新成功且被刷新点不在队列中,则加入到队列最后。重复执行直到队列为空。

Ps:若某个点进入队列超过N次则代表该图存在负环。
鄙人不曾做过SPFA,并且也找不到SPFA的朴素算法

下面是我copied的堆优化和判负环
OO

堆优化code

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
#include<cmath>
#include<queue>
#include<vector>
#include<climits>
using namespace std;
#define maxn 99999999
struct E{
    int next;
    int c;
    int zhi;
}e[1005000];

inline int read()
{
  char ls=getchar();for (;ls<'0'||ls>'9';ls=getchar());
  int x=0;for (;ls>='0'&&ls<='9';ls=getchar()) x=x*10+ls-'0';
  return x;
}
int last[100500];
int dis[100500];
int n,m;
int k;
int located[100500];
int heap[300500];
int heap_size;
inline void put(int d)
{
    int now,next;
    heap[++heap_size]=d;
    now=heap_size;
    located[d]=now;
    while(now>1)
    {
        next=now>>1;
        if(dis[heap[now]]>=dis[heap[next]])break;
        located[d]=next;
        located[heap[next]]=now;
        swap(heap[now],heap[next]);
        now=next;
    }
    return;
}
inline void change(int d)
{
    int now,next;
    now=located[d];
    while(now>1)
    {
        next=now>>1;
        if(dis[heap[now]]>=dis[heap[next]])break;
        located[d]=next;
        located[heap[next]]=now;
        swap(heap[now],heap[next]);
        now=next;
    }
    return;
}
inline int get()
{
    int now,next,res;
    res=heap[1];
    heap[1]=heap[heap_size--];
    now=1;
    located[heap[1]]=1;
    located[res]=0;
    while(now*2<=heap_size)
    {
        next=now*2;
        if(next<heap_size&&dis[heap[next+1]]<dis[heap[next]])++next;
        if(dis[heap[now]]<=dis[heap[next]])break;
        located[heap[now]]=next;
        located[heap[next]]=now;
        swap(heap[next],heap[now]);
        now=next;
    }   
    return res;
}

void spfa(int s)
{
    dis[s]=0;put(s);
    while(heap_size!=0)
    {
        int x;
        x=get();
        for(int j=last[x];j;j=e[j].zhi) 
        {
            int y=e[j].next;
            int c=e[j].c;
            if(dis[x]+c<dis[y])
            {
                dis[y]=dis[x]+c;
                if(located[y]==0)put(y);
                else change(y);
            }
        }
    }
    return;
}

int main()
{
    n=read(),m=read();
    for(int i=1;i<=m;i++)
        {
            int a1,b1,c1;
            a1=read();b1=read();c1=read();
            e[++k].next=b1;
            e[k].c=c1;
            e[k].zhi=last[a1];
            last[a1]=k;

            e[++k].next=a1;
            e[k].c=c1;
            e[k].zhi=last[b1];
            last[b1]=k;
        }
    for(int i=1;i<=n;i++)
     dis[i]=maxn;
     spfa(1);
    cout<<dis[n]<<endl;
    return 0;
}

OO

判负环code

来自于负环(模板)

 //7777777
    #include<stdio.h>
    #include<iostream>
    #include<algorithm>
    #include<math.h>
    #include<cstring>
    #include<string>
    #include<queue>
    #include<utility>
    #include<vector>
    #define lson l , m , rt << 1
    #define rson m+1 , r , rt << 1 | 1
    #define mem(a,b) memset(a,b,sizeof(a))
    using namespace std;
    typedef long long ll;
    const double pi = 3.1415926535;
    const double eps = 1e-6;
    const int MXN = 1e5 + 7;
    const int MXM = 1e6 + 7;
    const int MX = 1e4 + 7;
    const int maxbit = 18;
    const double val = pi/180.0;
    const int INF = 0x3f3f3f3f;
    const ll LINF = 0x3f3f3f3f3f3f3f3f;
    struct Edge
    {
        int to,w,next;
    }e[MX << 1];
    int cnt,n,m;
    int head[MX];
    inline void add(int u,int v,int w)
    {
        e[++cnt].to = v;
        e[cnt].w = w;
        e[cnt].next = head[u];
        head[u] = cnt;
    }
    bool SPFA(int S)
    {
        int num[MX] = {0};
        int vis[MX] = {0};
        int d[MX];
        queue<int>q;
        mem(vis,0);
        for(int i = 1;i <= n;++i) d[i] = INF;
        d[S] = 0;
        vis[S] = 1;
        q.push(S);
        while(!q.empty())
        {
            int u = q.front();
            q.pop();
            vis[u] = 0;
            for(int i = head[u];i;i=e[i].next)
            {
                int v = e[i].to;
                int w = e[i].w;
                if(d[v] > d[u] + w){
                    d[v] = d[u] + w;
                    if(!vis[v])
                    {
                        q.push(v);
                        vis[v] = 1;
                        num[v]++;
                    }
                    if(num[v] > n) return true;//存在负环
                }
            }
        }
        return false;//不存在负环
    }
    void init()
    {
        mem(head,0);
        mem(e,0);
        cnt = 0;
    }
    int main()
    {
        int T;
        cin >> T;
        while(T--)
        {
            init();
            cin >> n >> m;
            while(m--)
            {
                int u,v,w;
                cin >> u >> v >> w;
                add(u,v,w);
                //此题中要求 若出现负边则为单向边
                if(w >= 0) add(v,u,w);
            }
            if(SPFA(1)) cout << "YE5" << endl;
            else cout << "N0" << endl;
        }
        return 0;
    }

OO

真的结束了,更多精彩内容请点击这里

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值