ACM图论模板(更新ing...)

版权声明:最后一年,加油~ https://blog.csdn.net/zzti_xiaowei/article/details/79769398

1、 最短路算法

  • Bellman-Ford算法
  • Dijkstra算法
  • SPFA算法
  • Floyd算法
  • 被气死的WA

2、最小生成树算法

  • Prim算法
  • Kruskal算法
  • 被气死的WA

1、单源最短路(Bellman-Ford算法)

描述:思想为连续对每条边进行松弛操作,在每次松弛时把每条边都更新一下,若在V-1次松弛后还能更新,则说明图中有负环。可以求含负权图及判定负环的最短路算法。
复杂度: O(VE)

// Bellman-Ford算法
struct edge{ //从顶点from指向顶点to权值为cost的边
    int from,to,cost;
}es[Max_E]; 
int V,E;
int d[Max_V];

void Bellman_Ford(int s){
    memset(d,0x3f,sizeof(d));
    d[s]=0;
    //int k=0; //用于判定负环 
    while(true){
        bool flag=false;
        for(int i=0;i<E;i++){
            edge e=es[i];
            if(d[e.from]!=inf&&d[e.to]>d[e.from]+e.cost){
                d[e.to]=d[e.from]+e.cost;
                flag=true;
            }
        }
        if(!flag)break;
        //if(V==++k){printf("存在负环\n");return;}
    }
}

备注:有向图中只要含有一个权值和为负数的有向闭合路径,称图含有负环;无向图只要含有一条边权值为负,就称含有负环。

2、单源最短路(Dijkstra算法)

描述:思想为运用贪心策略(没有负边),不断从已经确定最短距离的顶点集合(开始只有起点s)向外找与该顶点集合距离最近的顶点,加入到该集合并更新与该顶点相连顶点的最短距离。
复杂度: 邻接表+优先队列 O(ElogV)       // 有负边的话

// Dijkstra算法
typedef pair<int,int> P;
struct edge{
    int to,cost; //first是最短距离,second是顶点的编号
    edge(int t,int c):to(t),cost(c){};
};
int V,E;
int d[Max_v];
vector<edge>G[Max_v];

void Dijkstra(int s){
    memset(d,0x3f,sizeof(d));
    d[s]=0;
    priority_queue<P,vector<P>,greater<P> >que; //按照first从小到大顺序取出
    que.push(P(0,s));
    while(!que.empty()){
        P p=que.top();que.pop();
        int v=p.second;
        if(p.first>d[v])continue;
        for(int i=0;i<G[v].size();i++){
            edge e=G[v][i];
            if(d[e.to]>d[v]+e.cost){
                d[e.to]=d[v]+e.cost;
                que.push(P(d[e.to],e.to));
            }
        }
    }
}

3、单源最短路(SPFA算法)

描述: 队列优化后的Bellman-Ford算法,减少了冗余的松弛操作。优化:在Bellman-Ford算法中,要是某个点的最短路径估计值更新了,那么我们必须对所有边指向的终点再做一次松弛操作;在SPFA算法中,某个点的最短路径估计值更新,只有以该点为起点的边指向的终点需要再做一次松弛操作。
复杂度: O(kE)   不稳定,最坏<O(VE)

// SPFA算法
struct edge{
    int to,cost;
    edge(int t,int c):to(t),cost(c){};
};
int V,E;
bool vis[Max_v]; //在队列标志
int d[Max_v];   
int cnt[Max_v]; //入队次数
vector<edge>G[Max_v];

bool SPFA(int s){
    memset(vis,0,sizeof(vis));
    memset(d,0x3f,sizeof(d));
    memset(cnt,0,sizeof(cnt));
    vis[s]=1;d[s]=0;cnt[s]=1;
    queue<int>que;
    que.push(s);
    while(!que.empty()){
        int u=que.front();que.pop();
        vis[u]=0;
        for(int i=0;i<G[u].size();i++){
            int v=G[u][i].to;
            if(d[v]>d[u]+G[u][i].cost){
                d[v]=d[u]+G[u][i].cost;
                if(!vis[v]){
                    vis[v]=1;que.push(v);
                    if(++cnt[v]>V)return false;  //判定负环
                }
            }
        }
    }
    return true;
}

4、多源最短路(Floyd算法)

描述: 运用动态规划思想的一个经典多源最短路算法。可以处理负权图的最短路和传递闭包,而判断图中是否有负圈,只需要检查是否存在d[i][i]是负数的顶点i就可以了。递推方程:d[i][j]=min(d[i][j],d[i][k]+d[k][j])
复杂度: O(v3)

