2.3搜索与图论(最短路)

1.朴素Dijkstra算法

稠密图用邻接矩阵,稀疏图用邻接表存

1≤m≤10^5,所以显然这是个稠密图,由于图中可能存在重边和自环,于是我们用g[N][N]存储每条边的距离时,先要将其初始化为无穷,然后读取时比较一下其与原数组中的值的大小,取最小值存储。Dijkstra方法中,对于每个数都循环一次,先找到没有找到过的距离1点最近的点,把它的st数组中的值设为真,然后遍历所有数更新其距离。

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;

const int N = 510;
int g[N][N] , dist[N];//g[i][j]存储i到j的距离,dist表示每个点到1的距离
int n , m;
bool st[N];//表示每个点是否能确定到1的距离

int dijkstra()
{
    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] and (t = -1 or dist[j] < dist[t]))//j没有确定最短路并且(t没有复制或者t并不是最短的)
                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];
}

int main()
{
    cin >> n >> m;
    memset(g , 0x3f , sizeof g);//初始化成很大的数
    while (m--)
    {
        int a , b , c;
        scanf("%d%d%d" , &a , &b , &c);
        g[a][b] = min(c , g[a][b]);
    }
    cout << dijkstra() <<endl;
    return 0;
}

2.用堆实现的Dijkstra算法

用新开一个数组w[N]表示权重,有了新的add方法的写法。

用priority_queue<PII , vector<PII> , greater<PII>>即小根堆存储,因为每次循环过后,找的是除了能确定距离的点外距离最小的点,将其的st数组值转为可以确定距离,多以用小根堆从小到大排列的特性可以非常方便的找到那个点。

priority_queue<PII , vector<PII> , greater<PII>>第一个数是距离,因为要用距离来排,第二个数是代表这是第几个点。如果队列不空就一直循环,先把队头取出,如果队头代表的点已经取出过了,就continue取下一个点;如果没有更新一下布尔数组,再对与其相连的点进行枚举,如果现存的距离比dist[k]加两点相连的距离大就表示dist数组可以更新,并且其距离已经确定,加入小根堆中。

#include <iostream>
#include <cstring>
#include <queue>
using namespace std;

typedef pair<int , int> PII;
const int N = 150010;
int h[N] , e[N] , ne[N] , w[N] , idx;//采用邻接表来存储,不是g[N][N]邻接矩阵
int dist[N];
bool st[N];
int n , m;

void add(int a , int b ,int c)
{
    e[idx] = b , w[idx] = c , ne[idx] = h[a] , h[a] = idx++;
    //有权重的邻接表存储
}

int dijkstra()
{
    memset(dist , 0x3f , sizeof dist);//初始化为无穷大;
    dist[1] = 0;//编号为1 的点距离起点的距离为0;
    priority_queue<PII , vector<PII> , greater<PII>> heap;//设置小根堆
    heap.push({0,1});//小根堆要根据距离来排,即第一个数是距离,第二个数是他是哪个点
    //插入1号点的距离为1

    while (heap.size())//当堆不空
    {
        auto t = heap.top();//取出队头并删除队头
        heap.pop();
        int k = t.second , distance = t.first;//t是第k个点,到起点距离为distance
        if (st[k]) continue;//此点冗余
        st[k] = true;

        for (int i = h[k] ; i != -1 ; i = ne[i])//循环
        {
            int j = e[i];
            if (dist[j] > dist[k] + w[i])//如果dist值可以更新
            {
                dist[j] = dist[k] + w[i];//那就更新,并将其加入堆
                heap.push({dist[j] , j});
            }
        }
    }
    if(dist[n] == 0x3f3f3f3f) return -1;
    return dist[n];
}

int main()
{
    cin >> n >> m;
    memset(h , -1 , sizeof h);//常规操作,把表头指向-1;
    while(m--)
    {
        int a, b ,c;
        scanf("%d%d%d" , &a , &b , &c);
        add(a , b , c); //采用堆就不用考虑重边的问题
    }
    cout << dijkstra() <<endl;
    return 0;
}

 3. Bellman Ford算法

处理最多经过k条边,拥有负权回路的最短路

先枚举k次操作,每次操作枚举m条边,看看能不能更新

代码如下:

#include <iostream>
#include <cstring>
#include <algorithm>
const int N = 510 , M = 10010;
int dist[N] , backup[N];
using namespace std;

