最短路基础

关于各种算法的分析,这篇文章讲的很清楚,包括优化等问题。文章链接
下面是我对算法的一些个人理解和我自己学的时候的疑惑的解释。
(目前只学会了最朴素形式的Dijkstra算法,其他的过几天再更)
Dijkstra 算法(类似最小生成树的prim算法)

将起点的距离设为0,然后进行n次循环,每次先找到到起点距离最短的点u,再将该点作为中转点,更新与其链接点到起点的最短距离,如果之前与其链接的点与起点的距离小于以点u为中转点到起点的距离,就不更新,反之,则更新,这样就保证了,每一步完成后,所以未标记点到起点的距离都是暂时最小的(因为后序的更新可能会改变剩下未标记点最短路的距离),因为我们求的是最短路,所以我们选一个目前dis最小的节点进行扩展。若是选择最大路径开始扩展,那么以后的点扩展到该点时时,若是有到该点的路径小于原路径的值,但是由于这点之前以及扩展过了,就不会更新最小值了,从而出现错误,而选择当前最小值的点进彳亍扩展,就是保证,每一次扩展该点并标记后,该点已经达到起点到该点的最短路了。
关于为什么该算法不能处理带有负权重的图,请参考这篇文章关于为什么不能带有处理负权值的图
下面以hdu2544作为模板hdu2544最短路

/*Dijkstra算法 单源最短路径,不能处理负值权*/

#include <iostream>
#include <string.h>
#define INF 99999999
#define MAXN 105

using namespace std;

int vis[MAXN],dis[MAXN],map[MAXN][MAXN];/*标记,起点到各个点最短距离,储存图*/

int Dijkstra(int start,int n)/*这里传的n即当做点的个数,也作为循环终点*/
{
    for(int i = 1; i <= n; i++){
        dis[i] = INF;
    }
    dis[start] = 0;
    for(int i = 1; i <= n; i++){
        int mina = INF;
        int k;
        for(int j = 1; j <= n; j++){/*所有未标记点中选取dis最小的点*/
            if(!vis[j]&&dis[j] < mina){
                mina = dis[j];
                k = j;
            }
        }
        vis[k] = 1;/*标记结点*/
        if(k == n){/*当到达要计算的终点时,返回起点到该点的最短路*/
            return dis[n];
        }
        for(int j = 1; j <= n; j++){
            if(!vis[j]&&dis[j] > dis[k] + map[k][j]){//更新起点到未到达点的最短路,保证dis
                dis[j] = dis[k] + map[k][j];        //中存储的值都是目前位置起点1到该点的最短路径值
            }
        }
    }
}
int main()
{
    int m,n;
    while(cin>>n>>m&&m + n != 0){
        memset(vis,0,sizeof(vis));
        for(int i = 1; i <= n; i++){
            for(int j = 1; j <= n; j++){
                map[i][j] = INF;
            }
        }
        int from,to,s;
        for(int i = 0; i < m; i++){
            cin>>from>>to>>s;
            if(map[from][to] > s){
                map[from][to] = map[to][from] =  s;/*防止出现重复边*/
            }
        }
        int minx = Dijkstra(1,n);
        cout<<minx<<endl;
    }
    return 0;
}

Dijsktra 算法实现最短路例题Silver Cow Party

Floyd算法:
这个算法刚开始确实不太好理解,这篇文章就很值观的展示了该算法的实现过程Floyd算法

负环:负环肯定就是整个环就是负的哇,而环是一个整体,肯定是组环的所有权值的加和了

这里参考了Floyd(弗洛伊德算法) 求最短路径 有向图

另外需要注意的是:Floyd-Warshall算法不能解决带有“负权回路”(或者叫“负权环”)的图,因为带有“负权回路”的图没有最短路。例如下面这个图就不存在1号顶点到3号顶点的最短路径。因为1->2->3->1->2->3->…->1->2->3这样路径中,每绕一次1->-2>3这样的环,最短路就会减少1,永远找不到最短路。其实如果一个图中带有“负权回路”那么这个图则没有最短路。
在这里插入图片描述

