【最短路算法】

在这里插入图片描述

朴素版Dijkstra算法

朴素版Dijkstra算法用于解决稠密无负权边图的最短路问题。

int n, m; //n(点数) m(变数)
int dis[N], g[N][N]; //dis[x](起点1到x的距离) g[a][b](a, b点间的距离)
bool v[N]; //标记数组

int dijkstra()
{
    memset(dis, 0x3f, sizeof dis); //初始化所有点到起点1的距离为正无穷
    dis[1] = 0; //把1的距离赋为0;
    
    for(int i = 0; i < n - 1; i ++ ) //循环( n - 1 )次
    {
        int t = -1;
        for(int j = 1; j <= n; j ++ )
            if(!v[j] && (t == -1 || dis[j] < dis[t])) //找出未被标记的点中距离起点最小的点
                t = j;
                
        v[t] = true; //标记点t
        
        for(int j = 1; j <= n; j ++ ) //用起点到t点的最短距离更新其他点
            dis[j] = min(dis[j], dis[t] + g[t][j]);
    }
    
    if(dis[n] == 0x3f3f3f3f) return -1; //如果图不联通就返回-1
    return dis[n];
}

堆优化Dijkstra算法

堆优化Dijkstra算法用于解决稀疏无负权边图的最短路问题。

int n, m, dis[N]; //n(点数) m(边数) dis[x](起点到x的距离) 
bool v[N]; // v[N](标记数组)
vector<PII> g[N]; //邻接表存图

int dijkstra()
{
    priority_queue<PII, vector<PII>, greater<PII>> q; //优先队列实现小根堆
    q.push({0, 1}); //把起点存入堆
    //(需要找出的是距离起点最小的点,应该把距离放在第一位(先按第一位排序))
    
    memset(dis, 0x3f, sizeof dis); //初始化每个点到起点的距离
    dis[1] = 0; //起点距离初始化为0
    
    while(q.size())
    {
        int t = q.top().second; //当前遍历到点t
        q.pop(); //把点从堆里弹出
        
        if(v[t]) continue; //如果点t点被遍历到了就跳过该点
        
        v[t] = true; //标记点t
        
        for(int i = 0; i < g[t].size(); i ++ )//遍历所有与点t有边相连的点
        {
            int j = g[t][i].first, w = g[t][i].second; 
            //j(与点t有边相连的点) w(边a-b的权值)
            
            if(dis[j] > dis[t] + w) //点j需要更新
            {
                dis[j] = dis[t] + w;
                q.push({dis[j], j});
                //点j更新后会影响所有与j有边相连接的点,需要把点j存入堆
            }
        }
    }
    
    if(dis[n] == 0x3f3f3f3f) return -1; //图不联通返回-1
    return dis[n];
    
}

Bellman_ford

bellman_ford算法用于解决有边数限制的带负权边图的最小路问题

struct node
{
    int l, r, w; //l(边的左顶点) r(边的右顶点) w(边权值)
}e[M];

int n, m, k; // n(点数) m(边数) k(限制边数)
int dis[N], last[N]; //dis[x](起点到x的距离) last[x](上次循环的出的起点到x的距离)

void bellman_ford()
{
    memset(dis, 0x3f, sizeof dis); //初始化起点到每个点的距离
    dis[1] = 0;
    
    for(int i = 0; i < k; i ++ )//限制变数k, 遍历k次
    {
        memcpy(last, dis, sizeof dis); //把上次循环得出的dis赋给last
        
        for(int j = 0; j < m; j ++ ) //循环每条边(a-b)
        {
            int a = e[j].l, b = e[j].r, w = e[j].w;
            dis[b] = min(dis[b], last[a] + w); 
            //dis[b]用上次循环得出的起点到a点的距离加上边a-b的权值
        }
    }
}
SPFA算法

spfa算法用来求无边数限制带负权边图的最短路,还可以用来判断一个图是否存在负环

spfa算法求最短路

int spfa()
{
    queue<int> q; //创建一个队列q来存储更新距离了的点
    q.push(1);
    
    memset(dis, 0x3f, sizeof dis); //初始化起点到每个点的距离
    dis[1] = 0;
    
    while(q.size())
    {
        int t = q.front();
        
        q.pop(); v[t] = false; //将点t弹出队列并且取消点t的标记
        
        for(int i = 0; i < g[t].size(); i ++ ) //遍历与点t有相连接边的所有点
        {
            int j = g[t][i].first, w = g[t][i].second;
            //j(与t有连接边的点) w(边的权值)
            if(dis[j] > dis[t] + w) //如果dis[j]需要更新
            {
                dis[j] = dis[t] + w;
                if(!v[j]) //点j不在队列中时把j点存入队列
                {
                    v[j] = true;
                    q.push(j);
                }
            }
        }
    }
    
    return dis[n];
}

spafa判断负环

int n, m; //n(点数) m(边数)
int f[N], dis[N], cnt[N]; //f[](标记数组) cnt[x](起点到点x的最短路径中的边数)
vector<PII> g[N];

int spfa()
{
   queue<int> q;
   
   for(int i = 1; i <= n; i ++ ) //判断负环而不是判断从一开始的负环所以负环可能从一到不了
   {
       q.push(i); f[i] = true; //把所有点放入初始点集 
   }
   
   while(q.size())
   {
       int t = q.front();
       
       q.pop(); f[t] = false;
       
       for(int i = 0; i < g[t].size(); i ++ )
       {
           int j = g[t][i].first, w = g[t][i].second;
           
           if(dis[j] > dis[t] + w)
           {
               dis[j] = dis[t] + w;
               cnt[j] = cnt[t] + 1;
               if(cnt[j] >= n) return true; //判断得出图里含负环
               //由抽屉原理(n条边最少经过了n+1个点,图里只有n个点所以图中一定存在负环)
               
               if(!f[j])
               {
                   q.push(j);
                   f[j] = true;
               }
           }
       }
   }
   
   return false;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值