最短路算法

解决最短路的问题主要有以下算法:Dijkstra算法,Bellman-Ford算法,Floyd算法和SPFA算法。

1、Dijkstra算法

参考链接:https://blog.csdn.net/kprogram/article/details/81225176
(此算法和普利姆算法求最小生成树类似)
:Dijkstra算法适用于边权为正的无向和有向图,不适用于有负边权的图

用途:

用于求图中指定两点之间的最短路径,或者是指定一点到其它所有点之间的最短路径。实质上是贪心算法

基本思想:

1.将图上的初始点看作一个集合S,其它点看作另一个集合

2.根据初始点,求出其它点到初始点的距离d[i] (若相邻,则d[i]为边权值;若不相邻,则d[i]为无限大)

3.选取最小的d[i](记为d[x]),并将此d[i]边对应的点(记为x)加入集合S

(实际上,加入集合的这个点的d[x]值就是它到初始点的最短距离)

4.再根据x,更新跟 x 相邻点 y 的d[y]值:d[y] = min{ d[y], d[x] + 边权值w[x][y] },因为可能把距离调小,所以这个更新操作叫做松弛操作。

5.重复3,4两步,直到目标点也加入了集合,此时目标点所对应的d[i]即为最短路径长度。

void Dijkstra(int v)
{
    int i, j, k;
    for(i = 1; i <= n; i++)//dist数组的初始化
    {
        dist[i] = map[v][i];
        vis[i] = 0;
    }
    dist[v] = 0;
    vis[v] = 1;

    for(i = 0; i < n-1; i++)
    {
        int min = INF, u = v;
        for(j = 1; j <= n; j++)//寻找未标记结点的最小值
        {
            if(vis[j] == 0 && dist[j] < min)
            {
                u = j;
                min = dist[j];
            }
        }
        vis[u] = 1;
        for(k = 1; k <= n; k++)//更新最短路
        {
            if(vis[k] == 0 && map[u][k] < INF && dist[k] > dist[u] + map[u][k])
            {
                dist[k] = dist[u] + map[u][k];
            }
        }
    }
}

2、Bellman-Ford算法

转发链接:https://blog.csdn.net/qq_40984919/article/details/80489441

适用条件&范围:

单源最短路径(从源点s到其它所有顶点v);

有向图&无向图(无向图可以看作(u,v),(v,u)同属于边集E的有向图);

边权可正可负(如有负权回路输出错误提示);

Bellman-Ford算法的流程如下:

给定图G(V, E)(其中V、E分别为图G的顶点集与边集),源点s,数组Distant[i]记录从源点s到顶点i的路径长度,初始化数组Distant[n]为, Distant[s]为0;

以下操作循环执行至多n-1次,n为顶点数:

对于每一条边e(u, v),如果Distant[u] + w(u, v) < Distant[v],则另Distant[v] = Distant[u]+w(u, v)。w(u, v)为边e(u,v)的权值;

若上述操作没有对Distant进行更新,说明最短路径已经查找完毕,或者部分点不可达,跳出循环。否则执行下次循环;

为了检测图中是否存在负环路,即权值之和小于0的环路。对于每一条边e(u, v),如果存在Distant[u] + w(u, v) < Distant[v]的边,则图中存在负环路,即是说改图无法求出单源最短路径。否则数组Distant[n]中记录的就是源点s到各顶点的最短路径长度。

可知,Bellman-Ford算法寻找单源最短路径的时间复杂度为O(V*E).

Bellman-Ford算法可以大致分为三个部分

第一,初始化所有点。每一个点保存一个值,表示从原点到达这个点的距离,将原点的值设为0,其它的点的值设为无穷大(表示不可达)。

第二,进行循环,循环下标为从1到n-1(n等于图中点的个数)。在循环内部,遍历所有的边,进行松弛计算。

第三,遍历途中所有的边(edge(u,v)),判断是否存在这样情况:
d(v) > d (u) + w(u,v);则返回false,表示途中存在从源点可达的权为负的回路。

之所以需要第三部分的原因,是因为,如果存在从源点可达的权为负的回路。则 应为无法收敛而导致不能求出最短路径。

只求最短路的代码:

#include<bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f

int n,m,i,j;
int check;

struct node
{
    int u,v,w;
} a[5000010];

int dis[3000000],top;

void add(int u,int v,int w)
{
    a[top].u=u;
    a[top].v=v;
    a[top].w=w;
    top++;
}

void Bellman(int s,int e)
{
    memset(dis,inf,sizeof(dis));
    dis[s]=0;
    for(i=1; i<n; i++)
    {
        check=0;
        for(j=1; j<top; j++)
        {
            if(dis[a[j].v] > dis[a[j].u]+a[j].w)
            {
                dis[a[j].v] = dis[a[j].u]+a[j].w;
                check=1;
            }
        }
        if(check==0)   break;
    }
    printf("%d\n",dis[e]);
}