该算法可以处理负权值的问题,但是不能解决负权值环的问题,可以判断是不是有负环,但是时间卡的很紧有时候会超时,所以之后还是多用spfa吧。
下面给出核心代码

void Floyd(int n){//核心代码
    for(int k = 1; k <= n; k++){
        for(int i = 1; i <= n; i++){
            if(discow[i][k] == INF){//剪枝减少运算量
                continue;
            }
            for(int j = 1; j <= n; j++){
                discow[i][j] = min(discow[i][j],discow[i][k] + discow[k][j]);
            }
        }
    }
}

下面给出例题poj2139 Six Degrees of Cowvin Bacon
ac代码

#include <iostream>
#include <cstdio>
#include <cmath>
#include <string.h>
#define INF 100000
#define MAXN 305
#define MAX 3005

using namespace std;

int discow[MAXN][MAXN],x[MAX];

void init(int n){//初始化
    for(int i = 1; i <= n; i++){
        for(int j  = 1; j <= n; j++){
            if(i == j){
                discow[i][j] = 0;
            }
            else{
                discow[i][j] = INF;
            }
        }
    }
}

void Floyd(int n){//核心代码
    for(int k = 1; k <= n; k++){
        for(int i = 1; i <= n; i++){
            if(discow[i][k] == INF){
                continue;
            }
            for(int j = 1; j <= n; j++){
                discow[i][j] = min(discow[i][j],discow[i][k] + discow[k][j]);
            }
        }
    }
}

int main(){
    int n,m;
    cin>>n>>m;
    init(n);
    for(int i = 1; i <= m; i++){
        int t;
        cin>>t;
        memset(x,0,sizeof(x));
        for(int j = 1; j <= t; j++){
            cin>>x[j];
        }
        for(int j = 1; j <= t; j++){
            for(int k = j + 1; k <= t; k++){
                discow[x[j]][x[k]] = discow[x[k]][x[j]] = 1;
            }
        }
    }
    Floyd(n);
    int ans = INF;
    for(int i = 1; i <= n; i++){
        int temp = 0;
        for(int j = 1; j <= n; j++){
            temp += discow[i][j];
        }
        ans = min(ans,temp);
    }
    ans = ans * 100 / (n - 1);//减少误差
    cout<<ans<<endl;
    return 0;
}

Floyd算法判断负环
poj3259 Wormholes

#include <iostream>
#include <cstdio>
#include <cmath>
#include <string.h>
#define INF 100000
#define MAXN 505
#define MAX 3005

using namespace std;

int discow[MAXN][MAXN];

void init(int n){//初始化
    for(int i = 1; i <= n; i++){
        for(int j  = 1; j <= n; j++){
            if(i == j){
                discow[i][j] = 0;
            }
            else{
                discow[i][j] = INF;
            }
        }
    }
}

int Floyd(int n){//核心代码
    for(int k = 1; k <= n; k++){
        for(int i = 1; i <= n; i++){
            if(discow[i][k] == INF){
                continue;
            }
            for(int j = 1; j <= n; j++){
                if(discow[i][j] > discow[i][k] + discow[k][j]){
                    discow[i][j] = discow[i][k] + discow[k][j];
                }
            }
            if(discow[i][i] < 0){
                return 1;
            }
        }
    }
    return 0;
}

int main(){
    int n,m,w,t;
    cin>>t;
    while(t--){
        cin>>n>>m>>w;
        init(n);
        int from,to,s;
        for(int i = 1; i <= m; i++){
            cin>>from>>to>>s;
            if(discow[from][to] > s){
                discow[from][to] = discow[to][from] = s;
            }
        }
        for(int i = 1; i <= w; i++){
            cin>>from>>to>>s;
            discow[from][to] = -s;
        }
        if(Floyd(n)){
            cout<<"YES"<<endl;
        }
        else{
            cout<<"NO"<<endl;
        }
    }
    return 0;
}

