图论算法整理与模板总结(一)

图论算法整理与模板总结(一)

这篇博客主要是acwing算法基础课的学习结果,同时作为图论的复习。
具体链接见: link

DFS与BFS

暂时不整理。

拓扑排序

在图论中,拓扑排序(Topological Sorting)是一个有向无环图(DAG, Directed Acyclic Graph)的所有顶点的线性序列。且该序列必须满足下面两个条件:
(1)每个顶点出现且只出现一次。
(2)若存在一条从顶点 A 到顶点 B 的路径,那么在序列中顶点 A 出现在顶点 B 的前面。
有向无环图(DAG)才有拓扑排序,非DAG图没有拓扑排序一说。

具体思路:
(1)建图
(2)统计所有的顶点的入度。
(3)构造队列,将所有入度为0的顶点入队,删除入度为0的顶点,更新队列和答案数组。

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

vector<vector<int>> map;
const int N=1e5+10;
int degree[N];
int ans[N];
int n,m;

bool Toposort()
{
    queue<int> q;
    int cnt=0;
    for(int i=1;i<=n;i++)
        if(degree[i]==0)
        {
            q.push(i);
            //cnt++;
        }
    
    //int t=0;   //总共输出了多少个;
    while(q.size())
    {
        int u=q.front();
        q.pop();
        ans[cnt++]=u;
        for(int i=0;i<map[u].size();i++)
        {
            int t=map[u][i];  //u->t;
            degree[t]--;
            if(degree[t]==0)
            {
                q.push(t);
            }
        }
    }
    
    //cout<<cnt<<endl;
    return cnt==n;
}

int main()
{
    cin>>n>>m;
    map.resize(n+1);
    
    for(int i=0;i<m;i++)
    {
        int a,b;
        cin>>a>>b;
        
        map[a].push_back(b);
        degree[b]++;   //a->b;
    }
    
    if(Toposort())
    {
        for(int i=0;i<n;i++)
            cout<<ans[i]<<' ';
    }
    else
        puts("-1");
    
    return 0;
}

最短路

正权图(Dijkstra)

朴素版Dij:
这里的模板注意:
不需要在一开始更新dis,在循环的过程中第一次一定选择第一个顶点并进行距离的更新。
复杂度为O(n^2),n为图中的顶点数;

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
const int N=510;

int map[N][N];
int dis[N];
bool vis[N];
int n,m;

int dij()
{
    memset(dis,0x3f,sizeof dis);
    dis[1]=0;
    
    for(int i=0;i<n-1;i++)
    {
        int t=-1;
        for(int j=1;j<=n;j++)
            if(!vis[j]&&(t==-1||dis[j]<dis[t]))
                t=j;
        
        vis[t]=true;
        for(int j=1;j<=n;j++)
            dis[j]=min(dis[j],dis[t]+map[t][j]);
    }
        
    if(dis[n]==0x3f3f3f3f) return -1;   //这里要特别注意?
    else return dis[n];
}

int main()
{
    cin>>n>>m;
    memset(map,0x3f,sizeof map);
    
    while(m--)
    {
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        map[a][b]=min(map[a][b],c);
    }
    
    int t=dij();
    cout<<t<<endl;
    
    return 0;
}

堆优化版Dij:
优化了找到距离源点最近的那个点的步骤,直接用堆存储。
注意堆里可能会有冗余的元素,需要判断,即一个顶点可能在堆里多次出现,只利用第一次出现进行更新。

#include<cstdio>
#include<iostream>
#include<queue>
#include<algorithm>
#include<vector>
#include<map>
#include<cstring>
using namespace std;
const int N=1e5+10;
typedef pair<int,int> PII;

vector<vector<PII>> g;
int n,m;
int dis[N];
bool vis[N];

int dij()
{
    memset(dis,0x3f,sizeof dis);
    dis[1]=0;
    
    priority_queue<PII,vector<PII>,greater<PII>> heap;
    heap.push({0,1});  //距离放在前面,编号放在第二个;
    
    while(heap.size())
    {
        auto t=heap.top();
        heap.pop();
        
        int u=t.second,d=t.first;
        if(vis[u]) continue;   //已经用u更新过了;(可能会有冗余)
        vis[u]=true;
        
        for(int i=0;i<g[u].size();i++)
        {
            int v=g[u][i].first;
            int w=g[u][i].second;        
            if(dis[v]>w+dis[u])
            {
                dis[v]=w+dis[u];
                heap.push({dis[v],v});
            }
        }
    }
    
    if(dis[n]==0x3f3f3f3f)  return -1;
    return dis[n];
} 

