最短路

最短路径问题

在一个图中有 n 个点、m 条边。边有权值,例如费用、长度等,权值可正可负。边可能是有向的,也可能是无向的。给定两个点,起点是 s,终点是 t,在所有能连接 s 和 t 的路径中寻找边的权值之和最小的路径,这就是最短路径问题。
注:若图中存在负圈,则不存在最短路问题,若存在负边权,则不能用 Dijkstra算法。

Floyd-Warshall 算法

算法复杂度:图片说明
Floyd 用到了动态规划的思想:求两点 i、j 之间的最短距离,可以分两种情况考虑,即经过图中某个点 k 的路径和不经过点 k 的路径,取两者中的最短路径。注:有重边要在输入的时候更新最小值。

const int maxn = 1e4+1;
const int inf = 0x3f3f3f3f;
int graph[maxn][maxn];

void init(int n) {
    for(int i = 1; i <= n; ++ i) {
        for(int j = 1; j <= n; ++ j) {
            if (i == j) graph[i][j] = 0;
            else graph[i][j] = inf;
        }
    }
}

void Floyd(int n) { // 传递闭包
    for(int k = 1; k <= n; ++ k) { // dp 是否过 k 点
        for(int i = 1; i <= n; ++ i) {
            for(int j = 1; j <= n; ++ j) {
                graph[i][j] = min(graph[i][j], graph[i][k] + graph[k][j]);
            }
        }
    }
}

Bellman-Ford 算法

时间复杂度:图片说明
Bellmen-Ford 算法用来解决单源最短路径问题:给定一个起点 s,求它到图中所有 n 个节点的最短路径。
Bellmen-Ford 算法的特点是只对相邻节点进行计算,可以避免 Floyd 那种大撒网式的无效计算,大大提高了效率,每次操作必定确定一个点的最短路。
注:可打印最短路径,可判断负圈

const int maxn = 1e4+1;
const int inf = 0x3f3f3f3f;
int d[maxn], pre[maxn];

struct edge{
    int u, v, w;
    edge(){};
    edge(int u, int v, int w): u(u), v(v), w(w){};
}e[maxn]; int tot; // tot 注意清空

void print_path(int st, int en){ // 打印从 st 到 en 的最短路
    if (st == en) {printf("%d", st); return ;}
    print_path(st, pre[en]);  // en 点向前搭载
    printf("->%d", en);
}

void Bellman(int st, int n) { // st 的单源最短路
    for(int i = 1; i <= n; ++ i) {
        if (i == st) d[i] = 0;     // 定义起点
        else d[i] = inf;
    }
    int k = 0;        // 记录几次操作 没有负圈最多 n-1 次操作
    bool update = true;
    while(update) {
        update = false;
        if (++ k > n) {printf("有负圈\n"); return ;}   // 第n次操作成功,有负圈
        for(int i = 1; i <= tot; ++ i) {               // 搭载每条边
            int x = e[i].u, y = e[i].v;
            if (d[x] > d[y] + e[i].w) { // 更新最短路
                update = true;
                d[x] = d[y] + e[i].w;
                pre[x] = y;         // 记录前驱点
            }
        }
    }
}

SPFA 算法

时间复杂度: 图片说明
Bellman-Ford 算法有很多低效或无效的操作。分析 Bellman-Ford 算法,其核心部分是在每一***作中更新所有节点到起点 s 的最短距离。根据前面的讨论可知,计算和调整一个节点 u 到 s 的最短距离之后,如果紧接着调整 u 到邻居结点,这些邻居肯定有新的计算结果;而如果漫无目的的计算不与 u 相邻的节点,很可能毫无变换,所以这些操作是低效的。
因此,在计算结点 u 之后,下一步只计算和调整特的邻居,这样能加快收敛过程。这些步骤可以用队列进行操作,这就是 SPFA(很像 BFS)。

const int maxn = 1e6+1;
const int inf = 0x3f3f3f3f;
int dis[maxn];  // 存储最短路
int pre[maxn];  // 存储路径
int Neg[maxn];  // 存储点已遍历次数
bool vis[maxn]; // 判断点是否在队内

