欢迎光临
我们一直在努力

算法讲解22:最短路径算法的优化

1. Floyd算法:O(|V|³),多源最短路,适用于出现负边权的情况,但无法处理存在负权回路的情况。
​
2. Dijkstra算法:O(|V|²),单源最短路,不能处理存在负边权的情况。(边比较多时用)
​
3. Bellman-Ford算法:O(|V||E|),单源最短路,适用于出现负边权的情况,但无法处理存在负权回路的情况。(边比较少时用)

v是点数,E是边数

多源最短路:计算每一对顶点的最短路径

单源最短路:计算从1个起点到途中所有其他顶点的最短路径

本期来优化后面两个算法

Dijkstra算法:

未优化:

#include <iostream>
#include <vector>
#include <utility>
#define inf 1001
using namespace std;

int n, m, cnt; // n<100  0<权值<1000
int h[105];
struct Edge
{
    int to, w, next;
} e[10005];
int dis[105], v[105];

void add(int u, int v, int w)
{
    e[cnt].to = v;
    e[cnt].w = w;
    e[cnt].next = h[u];//下一条边 
    h[u] = cnt;
    cnt++;
}

void dij(int x)
{
    for (int j = 1; j <= n; j++)
        dis[j] = inf;//dis数组实时记录并更新从起点 x 到图中每一个节点的当前最短距离估计值。
    dis[x] = 0;
    for (int i = 1; i <= n; i++)
        v[i] = 0; 
    for (int i = 1; i <= n; i++)//遍历所有点 
    {
        int minn = inf, k = -1;
        for (int j = 1; j <= n; j++)//从1-n中找到x, minn = 0,并把 k 设为起点 x 的编号,这是第一次,后面 就是找出来一个最短路径的节点  
        {
            if (v[j] == 0 && dis[j] < minn)
            {
                minn = dis[j];
                k = j;
            }
        }
        if (k == -1) break;//如果找不到任何一个处理,后面也不用看了 
        v[k] = 1;
        for (int i = h[k]; i != -1; i = e[i].next)//不断遍历下一条边 
        {
            int u = e[i].to;//下一条边终点的值 
            if (v[u] == 0 && dis[u] > dis[k] + e[i].w)
                dis[u] = dis[k] + e[i].w;
        }
    }
}

int main()
{
    memset(h, -1, sizeof(h));
    cin >> n >> m;
    int x, y, w;
    for (int i = 1; i <= m; i++)
    {
        cin >> x >> y >> w; // x—->y
        add(x, y, w);
    }
    cin >> x;
    dij(x);
    for (int i = 1; i <= n; i++)
    {
        cout << dis[i] << " ";
    }
    cout << endl;
    return 0;
}
/*
输出样例
5 5
2 3 2
1 2 3
*/

优化:

使用小根堆,也就是优先队列,遵循(谁的优先级最高,谁就先出去)

#include <iostream>
#include <vector>
#include <utility>
#include <queue> // 引入优先队列头文件
#define inf 1001
using namespace std;

int n, m, cnt;
int h[105];
struct Edge {
    int to, w, next;
} e[10005];
int dis[105], v[105]; // v数组依然需要,用于标记节点是否已确定

// <1> 定义优先队列的结构
// pair的第一个元素是距离,第二个是节点编号
// greater<> 表示距离小的优先级高(小根堆)
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;

void add(int u, int v, int w) {
    e[cnt].to = v;
    e[cnt].w = w;
    e[cnt].next = h[u];
    h[u] = cnt;
    cnt++;
}

void dij(int x) {
    // <2> 初始化
    for (int j = 1; j <= n; j++)
        dis[j] = inf;
    dis[x] = 0;
    
    // <3> 将起点{距离, 编号}放入队列
    pq.push({0, x});

    while (!pq.empty()) { // <4> 队列不为空就继续
        // <5> 取出队首(当前距离最小的)
        int d = pq.top().first;  // 当前取出的距离
        int k = pq.top().second; // 当前取出的节点
        pq.pop();

        // <6> 惰性删除:如果这个节点已经被处理过,或者距离变大了,就跳过
        if (v[k] == 1 || d > dis[k]) 
            continue;

        // <7> 标记节点k已确定,进行松弛操作
        v[k] = 1;
        for (int i = h[k]; i != -1; i = e[i].next) {
            int u = e[i].to;
            // <8> 如果通过k到u更近,就更新
            if (dis[u] > dis[k] + e[i].w) {
                dis[u] = dis[k] + e[i].w;
                // <9> 把更新后的新距离和节点放入队列
                pq.push({dis[u], u});
            }
        }
    }
}

int main() {
    memset(h, -1, sizeof(h));
    cin >> n >> m;
    int x, y, w;
    for (int i = 1; i <= m; i++) {
        cin >> x >> y >> w;
        add(x, y, w);
    }
    cin >> x;
    dij(x);
    for (int i = 1; i <= n; i++) {
        cout << dis[i] << " ";
    }
    cout << endl;
    return 0;
}

优化后时间复杂度O((|V|+|E|)*log|V|)

Bellman-Ford算法:

补充:第i轮一定有机会被更新的点是上一轮被更新的点的邻接点

优化:

可以发现,每一轮遍历dis值减小的点,是上一轮dis值减小的点的邻接点。
所以每一轮没必要遍历所有的边了,只遍历上一轮dis值减小的点的出边即可,即只找上一轮dis值减小的点的邻接点。则,采用队列进行优化,将dis值减小的点入队,每次只找队列中点的邻接点进行更新。
——-SPFA算法

使用该优化后就可以判断带环负权图

一个点最多入队不到n次,因为入队的都是指向的那个点,最多有n-2个点(还有个起点不算)指向它

这里的队列就用普通的就行,不用优先队列  

示例:

bool spfa(int x)
{
    for(int i=1; i<=n; i++)
        dis[i]=inf;
    queue<int> q;
    int u, k;
    dis[x]=0;
    v[x]=1;
    q.push(x);
    while(!q.empty())
    {
        u=q.front();
        q.pop();
        v[u]=0; // u不在队列中了
        for(int i=h[u]; i!=-1; i=e[i].next)
        {
            k=e[i].to;
            if(dis[k]>dis[u]+e[i].w)
            {
                dis[k]=dis[u]+e[i].w; // k点被更新
                cur[k]=cur[u]+1; // 记录起点到k的边数
                if(cur[k]>=n-1) return 0;
                if(v[k]==0)
                {
                    q.push(k);
                    v[k]=1;
                }
            }
        }
    }
}
 

赞(0)
未经允许不得转载:171主机测评 » 算法讲解22:最短路径算法的优化
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址