// Floyd算法
int n,m;
int d[Max_n][Max_n];
int path[Max_n][Max_n]; //记录路径

void init(){ //初始化
    for(int i=1;i<=n;i++) 
        for(int j=1;j<=n;j++){
            d[i][j]=inf;
            path[i][j]=j; //path[i][j]=x表示i到j的路径上(除i外)的第一个点是x
            if(i==j)d[i][j]=0;
    }
}

void getpath(int st,int ed){ //打印路径
    while(st!=ed) {
        printf("%d->",st);
        st=path[st][ed];
    }
    printf("%d\n",ed);
}

void Floyd(){  //Floyd算法
     for(int k=1;k<=n;k++) 
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++) {
                if(d[i][j]>d[i][k]+d[k][j]){
                    d[i][j]=d[i][k]+d[k][j];
                    path[i][j]=path[i][k];
                }
            }
}

5、被气死的WA

  • 数组开小越界。
  • 多次循环没有重新初始化变量,数组及容器。

    1. for(int i=0;i<=N;i++)G[i].clear(); //Vector容器清空
    2. while(!que.empty())que.pop();//Queue容器清空

6、Prim算法 —–让一棵小树长大

描述:又称”加点法”,运用贪心思想,从某个顶点出发,不断向生成树顶点集合X添加距离X最近的顶点。添加顶点数 < V时,图不连通。
复杂度: O(v2)

// Prim算法
// 已求得生成树的顶点集合记为X
int V,E;
int dist[Max_v]; //从集合X出发的边到每个顶点的最小权值
bool vis[Max_v]; //顶点i是否在集合X中
int cost[Max_v][Max_v]; //cost[u][v]表示边e=(u,v)的权值(不存在设为INF)

int Prim()
{
    int ans=0,sum=0; //收入顶点数和最小权重和
    memset(dist,0x3f,sizeof(dist));
    memset(vis,0,sizeof(vis));
    dist[0]=0;
    while(true)
    {
        int v=-1;
        for(int i=0;i<V;i++) //v=未收录顶点集合X中dist的最小值
            if(!vis[i]&&dist[i]!=inf&&(v==-1||dist[i]<dist[v]))v=i;
        //dist[i]!=inf是必要的,图可能不连通
        if(v==-1)break;
        vis[v]=1;  //将顶点v加入X
        ans++;sum+=dist[v];
        for(int i=1;i<=V;i++) //更新v的每个邻接点dist的值
            if(!vis[i]&&dist[i]>cost[v][i])
                dist[i]=cost[v][i];
    }
    if(ans<V)return -1; //图不连通
    return sum;
}

7、Kruskal算法 —–将森林合并成树

描述:又称”加边法”,运用贪心思想,按边的权值从小到大排序,依次从边中选择一条不会产生环路的具有最小权值的边加入生成树的边集合中。添加边数 < V-1时,图不连通。
复杂度: O(ElogE)

// Kruskal算法
struct edge{
    int from,to,cost;
    bool operator<(const edge& e)const{
        return cost<e.cost;
    }
}e[Max_e];
int V,E;
int par[Max_v];  //根节点

int Find(int x){ //查询树的根(路径压缩)
    if(par[x]==x)return x;
    return par[x]=Find(par[x]);
}

bool Kruskal(){
    int ans=0,sum=0; //收入边数和最小权重和
    for(int i=0;i<V;i++)par[i]=i; //初始化
    sort(e,e+E);
    for(int i=0;i<E;i++){
        int x=Find(e[i].from);
        int y=Find(e[i].to);
        if(x!=y){
            ans++;sum+=e[i].cost;
            par[x]=y;
        }
        if(ans==V-1)break;
    }
    if(ans<V-1)return -1;
    return sum;
}

8、被气死的WA

  • 顶点集、边集数组开小越界。
  • 顶点下标是0…n-1,处理数据时:
    1. Prim算法读入数据:cost[a-1][b-1]=c;cost[b-1][a-1]=c;
    2. Kruskal算法读入数据:e[i].from=a-1;e[i].to=b-1;e[i].cost=c;
    3. 如果不注意,Kruskal算法初始化par数组for(int i=0;i<V;i++)par[i]=i;时,多组数据会WA!

猜你喜欢

转载自blog.csdn.net/zzti_xiaowei/article/details/79769398