struct edge { // 邻接表
    int to, w;
    edge(){}
    edge(int to, int w): to(to), w(w){}
}; vector<edge> g[maxn];

void init(int n) { // 多实例初始化
    for(int i = 1; i <= n; ++ i) {
        g[i].clear();
    }
}

void print_path(int st, int en){ // 打印从 st 到 en 的最短路
    if (st == en) {printf("%d", st); return ;}
    print_path(st, pre[en]);  // en 点向前搭载
    printf("->%d", en);
}

void Spfa(int st, int n) { // SPFA 算法 - 求单源最短路
    for(int i = 1; i <= n; ++ i) { // 算法初始化
        Neg[i] = 0;
        dis[i] = inf;
        vis[i] = false;
    }
    queue<int> q;
    q.push(st);
    Neg[st] = 1;
    dis[st] = 0;
    vis[st] = true;
    while(!q.empty()) {
        int tp = q.front(); q.pop();
        vis[tp] = false;
        for(auto t: g[tp]) {
            int to = t.to;
            if (dis[to] > dis[tp] + t.w) {    // 更新最短路
                dis[to] = dis[tp] + t.w;
                pre[to] = tp;                     // 记录前驱点
                if (!vis[to]) { // to 点进入更新状态
                    q.push(to);
                    vis[to] = true;
                    if (++Neg[to] > n) {printf("有负圈\n"); return ;}  // 第n次操作成功,有负圈
                }
            }
        }
    }
}

Dijstra 算法

时间复杂度 图片说明
Dijkstra 算法也用来解决单源最短路径问题(无法解决有负边权的图)。
在现实生活中,Dijkstra 有另外的模型,例如多米诺骨牌:

  1. 在图中所有的边上排满多米诺骨牌,相当于把骨牌看成图的边。 一条边上的多米诺骨牌数量和边的权值(例如长度或费用)成正比。规定所有的骨牌倒下的速度都是一样的。如果在一个结点上推到骨牌,会导致这个结点上的所有骨牌都往后面倒去。
  2. 在起点 s 推到骨牌,可以观察到,从 s 开始,它连接的边上的骨牌都逐渐倒下,并到达所有能达到的结点。在某结点 t,可能先后从不同的线路倒骨牌过去;先倒过来的骨牌,其经过的路径肯定就是从 s 到 t 的最短路径;后倒过来的骨牌,对确定结点 t 的最短路径没有贡献,不用管它。

注:在程序中不可能随着时间的变化去模拟一遍上述过程,那就用优先队列做一个处理,保证在堆顶的结点一定是时间最短的,通过不断松弛其他点,加入队堆可以更新最短路。

const int maxn = 1e6+1;
const int inf = 0x3f3f3f3f;

struct edge{ // 链式前向星
    int to, next, w;
    edge(){}
    edge(int to, int next, int w): to(to), next(next), w(w){}
}e[maxn]; int head[maxn], tot;

struct node{
    int id, dis;
    node(){}
    node(int id, int dis): id(id), dis(dis) {}
    bool operator> (const node &n1) const { // 小顶堆需重载 >
        return dis > n1.dis;
    }
}pos[maxn];

int pre[maxn];   // 存储路径
bool vis[maxn];  // 是否已得出最短路

void init(int n) { // 多实例初始化
    tot = 0;
    for(int i = 1; i <= n; ++ i) {
        head[i] = -1;
    }
}

void print_path(int st, int en) { // 打印从 st 到 en 的最短路径
    if (st == en) {printf("%d", st); return ;}
    print_path(st, pre[en]); // en 点向前搭载
    printf("->%d", en);
}

void Dijkstra(int st, int n) { // Dijkstra 算法 - 单源最短路
    for(int i = 1; i <= n; ++ i) { // 算法初始化
        pos[i].id = i;
        pos[i].dis = inf;
        vis[i] = false;
    }
    pos[st].dis = 0;
    priority_queue<node, vector<node>, greater<node> > q; // 小顶堆
    q.push(pos[st]);
    while(!q.empty()) {
        node tp = q.top(); q.pop();
        if (vis[tp.id]) continue; // 该点最短路已得出 跳过
        vis[tp.id] = true;
        for(int i = head[tp.id]; ~i; i = e[i].next) {
            int to = e[i].to;
            if (vis[to]) continue;
            if (pos[to].dis > tp.dis + e[i].w) { // 松弛操作 无法处理和判断负圈
                pos[to].dis = tp.dis + e[i].w;   // 经过松弛后,同一个点可能会多次存入
                pre[to] = tp.id;                 // 记录前驱点
                q.push(pos[to]);
            }
        }
    }
}

