搜索与图论【复习】

 

 

负权正权都可用SPFA, 正权被卡用堆优化Dijkstra

正权用堆优化Dijkstra

  Dijkstra 堆优化Dijkstra bellman-ford   spfa  (很像BFS) ford
时间复杂度 n2 mlogn nm

一般m, 最坏nm

n3
适用情况 稠密图 稀疏图 稀疏图 稀疏图 稠密图
储存方式 数组(邻接矩阵) 链表 结构体, 随便存  链表 数组(邻接矩阵)
负权 不能解决 不能解决

边权可为负数, 无负权回路,

可判断是否有负权回路

边权可为负数, 无负权回路,

可判断是否有负权回路

可以解决
核心思想

n-1次循环, 每次查找最短边更新所有边

diat[j] = min(diat[j],dist[t]+w)

优先队列自动分出最短边,

每次拿它去更新, 更新的边入队

diat[j] = min(diat[j],dist[t]+w)

边数限定, 每次最多更新1条边

1.循环k次, 次数限定, 每次备份dist[],

防止连环更新距离, 走过的边>ki

dist[b] = min(diat[a]+w)

2.若第k次依然更新则存在负环

bellman-ford队列做法, 优化:Bellman_ford会遍历

所有的边,但只用遍历距离变小的点所连接的边即可.

1.如果距离可更新则入队,且入队成员标记,

防止队列元素重复

2.判断负环和上面很像, 下面是区别:

开始n个点都要入队, 某个点更新n次, 则有环

无限暴力
注意事项

查找最短边:

if(!st[j] && (t==-1 || dist[j] <dist[t]))
   t = j;

标记过此点就不能再入队了

1. 有边数限制

2. if(dist[n] >= 0x3f3f3f3f/2)无路

1. 判断负环开始n个点都要入队, dist[]开始不用=inf

2. 队列中标记, 不在队中不标记

没啥

 

 

 

 

1) st数组的作用:判断当前的点是否已经加入到队列当中了;已经加入队列的结点就不需要反复的把该点加入到队列中了,就算此次还是会更新到源点的距离,那只用更新一下数值而不用加入到队列当中。
即便不使用st数组最终也没有什么关系,但是使用的好处在于可以提升效率。
2) SPFA算法看上去和Dijstra算法长得有一些像但是其中的意义还是相差甚远的:

1] Dijkstra算法中的st数组保存的是当前确定了到源点距离最小的点,且一旦确定了最小那么就不可逆了(不可标记为true后改变为false);SPFA算法中的st数组仅仅只是表示的当前发生过更新的点,且spfa中的st数组可逆(可以在标记为true之后又标记为false)。顺带一提的是BFS中的st数组记录的是当前已经被遍历过的点。
2] Dijkstra算法里使用的是优先队列保存的是当前未确定最小距离的点,目的是快速的取出当前到源点距离最小的点;SPFA算法中使用的是队列(你也可以使用别的数据结构),目的只是记录一下当前发生过更新的点。

3) ⭐️Bellman_ford算法里最后return-1的判断条件写的是dist[n]>0x3f3f3f3f/2;而spfa算法写的是dist[n]==0x3f3f3f3f;其原因在于Bellman_ford算法会遍历所有的边,因此不管是不是和源点连通的边它都会得到更新;但是SPFA算法不一样,它相当于采用了BFS,因此遍历到的结点都是与源点连通的,因此如果你要求的n和源点不连通,它不会得到更新,还是保持的0x3f3f3f3f。

4) ⭐️ Bellman_ford算法可以存在负权回路,是因为其循环的次数是有限制的因此最终不会发生死循环;但是SPFA算法不可以,由于用了队列来存储,只要发生了更新就会不断的入队,因此假如有负权回路请你不要用SPFA否则会死循环。

5) ⭐️由于SPFA算法是由Bellman_ford算法优化而来,在最坏的情况下时间复杂度和它一样即时间复杂度为 O(nm)O(nm) ,假如题目时间允许可以直接用SPFA算法去解Dijkstra算法的题目。(好像SPFA有点小小万能的感觉?)

6) ⭐️求负环一般使用SPFA算法,方法是用一个cnt数组记录每个点到源点的边数,一个点被更新一次就+1,一旦有点的边数达到了n那就证明存在了负环。

 

部分转自  作者:orzorz  链接:https://www.acwing.com/solution/content/9306/

 

// dijkstra堆优化, 每个点只能用一次, 用它更新过就不能再用了, 值得注意的是, 每次在循环最前面标记即可, 已标记则跳过

#include <iostream>
#include <cstring>
#include <queue>

using namespace std;

const int N = 1e5 + 5e4;

typedef pair<int,int> PII;

int e[N], h[N], ne[N], w[N], idx;

int n, dist[N];
bool st[N];

void add(int a, int b, int c)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx, w[idx ++] = c;
}

void dijkstra()
{
   memset(dist, 0x3f, sizeof dist);
   dist[1] = 0;
   priority_queue<PII, vector<PII>, greater<PII>> q;
   q.push({0,1});
   
   while(q.size())
   {
       PII t = q.top();
       int x = t.second;
       q.pop();
       if(st[x])continue;
       
       st[x]=1;
       
       for(int i = h[x]; i != -1; i = ne[i])
       {
           int j = e[i];
           if(dist[j] > dist[x] + w[i])
           {
               dist[j]=dist[x]+w[i];
               q.push({dist[j], j});
           }
       }
   }
   
   if(dist[n]==0x3f3f3f3f)puts("-1");
   else cout << dist[n] << '\n';
   
}

int main()
{
    int m;
    cin >> n >> m;
    
    memset(h, -1, sizeof h);
    while(m --)
    {
        int a, b, c;
        cin >> a >> b >> c;
        add(a,b,c);
    }
    
    dijkstra();
}

 

// bellman_ford的思维, 每个点最多更新n-1次, 那么放心大胆的标记吧, 不会超时循环很多次
// 在队中的不能重复入队就行, 


#include <iostream>
#include <cstring>
#include <queue>

using namespace std;

const int N = 1e5 + 10;

int e[N], ne[N], h[N], w[N], idx;
int n, m, dist[N];
bool st[N];

void add(int a, int b, int c)
{
    e[idx]=b, ne[idx]=h[a], h[a]=idx, w[idx ++]=c;
}

void spfa()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;
    queue<int> q;
    q.push(1);
    st[1] = 1;
    
    while(q.size())
    {
        int t = q.front();
        q.pop();
        st[t] = 0;
        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] =1;
            }
        }
    }
    
    
    if(dist[n] >= 0x3f3f3f3f/2)puts("impossible");
    else    cout << dist[n] << '\n';
    return;
}

int main()
{
    cin >> n >> m;
    for(int i = 0; i <= n; i ++)h[i] = -1;
    for(int i = 0; i  <m; i ++)
    {
        int a, b, c;
        cin >> a >> b >> c;
        add(a,b,c);
    }
    
    spfa();
    
    return 0;
}

 

 

 

 

posted @ 2021-12-07 11:55  la-la-wanf  阅读(34)  评论(0编辑  收藏  举报