求最短路及判断是否有负权回路

#include<iostream>  
#include<cstdio>  
using namespace std;  
  
#define MAX 0x3f3f3f3f  
#define N 1010  
int nodenum, edgenum, original; //点,边,起点  
  
typedef struct Edge //边  
{  
    int u, v;  
    int cost;  
}Edge;  
  
Edge edge[N];  
int dis[N], pre[N];  
  
bool Bellman_Ford()  
{  
    for(int i = 1; i <= nodenum; ++i) //初始化  
        dis[i] = (i == original ? 0 : MAX);  
    for(int i = 1; i <= nodenum - 1; ++i)  
        for(int j = 1; j <= edgenum; ++j)  
            if(dis[edge[j].v] > dis[edge[j].u] + edge[j].cost) //松弛(顺序一定不能反~)  
            {  
                dis[edge[j].v] = dis[edge[j].u] + edge[j].cost;  
                pre[edge[j].v] = edge[j].u;  
            }  
            bool flag = 1; //判断是否含有负权回路  
            for(int i = 1; i <= edgenum; ++i)  
                if(dis[edge[i].v] > dis[edge[i].u] + edge[i].cost)  
                {  
                    flag = 0;  
                    break;  
                }  
                return flag;  
}  

3、Floyd算法

动态规划思想

mp[ ][ ]数组的初始化

for(i=1; i<=n; i++)
{
    for(j=1; j<=n; j++)
    {
        if(i==j)
            mp[i][j]=0;
        else
            mp[i][j]=INF;
    }
}
for(i=0; i<m; i++)
{
    scanf("%d %d %d",&a,&b,&c);
    if(c<mp[a][b])
    {
        mp[a][b]=c;
        mp[b][a]=c;
    }
}

void Flord()
{
    int i,j,k;
    for(k=1;k<=N;k++)
    {
        for(i=1;i<=N;i++)
        {
            for(j=1;j<=N;j++)
            {
                mp[i][j]=min(mp[i][j],mp[i][k]+mp[k][j]);
            }
        }
    }
}

4、SPFA算法

参考链接: https://www.cnblogs.com/xzxl/p/7246918.html

SPFA算法与Bellman-Ford算法相似,但是SPFA比Bellman-Ford算法多了一个队列优化;
算法描述

算法特点:在 Bellman-ford 算法的基础上加上一个队列优化,减少了冗余的松弛操作,是一种高效的最短路算法。

时间复杂度:O(mn)

关键词:初始化 松弛操作 队列

主要变量如下:

int n    表示有n个点,从1~n标号

int s,t     s为源点,t为终点

int dis[N]  dis[i]表示源点s到点i的最短路径

int pre[N]  记录路径,pre[i]表示i的前驱结点

bool vis[N] vis[i]=true表示点i在队列中

queue q 队列,在整个算法中有顶点入队了要记得标记vis数组,有顶点出队了记得消除那个标记

【初始化】

dis数组全部赋值为INF,pre数组全部赋值为-1(表示还不知道前驱),

dis[s] = 0 表示源点不要求最短路径(或者最短路径就是0)。

【队列+松弛操作】

读取队头顶点u,并将队头顶点u出队(记得消除标记);将与点u相连的所有点v进行松弛操作,如果能更新估计值(即令d[v]变小),那么就更新,另外,如果点v没有在队列中,那么要将点v入队(记得标记),如果已经在队列中了,那么就不用入队,这样不断从队列中取出顶点来进行松弛操作。

以此循环,直到队空为止就完成了单源最短路的求解。

【算法过程】

设立一个队列用来保存待优化的顶点,优化时每次取出队首顶点 u,并且用 u 点当前的最短路径估计值dis[u]对与 u 点邻接的顶点 v 进行松弛操作,如果 v 点的最短路径估计值dis[v]可以更小,且 v 点不在当前的队列中,就将 v 点放入队尾。这样不断从队列中取出顶点来进行松弛操作,直至队列空为止。

【检测负权回路】

方法:如果某个点进入队列的次数大于等于 n,则存在负权回路,其中 n 为图的顶点数。

说明:SPFA无法处理带负环的图。

void SPFA(int s,int e)
{
    int i;
    queue<int>q;
    memset(vis,0,sizeof(vis));
    for(i=1;i<=n;i++)
    {
        dis[i] = inf;
    }
    dis[s] = 0;//表示到点到某点的最短路径数组
    vis[s] = 1;//表示该点是否已经在队列中
    q.push(s);
    while(!q.empty())
    {
        int u = q.front();
        q.pop();
        vis[u] = 0;
        for(i=head[u];i!=-1;i = edge[i].next)
        {
            int v = edge[i].v;
            if(dis[v]>dis[u]+edge[i].w)
            {
                dis[v] = dis[u]+edge[i].w;
                if(vis[v]==0)
                {
                    vis[v] = 1;
                    q.push(v);
                }
            }
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值