因为太懒了。。所以转了一个spfa判断负环的代码,这里是原文代码出处
这里说一下,为什么有负环的条件是大于n,因为每两个点之间的最短路是k - 1条边,最远的两点可能距离n - 1 条边,所以就设置n为判断条件,当然如果不考虑时间也可以设成更大的值,但是没必要。
再尝试解释下一个疑惑,为什么用单源最短路算法可以判断负环,因为若是存在负环,那么我们规定的开始起点,到负环起点的距离,一定是不断缩小的,也就是该环上的点一定会不断重复进入队列,出队列,导致队列不可能为空,也就照应了如果某点进入队列次数>n的判断条件

#include <stdio.h>
#include <algorithm>
#include <stdlib.h>
#include <string.h>
#include <iostream>
#include <queue>
 
using namespace std;
 
typedef long long LL;
 
const int N = 600;
const int INF = 0x3f3f3f3f;
 
int G[N][N], dis[N], num[N], n, s;
bool vis[N];
 
int spfa()
{
    queue<int>que;
    for(int i = 1; i <= n; i++)
    {
        dis[i] = INF;
        num[i] = 0;
    }
    dis[1] = 0;
    vis[1] = true;
    que.push(1);
    while(!que.empty())
    {
        int now = que.front();
        que.pop();
        vis[now] = false;//记得还原!!
        for(int i = 1; i <= n; i++)
        {
            if(dis[now]+G[now][i]<dis[i])
            {
                dis[i] = dis[now]+G[now][i];
                if(!vis[i])
                {
                    vis[i] = true;
                    num[i]++;
                    if(num[i] > n) return 1;
                    que.push(i);
                }
            }
        }
    }
    return 0;
}
 
int main()
{
  //  freopen("in.txt", "r", stdin);
    int f, m, e, w, t;
    scanf("%d", &f);
    while(f--)
    {
        scanf("%d%d%d", &n, &m, &w);
        memset(vis, 0, sizeof(vis));
        for(int i = 1; i <= n; i++)
            for(int j = 1; j <= n; j++)
            {
                if(i == j) G[i][j] = 0;
                else G[i][j] = INF;
            }
        for(int i = 1; i <= m; i++)
        {
            scanf("%d%d%d", &s, &e, &t);
            if(t < G[s][e]) G[s][e] = G[e][s] = t;
        }
        for(int i = 1; i <= w; i++)
        {
            scanf("%d%d%d", &s, &e, &t);
            G[s][e] = -t;
        }
        int ans = spfa();
        if(ans) printf("YES\n");
        else printf("NO\n");
    }
    return 0;
}

比赛的时候遇到最短路问题,结果邻接图空间爆了,唉偷懒了呀,这里再补充一下链式前向星的存储方法链式前向星
关于链式存储,真的比邻接图要好用的多,不会被卡空间上,以后建议多使用链式存储
这里以上面hdu2544为模板,把上面的代码修改了一下

#include <iostream>
#include <string.h>
#define MAXM 10010
#define MAXN 105
#define INF 9999999

using namespace std;

typedef struct {
    int to;
    int v;
    int next;
}Edge;

Edge edge[MAXM * 2];
int vis[MAXN],head[MAXN],dis[MAXN],cnt;

void addedge(int a,int b,int c){
    cnt++;
    edge[cnt].next = head[a];
    edge[cnt].to = b;
    edge[cnt].v = c;
    head[a] = cnt;
}

int dij(int start ,int n){
    for(int i = 1; i <= n; i++){
        dis[i] = INF;
    }
    dis[start] = 0;
    for(int j = 1; j <= n; j++){
        int min = INF;
        int u;
        for(int i = 1; i <= n; i++){
            if(!vis[i]&&dis[i] < min){
                min = dis[i];
                u = i;
            }
        }
        vis[u] = 1;
        for(int i = head[u]; i; i = edge[i].next){
            if(!vis[edge[i].to]&&dis[edge[i].to] > edge[i].v + dis[u]){
                dis[edge[i].to] = edge[i].v + dis[u];
            }
        }
    }
    return dis[n];
}