专题

http://acm.hdu.edu.cn/showproblem.php?pid=6797 【最短路 + 随机数据】

图论 文章被收录于专栏

关于acm竞赛图论的个人笔记

全部评论

相关推荐

真tmd的恶心,1.面试开始先说我讲简历讲得不好,要怎样讲怎样讲,先讲背景,再讲技术,然后再讲提升多少多少,一顿说教。2.接着讲项目,我先把背景讲完,开始讲重点,面试官立即打断说讲一下重点,无语。3.接着聊到了项目的对比学习的正样本采样,说我正样本采样是错的,我解释了十几分钟,还是说我错的,我在上一家实习用这个方法能work,并经过市场的检验,并且是顶会论文的复现,再怎么不对也不可能是错的。4.面试官,说都没说面试结束就退出会议,把面试者晾在会议里面,丝毫不尊重面试者难受的点:1.一开始是讲得不好是欣然接受的,毕竟是学习。2.我按照面试官的要求,先讲背景,再讲技术。当我讲完背景再讲技术的时候(甚至已经开始蹦出了几个技术名词),凭什么打断我说讲重点,是不能听出人家重点开始了?这也能理解,每个人都有犯错,我也没放心上。3.我自己做过的项目,我了解得肯定比他多,他这样贬低我做过的项目,说我的工作是错误的,作为一个技术人员,我是完全不能接受的,因此我就和他解释,但无论怎么解释都说我错。凭什么,作为面试官自己不了解相关技术,别人用这个方式work,凭什么还认为这个方法是错的,不接受面试者的解释。4.这个无可厚非,作为面试官,不打招呼就退出会议,把面试者晾着,本身就是有问题。综上所述,我现在不觉得第一第二点也是我的问题,面试官有很大的问题,就是专门恶心人的,总结面试官说教,不尊重面试者,打击面试者,不接受好的面试者,技术一般的守旧固执分子。有这种人部门有这种人怎么发展啊。最后去查了一下,岗位关闭了。也有可能是招到人了来恶心人的,但是也很cs
牛客20646354...:招黑奴啊,算法工程师一天200?
点赞 评论 收藏
分享
深夜焦虑难以入眠:直通终面也很稳了
点赞 评论 收藏
分享
头像
10-22 20:13
中南大学 Java
序言大家好呀。我是希晨er,一个初入职场的程序猿小登最近上班摸鱼刷到了一篇文章:10年深漂,放弃高薪,回长沙一年有感,还有聊聊30岁大龄程序员过往的心路历程,突然就有点感慨。我如今也做出了和大明哥一样的抉择,只是更早。此外我22年的人生,好像从来没好好记录过。正好现在工作不太忙,就想把这些经历写下来,也希望能得到社区里各位前辈的指点个人背景我是03年出生的西安娃,父母都是普通打工人。刚从中南大学软件工程专业毕业半年,现在在老家的央企过着躺平摆烂的日子成长轨迹从农村到城市的童年我家并不是西安的,只是爸妈在西安上班,从小学之后就把我接到了西安。后来老家房子拆了,爷爷奶奶也搬了过来。农村的生活,我觉...
Yki_:看哭了,恋爱那一段你女朋友说你不够关心她,可你毕竟也愿意遇到矛盾写几千字来和她慢慢分析;说不愿意给她花钱,我感觉可能只是消费观不一样;如果她想留在长沙,也应该提前跟你说开。不过她也许会心疼你放弃大厂offer转向数字马力?我也因为同样的原因有过一段幸福而充满遗憾的感情,不过跟爱情相比确实前途更重要一点。至于offer的选择,换我我也会这么选。把这些旧事记录下来以后,接下来就好好向前看吧,加油兄弟
🍊晨光随笔
点赞 评论 收藏
分享
评论
1
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务