最短路问题

最短路问题

  • 单源最短路:一个点到其他点的做短路

  • 所有边权都是正数
    1.朴素Dijstra算法(稠密图) O(n*2)
    2.堆优化Dijstra算法(稀疏图)O(mlogn)

  • 存在负权边
    1.Bellman-fort算法(求有边数限制的单源最短路)O(mn)
    2.SPFA 一般O(m),最坏O(mn)

  • 多源最短路:任意两点间的最短路
    Floyd算法 O(n*3)

朴素Dijstra算法

  • 算法思想:从起点到终点的最短路的值。贪心的在已更新的结点集当中寻找距离起点最近的结点,并更新周围点,使得到最近的点的返回值。
  • 朴素Dijstr算法解决稠密图,用邻接矩阵来存储边权值
 g[a][b]=min(g[a][b],c)//a,b,为点,c为边

算法思路:

  1. (定义一个dist数组,表示点的权值)。初始化dist[i],且第一个点的值为0,dist【1】=0;
	memset(dist,0x3f,sizeof dist);
    dist[1]=0;
  1. (迭代n次)
  • 设st为当前已确定最短距离的点,遍历所有点,找到t不在st中的距离最近的点
	for(int j=1;j<=n;j++){
            if(!st[j]&&(t==-1||dist[t]>dist[j]))//!st[j]表示当前这个点还没确定最短路
            t=j;}
  • 将t存入st中
st[t]=true;
  • 用t更新其他点的距离,遍历所有点
	for(int j=1;j<=n;j++)
    dist[j]=min(dist[j],dist[t]+g[t][j]);

核心代码:

int dijstra()
{
    memset(dist,0x3f,sizeof dist);
    dist[1]=0;
    for(int i=0;i<n;i++)
    {
        int t=-1;
        for(int j=1;j<=n;j++){
            if(!st[j]&&(t==-1||dist[t]>dist[j]))
            t=j;}
            
        st[t]=true;
        for(int j=1;j<=n;j++)
        dist[j]=min(dist[j],dist[t]+g[t][j]);
    }
    if(dist[n]==0x3f3f3f3f) return -1;
    return dist[n];
}

堆优化版Dijstra算法

  • 算法思想
    和朴素版Dijstra算法相似,也是求起点到其他点的距离最短路。只是使用优先队列来做,和BFS做法相似。
    这里要使用邻接表来存储边的权值。
void add(int a,int b,int c)//加边操作
{
    e[idx]=b;
    w[idx]=c;
    ne[idx]=h[a];
    h[a]=idx++;
}

算法思路:

  1. (定义一个dist数组,表示点的权值)。初始化dist[i],且第一个点的值为0,dist【1】=0;
	memset(dist,0x3f,sizeof dist);//初始化dist(距离),dist[1]=0
    dist[1]=0;
  1. 建立小根堆,将1号点插入堆中
	priority_queue<PII,vector<PII>,greater<PII>>heap;//建立小根堆
    heap.push({0,1});//将1号点插入堆
  1. 设st为当前已确定最短距离的点,遍历所有点,找到t不在s中的距离最近的点,循环堆是否为空。
  • 取栈顶(当前最近点),并删除
	auto t=heap.top();//取栈顶
    heap.pop();//删栈顶
  • 取出标号(second)和距离(first),如果该点被访问,则跳过。
	int ver=t.second,d=t.first;//取出标号(second)和距离(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]>d+w[i])
            {
                dist[j]=d+w[i];
                heap.push({dist[j],j});
            }
        }

核心代码:

int dijstra()
{
    memset(dist,0x3f,sizeof dist);//初始化dist(距离),dist[1]=0
    dist[1]=0;
    
    priority_queue<PII,vector<PII>,greater<PII>>heap;//建立小根堆
    heap.push({0,1});//将1号点插入堆
    
    while(heap.size())//判断堆是否为空
    {
        auto t=heap.top();//取栈顶
        heap.pop();//删栈顶
        
        int ver=t.second,d=t.first;//取出标号(second)和距离(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]>d+w[i])
            {
                dist[j]=d+w[i];
                heap.push({dist[j],j});
            }
        }
    }
    
    if(dist[n]==0x3f3f3f3f) return -1;
    return dist[n];
}

