图论小结(超详细)

最短路

在这里插入图片描述

单源最短路

权值皆为正数

朴素Dijstra

以下是我对单源最短路径有了的认知,迪杰斯特拉算法就是我们常用的最短路算法,该算法本质是在贪心的基础上进行的图论延申。
单源最短路径即为从一个点到其他点的最短路。该算法主要通过以下几步来完成。
1:初始化最短路数组,dis[1]=0,dis[i]=+1000000000;
2:循环n次,每次找出所有点中距离起点最近的那个点,将该点标记后,用该点更新其他的点的最短路距离。
以下即为迪杰斯特拉算法的朴素算法代码模板。

#include <iostream>
#include <algorithm>
#include<vector>
#include<map>
#include<string>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<set>
#include<unordered_map>
#include<queue>
#include<climits>
#include<stack>
using namespace std;
const int maxn= 510;
const int INF=INT_MAX;
int g[maxn][maxn];
int d[maxn];
int n,m;
bool vis[maxn]= {false};
int dijkstra(int s)
{
    memset(d, 0x3f,sizeof d);
    d[s]=0;
    for(int i=0; i<n; i++)
    {
        int u=-1;
        for(int j=1; j<=n; j++)
            if(vis[j]==false &&(u==-1|| d[j]<d[u]))
                u=j;
        vis[u]=true;
        for(int v=1; v<=n; v++)
            d[v]=min(d[v],g[u][v]+d[u]);
    }
    if(d[n]!=0x3f3f3f3f)
        return d[n];
    else return -1;
}

int main()
{
    cin>>n>>m;
    memset(g,0x3f,sizeof g);
    for(int i=0; i<m; i++)
    {
        int x,y,z;
        cin>>x>>y>>z;
        g[x][y]=min(g[x][y],z);
    }
    printf("%d\n",dijkstra(1));
    return 0;
}

//用该算法最大可以解决100*100的路径问题;若是数据量增大到1e5则需要使用数据结构进行优化。

堆优化版Dijstra
#include<stdio.h>
#include<iostream>
#include<vector>
#include<cstring>
#include<queue>
using namespace std;
const int N=1e6+6;
#define PII pair<int ,int>
int n, m;
int h[N], e[N], ne[N], w[N], idx;
int dist[N];
bool st[N];

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;
    priority_queue<PII, vector<PII>, greater<PII> > heap;
    //先把1号点加入队列
    heap.push({0, 1});
    while(heap.size())
    {
        //直接取出对头min
        auto t = heap.top();
        heap.pop();
        int ver = t.second, dt = t.first;
        //如果已经是确定了最短路的点就跳过
        if(st[ver])
            continue;
        st[ver] = 1;
        //开始更新以对头t为起点的边
        for(int i = h[ver]; i!=-1; i = ne[i])
        {
            int j = e[i];
            if(dist[j] > dist[ver] + w[i])
            {
                dist[j] = dist[ver] + 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);
    while(m --)
    {
        int a, b, c;
        cin >> a >> b >> c;
        add(a, b, c);
    }
    cout << Dijkstra() << endl;
    return 0;
}



#include<iostream>
#include<vector>
#include<queue>
using namespace std;
const int MAX_V=10005;
const int INF=0x3f3f3f;
struct edge
{
    int to;//边的终点
    int cost;//权值
    edge(int t,int c)
    {
        to=t;
        cost=c;
    }
};
typedef pair<int,int> P;//first是该点入队时的最短距离,second是顶点编号
int V;//顶点数
int E;//边数
vector<edge> G[MAX_V];
int d[MAX_V];


void dijkstra(int s)
{
    priority_queue<P,vector<P>,greater<P> > que;//优先队列里存的都是最短距离已经确认的顶点
    fill(d,d+V+1,INF);
    d[s]=0;
    que.push(P(0,s));//起点,起点到起点的最短距离是确定的(0)
    while(!que.empty())
    {
        P p=que.top();
        que.pop();
        int v=p.second;
        if(d[v]<p.first)
            continue;//表示该点入队不只一次(即之前有多个点都可以到达它),那么d[v]也可能更新了不只一次,
        for(int i=0; i<G[v].size(); i++) //扫描其所有相邻的顶点,并更新他们的最短距离d[i]
        {
            edge e=G[v][i];
            if(d[e.to]>d[v]+e.cost)
            {
                d[e.to]=d[v]+e.cost;//d[v]是已经确定的S
                que.push(P(d[e.to],e.to));//更新后最短距离已经确认的点入队,优先队列里自动维护,队头是最小的那个
            }
        }
    }
}
int main()
{
    int start,to,cost;
    scanf("%d%d",&V,&E);
    for(int i=0; i<E; i++)
    {
        scanf("%d%d%d",&start,&to,&cost);
        G[start].push_back(edge(to,cost));
    }
    dijkstra(1);
    for(int i=1; i<=V; i++)
        printf("%d ",d[i]);
    return 0;
}

存在负权值边

Bellman-Ford
#include<bits/stdc++.h>
using namespace std;
const int N=100010;
int dist[N],backup[N];
int k,n,m;
struct edge
{
    int a;
    int b;
    int w;
} edge[N];
int bellman_ford()
{
    memset(dist,0x3f,sizeof dist);
    dist[1]=0;
    for(int i=1; i<=k; i++)
    {
        memcpy(backup,dist,sizeof dist);//将dist内的数据存储到backup
        for(int j=1; j<=m; j++)
        {
            int a=edge[j].a,b=edge[j].b,w=edge[j].w;
            dist[b]=min(dist[b],backup[a]+w);
        }
    }
    return dist[n];
}
int main()
{
    cin>>n>>m>>k;
    for(int i=1; i<=m; i++)
    {
        int a,b,c;
        cin>>a>>b>>c;
        edge[i].a=a,edge[i].b=b,edge[i].w=c;
    }
    int t=bellman_ford();
    if(t>=0x3f3f3f3f/2)puts("impossible");
    else cout<<t<<endl;
}

SPFA
#include <iostream>
#include <string.h>
#include <queue>
using namespace std;
const int N=100010;
int n,m;
int e[N],ne[N],w[N],h[N],idx;
int dist[N];
bool st[N];
void add(int a,int b,int c){
    e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
int spfa()
{
    queue<int> q;
    memset(dist,0x3f,sizeof dist);
    dist[1]=0;
    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;
                }
            }
        }
    }
    return dist[n];
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin>>n>>m;
    memset(h,-1,sizeof h);
    while(m--){
        int a,b,w;
        cin>>a>>b>>w;
        add(a,b,w);
    }
    if(spfa()>0x3f3f3f3f/2) cout<<"impossible";
    else    cout<<dist[n];
    return 0;
}

