图 单源最短路径Dijkstra & Floyd

单源最短路径

给定一个点,寻找它到每个点权值都最小的边

Dijkstra

伪代码描述
变量描述:给定一个顶点s,d[i]为s->i的最短路径,p[i]存下i的上一个顶点,visit[i]用来标记节点(0未标记,1标记)
以下为了循环好写一点,令s=0,共有n个顶点;

1.初始化:d[0]=0,for(int i=0;i<n;i++)d[i]=INF;
2.for i=1:n-1

  • 在未标定的顶点中,找到d[i]最短的顶点u。
  • 标记u,visit[u]=1
  • 更新d[i],如果d[i]>d[u]+w[u][i],d[i]=d[u]+w[u][i], 且p[i]=u;
    end
//Dijkstra算法 单元最短路径
void Dijkstra(AdjGraph& G,int s) {
    int n = G.VertexNum();
    int *visit = new int[n];
    int *d = new int[n];
    int *p = new int[n];
    for (int i = 0; i < n; i++) {//初始化
        visit[i] = 0;
        d[i] = INFINITY;
        if (i == s) {
            d[s] = 0;
            p[s] = s;
        }
    }
    int control = 0;//n次循环
    int flag = true;
    while (control<n) {
        int min = INFINITY;
        int u = 0;
        //找到未标定的点u且d[u]最小
        for (int i = 0; i < n; i++) {
            if (!visit[i] && d[i] <= min) {
                min = d[i];
                u = i;
            }
        }
        if (flag) {//第一次先标定起始点s
            u = s;
            flag = false;
        }
        visit[u] = 1;//标记访问过u
        //更新d[i]
        for (Edge e = G.FirstEdge(u); G.isEdge(e); e = G.NextEdge(e)) {
            if (d[e.end] > d[u] + e.weight) {
                d[e.end] = d[u] + e.weight;
                p[e.end] = u;
            }
        }
        control++;
    }
//输出
    cout << "start point:" << s << endl;
    cout << "path:" << endl;
    for (int i = 0; i < n; i++) {
        cout << "end point:" << i << " path:"<<i;
        int tmp = p[i];
        while (tmp != s) {
            cout << "<--" << tmp;
            tmp = p[tmp];
        }
        if(i!=s) cout << "<--" << s;
        cout << " weight:"<<d[i]<<endl;
    }
}

不难看出,上面程序的时间复杂度为O(n^2)
优化到O(mlogn)
1.用邻接表存储图
2.用优先队列(stl中的priority_queue)

  • 为什么是O(mlogn)咧?

优先队列的push,pop操作复杂度是logn的(因为优先队列的数据结构是二叉堆,即完全二叉树,搜索复杂度为logn)

代码来自刘汝佳的《算法竞赛入门》

#include<iostream>
#include <functional> 
#include<queue>
#define MAXM 100
using namespace std;

int f[MAXM];//保存下标(节点编号)的第一条边 的编号
int u[MAXM], v[MAXM], w[MAXM], Next[MAXM];//u[e],v[e]存的是边e(下标)的起始和结束节点编号;
//Next[e]表示编号为e的边的下一条边编号
int n, m;
typedef pair<int, int> pp;//pp代表一对数字:d[i]和i

void read_graph()
{
    cin >> n >> m;;
    for (int i = 0; i < n; i++)f[i] = -1;
    for (int e = 0; e < m; e++) {//e表示边的下标
        cin >> u[e] >> v[e] >> w[e];
        Next[e] = f[u[e]];//f[i],i代表的总是顶点下标,所以一直是u[e]
        f[u[e]] = e;
    }
}

void dijkstra() {
    int *d = new int[n];
    int *p = new int[n];
    int *done = new int[n];
    p[0] = 0;
    priority_queue<pp,vector<pp>,greater<pp> > q;//最小堆,greater<pp> 表示cmp大于,头文件functional
    //pp是一个二元组,pair比较大小时先比较 第一维
    for (int i = 0; i < n; i++) {
        d[i] = (i == 0 ? 0 : MAXM);
        done[i] = 0;//节点全都未访问过
        p[i] = -1;
    }
    q.push(make_pair(d[0],0));//make_pair->pp类型
    while (!q.empty()) {
        pp m = q.top();//取出d[u]最小的顶点u
        q.pop();
        int x = m.second;//顶点 m.first是权重,m.second是顶点编号
        if (done[x])continue;//被访问过就跳过
        done[x] = 1;//访问后标记一下
        for (int e = f[x]; e != -1; e = Next[e]) {
            if (d[v[e]] > d[x] + w[e]) {
                p[v[e]] = x;//x->v[e]
                d[v[e]] = d[x] + w[e];
                q.push(make_pair(d[v[e]], v[e]));
            }
        }
    }
    cout << "start point:" << 0 << endl;
    cout << "path:" << endl;
    for (int i = 1; i < n; i++) {
        cout << "end point:" << i << " path:" << i;
        int tmp = p[i];//i的前驱节点
        while (tmp != -1) {//不是0
            cout << "<--" << tmp;
            tmp = p[tmp];//tmp的前驱节点
        }
        cout << " weight:" << d[i] << endl;
    }
}
int main()
{
    read_graph();
    dijkstra();
    return 0;

}

