图 单源最短路径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;
}
    原文作者:烤肉拌饭多加饭
    原文地址: https://www.jianshu.com/p/6335c0f897a0
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