最短路和最小生成树及拓扑排序(简单图论算法)

最短路

最短路问题相必大家都有接触,就是求一个图中的最短权重路径的和是多少

这种问题可以被抽象为很多种形式,算法的思维也很多,但归结到底都是最短路问题的探讨

先说最短路算法总共分为三种 dijkstra算法,spfa算法和foyd算法

用的最多的是spfa算法 (主要是方便)

  • 思维
    1 将所有点全部变为正无穷( inf ),并将起点存入队列
    在这里插入图片描述
    2 以起点为开始,用邻接表进行遍历,更新每个点(将每个点更新为距离最小的点)
    在这里插入图片描述
    3 直到找到最短路径

大多数的最短路问题都可以用spfa算法进行求解

int spfa(){
    
    
    memset(dist,0x3f,sizeof dist);  //第一步  将dist数组全变为正无穷
    queue<int> q; 
    q.push(1);   //将起点装进队列
    dist[1]=0;
    st[1]=true;   //用布尔st 数组来判断是否点已在队列
    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数组
                dist[j]=dist[t]+w[i]; 
                if(!st[j]){
    
      //防止重复加入队列已有的点
                    st[j]=true;
                    q.push(j);
                }
            }
        }
    }
    return dist[n];  //返回终点dist最小值
}

这里就不多讲了

来一道题
最优贸易
-> 讲解此题

#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int N=1e6+10,M=5e6+10;
int h[N],rh[N],e[M],ne[M],idx;
int p[N],dmax[N],dmin[N];
bool st[N];
int n,m;
void add(int *h,int a,int b){
    
    
    e[idx]=b;ne[idx]=h[a];h[a]=idx++;
}
void spfa_max(){
    
    
    queue<int> q;
    memset(st,0,sizeof st);
    q.push(n);
    dmax[n]=p[n];
    while(q.size()){
    
    
        int t=q.front();
        q.pop();
        st[t]=false;
        for(int i=rh[t];~i;i=ne[i]){
    
    
            int j=e[i];
            if(dmax[j]<max(p[j],dmax[t])){
    
    
                dmax[j]=max(p[j],dmax[t]);
                if(!st[j]){
    
    
                    st[j]=true;
                    q.push(j);
                }
            }
        }
    }
}
void spfa_min(){
    
    
    queue<int> q;
    memset(st,0,sizeof st);
    memset(dmin,0x3f,sizeof dmin);
    q.push(1);
    dmin[1]=p[1];
    while(q.size()){
    
    
        int t=q.front();
        q.pop();
        st[t]=false;
        for(int i=h[t];~i;i=ne[i]){
    
    
            int j=e[i];
            if(dmin[j]>min(dmin[t],p[j])){
    
    
                dmin[j]=min(dmin[t],p[j]);
                if(!st[j]){
    
    
                    st[j]=true;
                    q.push(j);
                }
            }
        }
    }
}
int main(){
    
    
    cin>>n>>m;
    memset(h,-1,sizeof h);
    memset(rh,-1,sizeof rh);
    for(int i=1;i<=n;i++) scanf("%d",&p[i]);
    for(;m;m--){
    
    
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        add(h,a,b),add(rh,b,a);  //反向建边
        if(c==2) add(h,b,a),add(rh,a,b);
    }
    spfa_max(); spfa_min();  //分别求起点路径最小权值的距离和终点路径权重到该点
    int res=0;
    for(int i=1;i<=n;i++) res=max(res,dmax[i]-dmin[i]);
    cout<<res;
    return 0;
}

这里来讲了下拓扑排序

拓扑排序

拓扑排序就是将图的每个点进行排序,以入度为0 为起点,进行沿着图进行排序输出

再讲拓扑排序之前,要讲两个概念入度和出度

  • 啥是入度
    就是一个点的有点多少个指向它的边(这里不能有重边)

  • 啥是出度
    就是一个点有多少个它指向其他点的边(这里不能有重边)

拓扑排序的怎样写
1 记录入度和出度的多少
2 将入度为0的点装进队列
3 每次遍历点,将该点的入度减一,为0加入队列

其实具体问题还是要具体分析,但思维还是没什么差别

来一道具体问题