int main(){
    int n,m;
    while(cin>>n>>m,m + n != 0){
        int a,b,c;
        cnt = 0;
        memset(vis,0,sizeof(vis));
        memset(head,0,sizeof(head));
        for(int i = 1; i <= m; i++){
            cin>>a>>b>>c;
            addedge(a,b,c);
            addedge(b,a,c);
        }
        int start = 1;
        int ans = dij(start,n);
        cout<<ans<<endl;
    }
    return 0;
}

spfa版本的链式存储代码模板,模板依旧为上面的hdu2544

#include <iostream>
#include <string.h>
#include <queue>
#define MAXM 10010
#define MAXN 105
#define INF 9999999

using namespace std;

typedef struct {
    int to;
    int v;
    int next;
}Edge;

Edge edge[MAXM * 2];
int vis[MAXN],head[MAXN],dis[MAXN],cnt;

void addedge(int a,int b,int c){
    cnt++;
    edge[cnt].next = head[a];
    edge[cnt].to = b;
    edge[cnt].v = c;
    head[a] = cnt;
}

int spfa(int start,int n){
    for(int i = 1; i <= n; i++){
        dis[i] = INF;
    }
    dis[start] = 0;
    queue <int> q;
    q.push(start);
    vis[start] = 1;
    while(!q.empty()){
        int temp = q.front();
        q.pop();
        vis[temp] = 0;
        for(int i = head[temp]; i; i = edge[i].next){
            if(dis[edge[i].to] > dis[temp] + edge[i].v){
                dis[edge[i].to] = dis[temp] + edge[i].v;
                if(!vis[edge[i].to]){
                    q.push(edge[i].to);
                    vis[edge[i].to] = 1;
                }
            }
        }
    }
    return dis[n];
}

int main(){
    int n,m;
    while(cin>>n>>m,m + n != 0){
        int a,b,c;
        cnt = 0;
        memset(vis,0,sizeof(vis));
        memset(head,0,sizeof(head));
        for(int i = 1; i <= m; i++){
            cin>>a>>b>>c;
            addedge(a,b,c);
            addedge(b,a,c);
        }
        int start = 1;
        int ans = spfa(start,n);
        cout<<ans<<endl;
    }
    return 0;
}

这个题是最短路的变形,利用的是spaf的链式存储算法。传送门ICPC Pacific Northwest Regional Contest 2019 C Coloring Contention 最短路

偷懒就被打脸的我,总是在比赛被制裁后再来补。。

这里补一下栈堆优化后的Dijsktra算法,本质就是用优先队列,来替代第一个循环的查找,对于大于1e5,就需要使用了
这里把比赛的题当模板吧
传送门

#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <vector>
#include <cstdio>
#define ll long long
#define MAXN 1000005 + 10
#define INF 9999999

using namespace std;

const ll mod = 1e9+7;

struct node{
    int to;
    int v;
    int next;
}edge[4 * MAXN];

struct enode{
    int dis,to;
};

bool operator <(const enode a,const enode b){
    return a.dis > b.dis;
}

int head[MAXN],cnt,vis[MAXN],dis[MAXN];

ll fast(ll a,ll b){
    ll ans = 1;
    while(b){
        if(b % 2){
            ans  = ans * a % mod;
        }
        a = a * a % mod;
        b /= 2;
    }
    return ans;
}

void addedge(int a,int b,int val){
    cnt++;
    edge[cnt].next = head[a];
    edge[cnt].to = b;
    edge[cnt].v = val;
    head[a] = cnt;
}