多源最短路

Floyd

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

using namespace std;

typedef pair<int, int> PII;

const int inf = 0x3f3f3f3f;
const int N = 55, M = 1600;

PII p[M];  // 保存边插入的顺序
int n, m, k, T, f[N][N], init[N][N];


void floyed()
{
    for (int v = 1; v <= n; ++v)
    {
        f[v][v] = 0;
        for (int i = 1; i <= n; ++i)
        {
            for (int j = 1; j <= n; ++j) f[i][j] = min(f[i][j], f[i][v] + f[v][j]);
        }
    }
}

int main()
{
    cin >> T;
    while (T--)
    {
        memset(init, inf, sizeof init);
        cin >> n >> m >> k;
        for (int i = 1; i <= m; ++i)
        {
            int a, b, c;
            cin >> a >> b >> c;
            p[i] = {a, b};
            init[a][b] = c;
            init[b][a] = c;
        }
        memcpy(f, init, sizeof f);  // 回到初始状态
        floyed();
        printf("%d\n", f[1][n]);
        memcpy(f, init, sizeof f);
        for (int i = 1; i <= k; ++i)
        {
            // 删除第x次插入得边,注意从a->b, b->a都要删除,删除就是更新为inf
            int x;
            cin >> x;
            f[p[x].first][p[x].second] = inf;
            f[p[x].second][p[x].first] = inf;
        }
        floyed();  // 再次佛洛依德[添加链接描述](https://editor.csdn.net/md/?articleId=121609566)
        if (f[1][n] > inf / 2) puts("-1");
        else printf("%d\n", f[1][n]);
    }
    return 0;
}


最小生成树

2:最小生成树

Kruskal

此方法可以称为加边法。即将边的权值大小排好序之后按照从小到大的顺序依次将n-1条边加入到生成树中

Prim

对应于上述的加边法,这个算法可以称之为加点法。 每次迭代选择代价最小的边对应的点,加入到最小生成树中。算法从某一个顶点s开始,逐渐长大覆盖整个连通网的所有顶点。先遍历一遍当前点的所有边,找出最小边,将该边的另一个顶点加入到生成树上,并继续遍历该点的所有边,每次将边的权值放入辅助数组中,最后在数组中求出最小便=边的顶点,重复上述步骤,直到最小生成树有n-1条边或者n个顶点为止。

点此连接直达最小生成树板块(prim&&kruskal)

最近公共祖先(LCA)

在这里插入图片描述

朴素算法

1,首先将数据存储在邻接表里,先将数据按照并查集存储,然后将叶子节点的深度全部神深搜出来,存储到深度数组中。
2,然后就是具体做法:
先判断两个数是否在一棵树的同一层上,若不是先调整到同一层上。然后将两个数据在并查集内,同时向上搜寻,直至fath[a]==fath[b];

点此链接查看例题与代码解释

倍增法(Tarjan)

这个算法应该是最容易想到的,因为它本质上属于暴力算法的优化版本,只是把暴力算法的一次只跳一步变为了一次跳 2^k 步。

拓扑排序

对一个有向无环图 ( Directed Acyclic Graph 简称 DAG ) G 进行拓扑排序,是将 G
中所有顶点排成一个线性序列,使得图中任意一对顶点 u 和 v ,若边 < u , v > ∈ E ( G ),则 u 在线性序列中出现在 v之前。通常,这样的线性序列称为满足拓扑次序 ( Topological Order )
的序列,简称拓扑序列。简单的说,由某个集合上的一个偏序得到该集合上的一个全序,这个操作称之为拓扑排序。(若图中存在环路则不可能排出一个线性次序)

总结起来有三个要点:
1.有向无环图;
2.序列里的每一个点只能出现一次;
3.任何一对 u 和 v ,u 总在 v 之前(这里的两个字母分别表示的是一条线段的两个端点,u 表示起点,v 表示终点)

许多实际应用都需要有向无环图来指明事件的优先次序,例如可以应用来求解食物链总条数。

点此跳转查看

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Prime me

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值