最大食物链计数

代码

#include<bits/stdc++.h>
#define mod 80112002
using namespace std;
const int N=5010,M=5e7+10;
int h[N],e[M],ne[M],idx;  //邻接表
int d[N],r[N],c[N];    
int n,m,ans;
inline int read() {
    
        //快读
	char c = getchar(); int n = 0;
	while (c < '0' || c > '9') {
    
     c = getchar(); }
	while (c >= '0' && c <= '9') {
    
     n = (n << 1) + (n << 3) + (c & 15); c = getchar(); }
	return n;
}
void add(int a,int b){
    
      //存边
    e[idx]=b;ne[idx]=h[a];h[a]=idx++;
}
void spfa(){
    
    
    queue<int> q;
    for(int i=1;i<=n;i++)
        if(r[i]==0){
    
    
            q.push(i);  //入度为零,加入队列作为起点
            d[i]=1; 
        }
    while(q.size()){
    
    
        int t=q.front();
        q.pop();
        for(int i=h[t];~i;i=ne[i]){
    
    
            int j=e[i];
            d[j]+=d[t];  //记录此地以上有多少条食物链
            d[j]%=mod;
            r[j]--;   //每次入度减一
            if(!r[j]){
    
       //为零加入队列
                if(c[j]) q.push(j);      
                else ans+=d[j],ans%=mod;  //出度为零为终点,此时加食物链的个数
            }
        }
    }
}
int main(){
    
    
    cin>>n>>m;
    memset(h,-1,sizeof h);  //邻接表初始化
    for(;m;m--){
    
    
        int a,b;
        a=read(),b=read();
        add(a,b); //食物链不存在重边
        c[a]++;   //入度和出度记录
        r[b]++;
    }
    spfa();
    cout<<ans;  //输出
    return 0;
}

最小生成树

这里我直接介绍最好用的 kruskal算法

  • 原理
    1 将所有权边由小到大排序(sort)
    2 记录两点是否在一个集合(并查集)
    3 累加权边

在这里插入图片描述
模板

struct Edge     // 存储边
{
    
    
    int a, b, w;

    bool operator< (const Edge &W)const //重载
    {
    
    
        return w < W.w;
    }
}edges[M];

int kruskal()
{
    
    
    sort(edges, edges + m);  

    for (int i = 1; i <= n; i ++ ) p[i] = i;    // 初始化并查集

    int res = 0, cnt = 0;
    for (int i = 0; i < m; i ++ )
    {
    
    
        int a = edges[i].a, b = edges[i].b, w = edges[i].w;

        a = find(a), b = find(b);
        if (a != b)     // 如果两个连通块不连通,则将这两个连通块合并
        {
    
    
            p[a] = b;
            res += w;
            cnt ++ ;
        }
    }

    if (cnt < n - 1) return 0x3f3f3f3f;
    return res;
}

来一道简单题吧

城市公交网建设问题

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
using namespace std;
const int N=110,M=1010;
int p[N];
int n,m;
struct ss{
    
      
    int a,b,w;
    bool operator <(const ss& W)const{
    
    
        return w<W.w;
    }
}s[M];
int find(int x){
    
     //并查集找爸爸函数
    if(p[x]!=x) p[x]=find(p[x]);
    return p[x];
}
void kruskal(){
    
    
    sort(s,s+m);  //排序
    for(int i=1;i<=n;i++) p[i]=i; //初始化并查集
    for(int i=0;i<m;i++){
    
    
        int a,b,w;
        a=s[i].a,b=s[i].b,w=s[i].w;
        int fx=find(a),fy=find(b);
        if(fx!=fy){
    
    
            p[fx]=fy;
            cout<<a<<" "<<b<<endl; //输出连接的边
        }
    }
}
int main(){
    
    
    cin>>n>>m;
    for(int i=0;i<m;i++){
    
    
        int a,b,w;
        scanf("%d%d%d",&a,&b,&w);
        s[i]={
    
    a,b,w};
    }
    kruskal();
    return 0;
}

好了我也才刚刚学完图论基础,也不是很懂,请见谅

猜你喜欢

转载自blog.csdn.net/m0_52361859/article/details/113678984
今日推荐