void dij(int start,int n){
    for(int i = 1; i <= n; i++){
        dis[i] = INF;
    }
    dis[start] = 0;
    priority_queue <enode> q;
    q.push(enode {0,start});
    while(!q.empty()){
        enode x = q.top();
        q.pop();
        int u = x.to;
        vis[u] = 1;
        for(int i = head[u]; i != 0; i = edge[i].next){
            if(!vis[edge[i].to]&&dis[edge[i].to] > edge[i].v + dis[u]){
                dis[edge[i].to] = edge[i].v + dis[u];
                q.push(enode {dis[edge[i].to],edge[i].to});
            }
        }
    }
}

int main(){
    int m,n;
    scanf("%d%d",&n,&m);
    for(int i = 1; i <= m; i++){
        int a,b;
        scanf("%d%d",&a,&b);
        addedge(a,b,1);
        addedge(b,a,1);
    }
    dij(1,n);
    ll ans = 0;
    for(int i = 2; i <= n; i++){
        ans = (ans + fast(2,dis[i])) % mod;
    }
    printf("%lld",ans);
    return 0;
}


没错,又来更了,最近发现一种使用邻接矩阵给出数据的题目,我又无奈去学习了一下BFS解法和DFS的记忆化搜索
BFS其实和spfa感觉差不多,本质都是不断松弛邻接矩阵上的某一点,不同在于bfs的思路还是从每个点向外扩展,好像。。还是跟spfa差不多。。算了,就当是邻接矩阵版本的spfa吧(doge)
例题
hdu1428

#include <iostream>
#include <queue>
#include <cstring>

using namespace std;

typedef long long ll;
const int INF = 99999999;
const int MAXN = 55;

struct node
{
    int x;
    int y;
};

int dir[4][2]= {{1,0},{0,1},{-1,0},{0,-1}};
ll maps[MAXN][MAXN],dis[MAXN][MAXN],a[MAXN][MAXN],vis[MAXN][MAXN];

bool if_fair(int x,int y,int n)
{
    if(x >= 0&&y >= 0&&x < n&&y < n){
        return true;
    }
    return false;
}

void BFS(int n)
{
    memset(vis,0,sizeof(vis));
    queue <node> q;
    node now,next;
    now.x = n - 1;
    now.y = n - 1;
    dis[n - 1][n - 1] = maps[n - 1][n - 1];
    vis[now.x][now.y] = 1;
    q.push(now);
    while(!q.empty()){
        now = q.front();
        q.pop();
        vis[now.x][now.y] = 0;
        for(int i = 0; i < 4; i++){
            next.x = now.x + dir[i][0];
            next.y = now.y + dir[i][1];
            if(if_fair(next.x,next.y,n)){
                //松弛操作,参考spfa
                if(dis[now.x][now.y] + maps[next.x][next.y] < dis[next.x][next.y]){
                    dis[next.x][next.y] = dis[now.x][now.y] + maps[next.x][next.y];
                    //松弛后如果没有入队列,就进入队列,还是参考spfa
                    if(!vis[next.x][next.y]){
                        vis[next.x][next.y] = 1;
                        q.push(next);
                    }
                }
            }
        }
    }
}

ll DFS(int x,int y,int n)
{
    if(a[x][y] > 0){
        return a[x][y];
    }
    ll count = 0;
    for(int i = 0; i < 4; i++){
        int xx = x + dir[i][0];
        int yy = y + dir[i][1];
        if(if_fair(xx,yy,n)){
            //只要下一步的点到终点的距离小于上一个点的距离,就有解
            if(dis[xx][yy] < dis[x][y]){
                count += DFS(xx,yy,n);
            }
        }
    }
    a[x][y] = count;
    return a[x][y];
}

int main()
{
   int n;
   while(cin>>n){
     for(int i = 0; i < n; i++){
        for(int j = 0; j < n; j++){
            cin>>maps[i][j];
            dis[i][j] = INF;
        }
     }
     BFS(n);
     memset(a,0,sizeof(a));
     a[n - 1][n - 1] = 1;
     ll ans = DFS(0,0,n);
     cout<<ans<<endl;
   }
   return 0;
}

再加几个变形吧
Jzzhu and Cities CodeForces - 449B
hdu3499Flight

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值