Bellman-Ford算法

Dijkstra无法处理无项负权边的情况
BF算法可以解决 单元最短路径同时也能够处理负边的出现。
即能够判断是否有从源点可达的负环。
算法描述(bool bf(int s):返回false就是存在负边环;返回true即d数组就是单源最短路径):
(用邻接表存储图)
while循环下列操作n-1次:
1.遍历每条边e:u->v
2.如果d[v] > d[u] + w(e), 就更新d[v]
end
然后再遍历一遍边,执行1,2操作,如果有d[v]<d[u]+w(e),说明存在负边环,返回false;


SPFA(shortest path Faster algorithm)

基于Bellman-Ford的优化
其实不用遍历所有的边,只需要遍历d[i]被修改的点的边就行了。
于是用队列来进行操作。
每次取队列队首元素v,然后遍历这个v的边 ,如果d[i]改变,i入队列
直到队列为空

(如果有负环还需增添数组来计算节点入队列的次数如果大于n-1就存在源点可达的负环。)


Floyd算法---解决全源最短路径问题

[这个描述挺清楚地](http://wiki.jikexueyuan.com/project/easy-learn-algorithm/floyd.html
核心代码部分

for(k=0;k<n;k++)
   { 
        for(i=0;i<n;i++)
           for(j=0;j<n;j++)
               if(A[i][j]>(A[i][k]+A[k][j]))
               {
                     A[i][j]=A[i][k]+A[k][j];
                     path[i][j]=k;
                } 
     } 

算法流程描述:
选择一个节点k,然后遍历图(二维数组),如果A[i][j] > A[i][k]+A[k][j];
就更新A[i][j]=A[i][k]+A[k][j];
直到k取完图中节点~
算法复杂度为O(n^3).

习题

/*1003.Emergency*/
#include<iostream>
#include<climits>
using namespace std;


int n;//几个城市
int *num;//一个城市有几个救援队
int **G;//城市图
//int *d,*visit,*pre;//单源最短路径,是否访问过,前驱顶点,这里和Dijkstra是一样的
//int *road, *maxTeam;//road[i]代表起点到i有几条最短路径,maxTeam[i]为s->i最大救援队数量
//initiate: road[s]=1;road[i]=0;maxTeam[s]=num[s].maxTeam[i]=0;

void Dijkstra(int start, int end) {
    //给d,visit,pre,road,maxTeam开辟空间并且初始化
    int *d = new int[n];
    int *visit = new int[n];
    //int *pre = new int[n];
    int *road = new int[n];
    int *maxTeam = new int[n];
    for (int i = 0; i < n; i++) {
        d[i] = INT_MAX;
        visit[i] = 0;
        //pre[i] = -1;
        road[i] = 0;
        maxTeam[i] = 0;
    }
    d[start] = 0;
    //pre[start] = start;
    road[start] = 1;
    maxTeam[start] = num[start];
    if (start == end) {
        cout << road[end] << " " << maxTeam[end] << endl;
        return;
    }
    int control = 0;
    bool flag = true;
    while (control < n) {
        int u = 0;
        int min = INT_MAX;
        //找到未访问过且d[u]最小的顶点,第一次从s开始,即u=s
        for (int i = 0; i < n; i++) {
            if (!visit[i] && d[i] < min) {
                min = d[i];
                u = i;
            }
        }
        visit[u] = 1;//标记访问过
        for (int j = 0; j < n; j++) {//从u出发
            if (G[u][j] != 0 && !visit[j]) {//有路
                if ( d[j] > d[u] + G[u][j]) {
                    d[j] = d[u] + G[u][j];
                    //pre[j] = u;
                    maxTeam[j] = maxTeam[u] + num[j];
                    road[j] = road[u];
                }
                else if (d[j] == d[u] + G[u][j]) {
                    road[j] = road[j] + road[u];
                    if (maxTeam[j] < maxTeam[u] + num[j]) {
                        maxTeam[j] = maxTeam[u] + num[j];
                    }
                }
            }
        }
        control++;
    }
    cout << road[end] << " " << maxTeam[end] << endl;
}

int main()
{
    int m,s, e;
    cin >> n >> m >> s >> e;
    num = new int[n];
    G = (int **)new int*[n];
    for (int i = 0; i < n; i++) {//初始化图G,有n个城市(顶点)
        G[i] = new int[n];
        for (int j = 0; j < n; j++) {
            G[i][j] = 0;//表示没路
        }
    }
    for (int i = 0; i < n; i++) {
        cin >> num[i];//一个城市有几个救援队
    }
    int x, y, w;
    for (int i = 0; i < m; i++) {//初始化路径
        cin >> x >> y >> w;
        G[x][y] = w;
    }
    Dijkstra(s,e);
    return 0;
}

1030. Travel Plan (30)

/*1030 Travel Plan*/
#include<iostream>
#include<climits>
#include<vector>
#include<iterator>
using namespace std;

const int N = 510;
int n,m, s, e;;//几个城市
int **G;//城市图
int Cost[N][N] = { 0 };
vector<int> pre[N];//节点i的前驱节点数组
vector<int> Path, tmpPath;
int *d = new int[N];
int Mincost = INT_MAX;


void Dijkstra() {
    int *visit = new int[n];
    for (int i = 0; i < n; i++) {
        d[i] = INT_MAX;
        visit[i] = 0;
    }
    d[s] = 0;
    int control = 0;
    while (control < n) {
        int u = -1;
        int min = INT_MAX;
        //找到未访问过且d[u]最小的顶点
        for (int i = 0; i < n; i++) {
            if (!visit[i] && d[i] < min) {
                min = d[i];
                u = i;
            }
        }
        if(u == -1)return;
        visit[u] = 1;//标记访问过
        for (int j = 0; j < n; j++) {//从u出发
            if (G[u][j] != 0 && !visit[j]) {//有路
                if ( d[j] > d[u] + G[u][j]) {
                    d[j] = d[u] + G[u][j];
                    pre[j].clear();
                    pre[j].push_back(u);
                }
                else if (d[j] == d[u] + G[u][j]) {
                    pre[j].push_back(u);
                }
            }
        }
        control++;
    }
    
}

void DFS(int v) {//DFS输出Cost最小的路线
    if (v == s) {//到达边界
        tmpPath.push_back(v);
        //计算cost
        int cost = 0;
        for (int i = 0; i < tmpPath.size()-1; i++) {
            int u = tmpPath[i];
            int v = tmpPath[i + 1];
            cost += Cost[u][v];
        }
        if (cost < Mincost) {
            Mincost = cost;
            Path.clear();
            vector<int>::iterator it;
            for (it = tmpPath.begin(); it != tmpPath.end(); it++)
                Path.push_back(*it);
        }
        tmpPath.pop_back();
        return;
    }
    //递归
    tmpPath.push_back(v);
    for (int j = 0; j < pre[v].size(); j++) {
        DFS(pre[v][j]);
    }
    tmpPath.pop_back();
}
int main()
{
    
    cin >> n >> m >> s >> e;
    G = (int **)new int*[n];
    for (int i = 0; i < n; i++) {//初始化图G,有n个城市(顶点)
        G[i] = new int[n];
        for (int j = 0; j < n; j++) {
            G[i][j] = 0;//表示没路
        }
    }
    int x, y, w,c;
    for (int i = 0; i < m; i++) {//初始化路径和消耗权重
        cin >> x >> y >> w>>c;
        G[x][y] = w;
        G[y][x] = w;
        Cost[x][y] = c;
        Cost[y][x] = c;

    }
    Dijkstra();
    DFS(e);
    for (int i = Path.size() - 1; i >= 0; i--) {
        cout << Path[i] << " ";
    }
    cout << d[e] << " " << Mincost;
    return 0;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,001评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,210评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,874评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,001评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,022评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,005评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,929评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,742评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,193评论 1 309
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,427评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,583评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,305评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,911评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,564评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,731评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,581评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,478评论 2 352

推荐阅读更多精彩内容

  • "use strict";function _classCallCheck(e,t){if(!(e instanc...
    久些阅读 2,029评论 0 2
  • 参见贪心算法——最短路径Dijkstra算法参见动态规划 目录 0.最短路径问题0.1 最短路径问题描述 0.1....
    王侦阅读 4,808评论 1 9
  • 读书会《非暴力沟通》 2018年2月25日 今天共读:第二章,第三章 第二章:是什么蒙蔽了爱 “异化的沟通方式” ...
    毛毛教练阅读 281评论 0 0
  • 然而,对于真理甚至是真理剩余部分的问题,在我们思考和行动中对我们有重要的影响,不幸的是,对这个问题我们有大量不明确...
    Fx_阅读 164评论 0 1
  • 《奇葩说》有一期的辩题是:“很喜欢的一个人,通过时光机我发现十年后跟我在一起的并不是TA,到底是去追还是放弃?” ...
    A魔女之森阅读 525评论 1 2