Bellman-ford算法(解决有边数限制的最短路)

  • 算法思想
    先将每个点到达起点的距离设为正无穷,然后直接暴力枚举所有边,如果一个点的距离可以被更新,就更新该点的距离。枚举一次至少可以得到一个点到达起点的距离,直接无脑枚举n次,就可以得到所有点到起点的最短距离。

核心代码:

int bellman_fort()
{
    memset(dist,0x3f,sizeof dist);//初始化
    dist[1]=0;
    
    for(int i=0;i<k;i++)//迭代n次
    {
        memcpy(backup,dist,sizeof dist);//备份dist,防止串连
        for(int j=0;j<m;j++)//遍历所有边
        {//松弛操作
            int a=edges[j].a,b=edges[j].b,k=edges[j].k;
            dist[b]=min(dist[b],backup[a]+k);
        }
    }
    if(dist[n]>0x3f3f3f) return -1;
    return dist[n];
}

SPFA(对Bellman-Frod做优化)

  • 算法思想:
    用宽搜(BFS)对Bellman-frod算法进行优化。在Bellman-frod算法中每次迭代的时候遍历所有边,然后来更新点之间的边,但是每次迭代每一条边不一定都会更新,也就是dist[b]>min(dist[b],backup[a]+k)这个不等式不一定都会把b的边变小。dist[b]=min(dist[b],backup[a]+k)如果每次迭代想让b此变小,得先让a变小。那么spfa就是对这步进行优化,用宽搜来优化,迭代的时候用一个队列来做 。

  • 算法思路:
    用一个队列先把起点(源点)放在里面去,只要队列不空,队列里面存的是所有变小的a(结点)。

  1. 取出队头,然后把队头删除
		int t=q.front();
        q.pop();
        
        st[t]=false;
  1. 更新t的所有初边,如果更新成功,将b加入队列
		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])//判断如果b是否已经在队列里,如果没有,再加入 
                {
                    q.push(j);
                    st[j]=true;
                }
            }
        }

核心代码:

int spfa()
{
    memset(dist, 0x3f,sizeof dist);
    dist[1]=0;
    
    queue<int>q;
    q.push(1);
    
    st[1]=true;
    while(q.size())
    {
        int 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])
                {
                    q.push(j);
                    st[j]=true;
                }
            }
        }
    }
    if(dist[n]==0x3f3f3f3f) return -1;
    return dist[n];
}

SPFA判断负环**(抽屉原理)**:
用cnt[N]来表示当前最短路边的数量。每次更新cnt[x]=cnt[t]+1
在这里插入图片描述
如果说过程当中,某一次cnt[x]>=n,也就是说从1到x至少经过了n条边,也就有n+1个点,由抽屉原理,只应该有n个点,所以可以判断有负环了。

注意:必须是每个点都进行判断,需要把每个点都放入队列中,这样的话每个点的负环都能找到,而不是以一定从1号点开始,因为有可能从1号点的最短路不经过负环。

Floyd算法

  • 算法思想:
    用邻接矩阵来存储所有的边,d[i,j]。
    Floyd算法是一个动态规划算法。
    从任意节点i到任意节点j的最短路径只有2种可能,一是直接从i到j,二就是从i经过若干个节点k到j。所以,算法假设d(i,j)为结点i到结点j的最短路径的距离,对于每一个结点k,算法判断d[i,j]>d[i,k]+d[k,j]是否成立,如果成立,证明从i到k再到j的路径比i直接到j的路径短,便设置d[i,j]=d[i,k]+d[k,j],所以,当遍历完所有节点k,d(i,j)中记录的便是i到j的最短路径的距离。

核心代码:

void floyd()
{
	for(int k=1;k<=n;i++){
		for(int i=1;i<=n;i++){
			for(int j=1;j<=n;j++){
				d[i][j]=min(d[i][j],[i][k]+d[k][j]);
			}
		}
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值