struct Edge{  //结构体
    int a , b , w;
}edges[M];

int n , m , k;

void bellman_ford()
{
    memset(dist , 0x3f , sizeof dist);
    dist[1] = 0;
    
    for (int i = 0; i < k; i ++ )  //枚举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;
            dist[b] = min(dist[b] , backup[a] + w);
        }
    }
}


int main()
{
    cin >> n >> m >> k;
    int i = 0;
    for (int i = 0; i < m; i ++ )
    {
        int a , b , w;
        scanf("%d%d%d" , &a , &b , &w);
        edges[i] = {a , b , w};
    }
    bellman_ford();
    if (dist[n] > 0x3f3f3f3f / 2) puts("impossible");  //如果dist[n]依然很大
    else cout << dist[n] << endl;
    
    return 0;
}

4. spfa算法

代码跟堆优化的dijkstra比较相似,y总推荐使用

说很多正权边的最短路本来是用dijkstra算法,事实上可能spfa也可以过

处理边权可能为负数,不存在负权回路的最短路

代码如下:

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

using namespace std;
const int N = 100010;
int h[N] , e[N] , w[N] , ne[N] , idx;
int n , m;
int dist[N];
bool st[N];  //存储每个数在不在队列q中

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

void spfa()
{
    memset(dist , 0x3f , sizeof dist);
    dist[1] = 0;
    
    queue<int> q;
    q.push(1);
    
    while (q.size())
    {
        int t = q.front();
        st[t] = false;  //t出队,将其状态变更
        q.pop();
        
        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不在队列中
                {
                    st[j] = true;  //将j加入队列
                    q.push(j);
                }
            }
        }
    }
}


int main()
{
    cin >> n >> m;
    memset(h, -1, sizeof h);
    
    while (m -- )
    {
        int a , b , c;
        scanf("%d%d%d" , &a , &b , &c);
        add(a , b , c);
    }
    spfa();
    
    if (dist[n] == 0x3f3f3f3f) puts("impossible");
    else cout << dist[n] << endl;
    
    return 0;
}

4.1 spfa算法判负环

跟spfa本体大差不差

#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
const int N = 2010 , M = 10010;
int h[N] , e[M] , ne[M] , w[M] , idx;
int dist[N] , cnt[N];
bool st[N];  //该数是否进队列
int n , m;

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

bool spfa()
{
    
    queue<int>q;
    //并不是求1到n的路径有没有负环,有可能1到不了,所以把所有点加进去
    for (int i = 1; i <= n; i ++ )  
    {
        q.push(i);
        st[i] = 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];
                cnt[j] = cnt[t] + 1 ;
                if (cnt[j] >= n) return true;  //如果到j的路径经过大于等于n个点
                if (!st[j])
                {
                    st[j] = true;
                    q.push(j);
                }
            }
        }
    }
    return false;
}

int main()
{
    cin >> n >> m;
    memset(h, -1, sizeof h);
    
    while (m -- )
    {
        int a , b , c;
        scanf("%d%d%d" , &a , &b , &c);
        add(a, b, c);
    }
    if (spfa()) puts("Yes");
    else puts("No");
    
    return 0;
}

5. Floyd多源汇最短路

代码挺简单,三重循环

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 210;
int g[N][N];
int n , m , k;

void floyd()
{
    for (int k = 1 ; k <= n ; k ++)  //三重循环
        for (int i = 1 ; i <= n ; i ++)
            for (int j = 1 ; j <= n ; j ++)
                g[i][j] = min(g[i][j] , g[i][k] + g[k][j]);
}

int main()
{
    cin >> n >> m >> k;
    memset(g , 0x3f , sizeof g);  //除了相同的点,其余边全部初始化成无穷
    for (int i = 1 ; i <= n ; i ++)
        g[i][i] = 0;
    
    while (m -- )
    {
        int a , b , c;
        scanf("%d%d%d" , &a , &b , &c);
        g[a][b] = min(g[a][b] , c);  //读入所有边
    }
    floyd();
    while (k --)
    {
        int a , b;
        scanf("%d%d" , &a , &b);
        if (g[a][b] > 0x3f3f3f3f / 2) puts("impossible");
        else printf("%d\n" , g[a][b]);
    }
    
    return 0;
}

 

posted @ 2022-03-05 17:56  乐池  阅读(39)  评论(0)    收藏  举报