int main()
{
    cin>>n>>m;
    g.resize(n+1);
    
    int a,b,c;
    for(int i=0;i<m;i++)
    {
        cin>>a>>b>>c;
        g[a].push_back({b,c});
    }
    
    int k=dij();
    cout<<k<<endl;
    
    return 0;
}

有边数限制的最短路

Bellman-ford 算法:
这是存储所有边,并利用边的信息求最短路的一种方法;
边权可以为负,复杂度恒为O(mn)
不能出现负环。
题目有边数限制的要求,则一定只能用bellman_ford.

寻找有k条边的最短路;

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

const int N=510,M=10010;
struct Edge{
    int a,b,c;
}edges[M];

int n,m,k;
int dis[N];
int last[N];   //存储上一次更新后的结果;

void bellman_ford()
{
    memset(dis,0x3f,sizeof dis);
    dis[1]=0;
    for(int i=0;i<k;i++)   //最多k条边, 每循环一次添加一条边
    {
        memcpy(last,dis,sizeof dis);
        for(int j=0;j< m;j++)  
        {
            auto e=edges[j];
            dis[e.b]=min(dis[e.b],last[e.a]+e.c);   //这里很关键,本次的结果用上一次存储的来更新;
        }
    }
}

int main()
{
    cin>>n>>m>>k;
    
    for(int i=0;i<m;i++)
    {
        int a,b,c;
        cin>>a>>b>>c;
        edges[i]={a,b,c};
    }
    
    bellman_ford();
    if(dis[n]>0x3f3f3f3f / 2) cout<<"impossible"<<endl;
    else cout<<dis[n]<<endl;
    
    return 0;
}

spfa

spfa是求最短路的另一种方法,它的优点是允许有负权值出现,且复杂度的上限为O(mn);
它是bellman-ford的队列优化形式(仅有被松弛后的边继续参与后续的计算):

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

const int N=1e5+10;
typedef pair<int,int> PII;
vector<vector<PII>> g;
int dis[N];
bool vis[N];

int n,m;

int spfa()
{
    memset(dis,0x3f,sizeof dis);
    queue<int> q;
    dis[1]=0;
    
    q.push(1);
    vis[1]=true;
    
    while(q.size())
    {
        int t=q.front();
        q.pop();
        
        vis[t]=false;
        for(int i=0;i<g[t].size();i++)
        {
            int u=g[t][i].first;
            if(dis[u]>dis[t]+g[t][i].second)
            {
                dis[u]=dis[t]+g[t][i].second;
                if(!vis[u])
                {
                    q.push(u);
                    vis[u]=true;
                }
            }
        }
        
    }
    
    return dis[n];
}

int main()
{
    cin>>n>>m;
    
    g.resize(n+1);
    for(int i=0;i<m;i++)
    {
        int a,b,c;
        cin>>a>>b>>c;
        
        g[a].push_back({b,c});
    }
    
    int k=spfa();
    if(k==0x3f3f3f3f) cout<<"impossible"<<endl;
    else
        cout<<k<<endl;
    
    return 0;
}

spfa还可以用来判断图中是否出现负环:
核心思想是判断当前最短路径边数是否超过n。如果超过一定存在负环。

#include<cstdio>
#include<iostream>
#include<cstring>
#include<vector>
#include<map>
#include<queue>
using namespace std;
int n,m;
vector<vector<pair<int,int>>> g;

int cnt[2010],dis[2010];
bool vis[2010];

bool spfa()
{
    //注意,由于是寻找负环,所以这里并不需要进行正无穷的初始化
    //默认为0就可以
    queue<int> q;
    for(int i=1;i<=n;i++)   //先把所有顶点都放入队列中;
    {
        q.push(i);       
        vis[i]=true;    
    }
    
    while(q.size())
    {
        int t=q.front();
        q.pop();
        vis[t]=false;
        
        for(int i=0;i<g[t].size();i++)
        {
            int j=g[t][i].first;
            if(dis[j]>dis[t]+g[t][i].second)
            {
                dis[j]=dis[t]+g[t][i].second;
                cnt[j]=cnt[t]+1;
                if(cnt[j]>=n) return true;   //代表最短路径有n+1条边,肯定出现环;
            
                if(!vis[j])
                {
                    q.push(j);
                    vis[j]=true;
                }
            }
        }
    }
    
    return false;
}

int main()
{
    cin>>n>>m;
    g.resize(n+1);
    for(int i=0;i<m;i++)
    {
        int a,b,c;
        cin>>a>>b>>c;
        
        g[a].push_back({b,c});
    }
    
    if(spfa()) cout<<"Yes"<<endl;
    else cout<<"No"<<endl;
    
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值