最短路问题学习笔记

注意时间复杂度

目录

dijkstra算法(单源最短路,无负权边,有两种版本)

Bellman_Ford算法(单源最短路,有负权边,可以处理不超过k条边问题)

spfa算法(单源最短路,有负权变,比Bellman好)

Floyd算法(多源最短路问题)


dijkstra算法

dijkstra算法分两种,一种是朴素版,另一种是堆优化版。朴素版适用于稠密图,时间复杂度与边数无关,堆优化版适用于稀疏图。

朴素版dijkstra算法思路:

朴素版的图用领接矩阵储存

  1. 初始化:将dist数组所有元素置为+无穷,起点置成0.
  2. 进行n-1次循环(起点已经确定最短路了,所以是n-1次),每次循环找到dist最小的且未标记的点,然后标记这个点
  3. 更新所有点的dist  方法:dist[i]=min(dist[i],dist[tar]+w[tar][i]).
    int dijkstra(int start,int end){
        memset(dist,inf,sizeof dist);
        dist[start]=0;
        for(int i=0;i<n-1;i++){
            int t=-1;
            for(int j=1;j<=n;j++)
                if(!vis[j]&&(t==-1||dist[t]>dist[j])t=j;
    
            for(int j=1;j<=n;j++)
                   dist[j]=min(dist[j],dist[t]+w[t][j]));
            vis[t]=1;
        }
        if(dist[end]==inf)return -1;
        return dist[end];
    
    }

堆优化版dikstar算法

利用小根堆来寻找dist最小的点,能在o(1)的时间复杂度得到dist最小的点

堆优化版的图用邻接表储存

思路:

  1. 初始化:所有dist置成inf,起始点dist为0.
  2. 将起始点入堆
  3. 做循环,直到堆为空,每次循环取出最小dist点,并标记这个点(如果取出的点被标记,则直接continue),将与这个点联通的点的dist更新方法:dist[i]=min(dist[i],dist[tat]+w[tar][i]),将这个点放入堆中。
typedef pair<int,int> PII;
int dijkstra(int start ,int end)
{
    memset(dist, 0x3f, sizeof dist);
    dist[start] = 0;
    priority_queue<PII, vector<PII>, greater<PII>> heap;
    heap.push({0, start});      // first存储距离,second存储节点编号

    while (heap.size())
    {
        auto t = heap.top();
        heap.pop();

        int ver = t.second, distance = t.first;

        if (st[ver]) continue;
        st[ver] = true;

        for (int i = h[ver]; i != -1; i = ne[i])
        {
            int j = e[i];
            if (dist[j] > distance + w[i])
            {
                dist[j] = distance + w[i];
                heap.push({dist[j], j});
            }
        }
    }

    if (dist[end] == 0x3f3f3f3f) return -1;
    return dist[end];
}

Bellman_Ford算法

用于处理含负权边的单源最短路问题,但不如spfa算法,可以处理有边数限制的最短路问题

可以以任何方式储存图,但要能做到可以遍历到所有的点和边,建议用结构

注意:因为数值更新时可能发生串联现象,所以更新时要用未更新前的数据进行更新,要用另一个数组储存未更新前的值

思路:

  1. 初始化:所有点的dist置未inf,起点dist为0
  2. 进行二重循环,外层n(点的数目)次,内层m(边的数目)次,每次更新dist值。方法:dist[b]=min(dist[b],dist[a]+w)


struct Edge    //边的结构
{
    int a, b, w;
}

int bellman_ford()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;

    for (int i = 0; i < n; i ++ )//如果不超过k条边,这里设置成k
    {
        memcpy(backup,dist,sizeof dist);
            for (int j = 0; j < m; j ++ )
        {
            int a = edges[j].a, b = edges[j].b, w = edges[j].w;
            if (dist[b] > bakcup[a] + w)//bakcup是未更新前的数据
                dist[b] = backup[a] + w;
        }
    }

    if (dist[n] > 0x3f3f3f3f / 2) return -1;
    return dist[n];
}

特别注意:如果要求不超过k条边,那么外层循环置成k次。

spfa算法

spfa实质是用优先队列优化的Bellman_Ford算法

用邻接表储存图,优化思路就是只有发生变化的值才会影响它后面的值,所有只需要对发生变化的值进行更新操作就可以优化算法

思路:

  1. 初始化:将所有点的dist置成inf,出发点为0
  2. 将初始点加入优先队列,并标记这个点
  3. 当优先队列不空时,取出队头元素,取消这个点的标记,然后将与这个点相通的点的dist更新。方法:dist[i]=min(dist[i],dist[t]+w[t][i]).
  4. 被更新的点未被标记时,将这个点加入优先队列中,并标记这个点
int spfa()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;

    queue<int> q;
    q.push(1);
    st[1] = true;

    while (q.size())
    {
        auto t = q.front();
        q.pop();

        st[t] = false;

        for (int i = h[t]; i != -1; i = ne[i])
        {
            int j = e[i];
            if (dist[j] > dist[t] + w[i])
            {
                dist[j] = dist[t] + w[i];
                if (!st[j])     // 如果队列中已存在j,则不需要将j重复插入
                {
                    q.push(j);
                    st[j] = true;
                }
            }
        }
    }

    if (dist[n] == 0x3f3f3f3f) return -1;
    return dist[n];
}

spfa算法还可以判断负环,只要取消初始化,加上一个cnt数组,当某一个点的路径上通过的边数大于等于n时,就说明有负环

bool spfa()
{
    // 不需要初始化dist数组
    // 原理:如果某条最短路径上有n个点(除了自己),那么加上自己之后一共有n+1个点,由抽屉原理一定有两个点相同,所以存在环。

    queue<int> q;
    for (int i = 1; i <= n; i ++ )
    {
        q.push(i);
        st[i] = true;
    }

    while (q.size())
    {
        auto t = q.front();
        q.pop();

        st[t] = false;

        for (int i = h[t]; i != -1; i = ne[i])
        {
            int j = e[i];
            if (dist[j] > dist[t] + w[i])
            {
                dist[j] = dist[t] + w[i];
                cnt[j] = cnt[t] + 1;
                if (cnt[j] >= n) return true;       // 如果从1号点到x的最短路中包含至少n个点(不包括自己),则说明存在环
                if (!st[j])
                {
                    q.push(j);
                    st[j] = true;
                }
            }
        }
    }

    return false;
}

Floyd算法

Floyd算法用于多源最短路问题,三重循环就可以了,基于动态规划

用领接矩阵储存图

思路:

  1. 将领接矩阵初始化(对于自环设置成0,其他的为正无穷)
  2. 读入边的情况
  3. 对领接矩阵进行操作。
int d[N][N];
void Floyd(){
    for(int k=1;i<=n;k++)
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++){
                d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
    }
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值