所有的基础最短路问题 dijkstra、堆优化dijkstra、bellman-ford、SPFA、Floyd

以下代码都是自己敲的,结合acwing和算法笔记的一些思路整理。禁止盗用!

有些模板算法是acwing的 做了标记。

所有的基础最短路问题 dijkstra、堆优化dijkstra、bellman-ford、SPFA、Floyd_第1张图片

一、朴素Dijkstra算法

       整体思想

              1.找到与起点s最近的且未确认最短路径的顶点(记为u),访问并加入集合st(确定了最短路的集合)。

              2.之后,令u为中介点,优化起点s与所有经过u可以到达的顶点v的最短距离。

       算法模板

//朴素Dijkstra 邻接矩阵存储稠密图  
int g[N][N];//g[a][b]:a->b的权重 
int dist[N];//dist[i]:起点到i的距离 
bool st[N];//st[i]:i点是否已确定最短路 

void dijkstra(int u)//u为起点 
{
	memset(dist,0x3f,sizeof dist);//距离都初始化为很大的数 
	dist[u]=0;//u->u 距离为0 起点为0 
	
	for(int i=0;idist[j]))//寻找最近的点 
				t=j;
		
		st[t]=true;//找到的点 确定了最短路 
		
		for(int j=1;j<=n;j++)//遍历n个点 用最新的点更新迭代所有出边  
			dist[j]=min(dist[j],dist[t]+g[t][j]);

	}
}


       算法题目

                                            849. Dijkstra求最短路 I - AcWing题库

#include
#include
using namespace std;

const int N=510;
int g[N][N],dist[N];
bool st[N];
int n,m;

void dijkstra(int u)
{
    memset(dist,0x3f,sizeof dist);
    dist[u]=0;
    
    for(int i=0;idist[j]))
                t=j;
        
        st[t]=true;
        
        for(int j=1;j<=n;j++)
            dist[j]=min(dist[j],dist[t]+g[t][j]);
    }
}

int main()
{
    scanf("%d%d",&n,&m);
    memset(g,0x3f,sizeof g);
    
    while(m--)
    {
        int a,b,w;
        scanf("%d%d%d",&a,&b,&w);
        g[a][b]=min(g[a][b],w);
    }
    
    dijkstra(1);
    
    if(dist[n]==0x3f3f3f3f) printf("-1\n");
    else printf("%d\n",dist[n]);
    return 0;
}

算法笔记—P368 例题 亚历山大灭六国

#include
#include
using namespace std;

const int N=1000;
int g[N][N],dist[N];
bool st[N];
int n,m,k;

void dijkstra(int u)
{
	memset(dist,0x3f,sizeof dist);
	dist[u]=0;
	
	for(int i=0;idist[j]))
				t=j;
		
		st[t]=true;
		
		for(int j=0;jdist[t]+g[t][j])
				dist[j]=dist[t]+g[t][j];
	}
}
int main()
{
	memset(g,0x3f,sizeof g);
	scanf("%d%d%d",&n,&m,&k);
	for(int i=0;i

我们已经求出了最短距离,那么最短路径本身我们应该怎么求呢?

在每一次u作为中间点可以更新其余顶点v的时候,可以用pre数组记录一下。(s->…->u->v)

int pre[N];//最短路径
        //一开始将前驱都指向自己   
        for(int i=1;i<=n;i++) pre[i]=i;
        //更新同时求最短路径
        for(int j=1;j<=n;j++)
			if(dist[j]>dist[t]+g[t][j])
            {
                dist[j]=dist[t]+g[t][j];
                pre[j]=t;
            }
        

但是平时比赛中,dijkstra不会就单纯考最短路径长度,或者,最短路径有多条,会增加一个第二标尺(第一标尺是距离)。第二标尺常见的是以下三种出题方法或其组合。

其实就只需要增加一个数组来存放新的数据,然后再优化dist[i]的时候增加修改即可。

        1、给每条边再增加一个边权(比如说花费),要求在最短路径多条时要求路径上花费之和最小(其他含义的话也可能是最大)

        解题方法:cost[u][v]表示u->v的花费(题目给出),并增加一个数组c[ ],令从起点s到达顶点u的最少花费为c[u],初始时只有c[s]=0,其余都最大。这样就可在优化dist时,如果路径相同,取花费更少的为最优解。

int cost[N][N],c[N];//cost[a][b]:a->b的花费 c[i]:起点到i点的最少花费 
	
	memset(c,0x3f,sizeof c);
	ct[u]=0; //u->u 不要花钱 
		
		for(int j=1;j<=n;j++)//遍历n个点 用最新的点更新迭代所有出边  
			if(dist[j]>dist[t]+g[t][j])
			{
				dist[j]=dist[t]+g[t][j];//以t为中介点可以使dist[j]更优 
				c[j]=c[t]+cost[t][j];
			}else if(dist[j]==dist[t]+g[t][j] && c[j]>c[t]+cost[t][j])
				c[j]=c[t]+cost[t][j];//最短距离相同看是否可以让c[v]更优 
			


        2、增加点权(每个城市能收集到物资),在最短路径多条时,要求路径点权之和最大(也可以是最小)。

        解题方法:weight[u]表示城市u中的物资数量(题目给出),并增加一个数组w[ ],令从起点s到达顶点u可以收集到的最大物资为w[u],初始化时w[s]=weight[s],其余w[u]=0;更新最短路径时,更新w[u],如果最短路径长度相同,取最大者。

int weight[N],w[N];//weight[i]:i点的物资 w[i]:起点->i的最大物资量 

	memset(w,0,sizeof w);//物资初始化为0 
	w[u]=weight[u];//起点物资 
	
		
			for(int j=1;j<=n;j++)
			{
				if(dist[j]>dist[t]+g[t][j])
				{
					dist[j]=dist[t]+g[t][j];
					w[j]=w[t]+weight[j];//更新同时更新物资 
				}
				else if(dist[j]==dist[t]+g[t][j] && w[j]

        3、求出有多少条最短路径。

         解题方案:增加一个数组num[ ],令从起点s到达顶点u的最短路径条数为num[u],初始化时num[s]为1,其余num[ ]为0,更新dist[j]时,只需要让num[j]继承num[t]即可,最短距离相同时,要累加。

int num[N];//num[i]: 起点到i的最短路径条数 

	memset(num,0,sizeof num);//路径为0 
	num[u]=1;//起点一条路径 自环 
	
		
			for(int j=1;j<=n;j++)
			{
				if(dist[j]>dist[t]+g[t][j])
				{
					dist[j]=dist[t]+g[t][j];
					num[j]=num[t];//更新同时继承路径条数 
				}
				else if(dist[j]==dist[t]+g[t][j])
					num[j]+=num[t];//最短路径长度相同,累加num 
			}	
			
			
		 

题目详情 - 1003 Emergency (25 分) (pintia.cn)

//城市互通 无向图!!!! 一开始看成有向图做的
#include
#include
using namespace std;
const int N=510;
int g[N][N];
int weight[N],w[N],num[N],dist[N];
int n,m,c1,c2;
bool st[N];

void dijkstra(int u)
{
    memset(dist,0x3f,sizeof dist);
    memset(w,0,sizeof w);
    memset(num,0,sizeof num);
    w[u]=weight[u];
    num[u]=1;
    dist[u]=0;
    
    for(int i=0;idist[j]))
                t=j;
        
        st[t]=true;
        
        for(int j=0;jdist[t]+g[t][j])
            {
                dist[j]=dist[t]+g[t][j];
                num[j]=num[t];
                w[j]=w[t]+weight[j];
            }
            else if(dist[j]==dist[t]+g[t][j])
            {
                num[j]+=num[t];
                if(w[j]

 题目详情 - 1030 Travel Plan (30 分) (pintia.cn)

#include
#include
#include
using namespace std;

const int N=510;
int n,m,s,d;
int g[N][N],dist[N];
int cost[N][N],c[N],pre[N];
bool st[N];
vector path;

void dijkstra(int u)
{
    memset(dist,0x3f,sizeof dist);
    memset(c,0x3f,sizeof c);
    dist[u]=0;
    c[u]=0;
    for(int i=0;idist[j]))
                t=j;
        
        st[t]=true;
        
        for(int j=0;jdist[t]+g[t][j])
            {
                dist[j]=dist[t]+g[t][j];
                pre[j]=t;
                c[j]=c[t]+cost[t][j];
            }
            else if(dist[j]==dist[t]+g[t][j] && c[j]>c[t]+cost[t][j])
            {
                c[j]=c[t]+cost[t][j];
                pre[j]=t;
            }
        }
    }
}

int main()
{
    scanf("%d%d%d%d",&n,&m,&s,&d);
    memset(g,0x3f,sizeof g);
    
    while(m--)
    {
        int a,b,dis,c;
        scanf("%d%d%d%d",&a,&b,&dis,&c);
        if(g[a][b]>dis)
        {
            g[a][b]=g[b][a]=dis;
            cost[a][b]=cost[b][a]=c;
        }
        
    }
    
    dijkstra(s);
    
    int t=d;
    while(t!=s)
    {
        path.push_back(t);
        t=pre[t];
    }
    path.push_back(s);
    
    for(int i=path.size()-1;i>=0;i--) printf("%d ",path[i]);
    
    printf("%d %d",dist[d],c[d]);
    return 0;
}

二、堆优化Dijkstra

        整体思想:堆优化,就是将寻找最近的未确定的点的时间复杂度变为O(1)。用邻接表来存储稀疏图,用优先队列来放堆。每次取出堆顶,然后去更新堆顶的所有出边,更新过的放入队列中。

//堆优化Dijkstra
typedef pair PII;

int n;//点数 
int h[N],w[N],e[N],ne[N],idx;//邻接表的存储 w边的权重 
int dist[N];
bool st[N];

void dijkstra(int u)
{
	memset(dist,0x3f,sizeof dist);
	dist[u]=0;
	priority_queue,greater> heap; 
	heap.push({0,u});
	
	while(heap.size())//堆不空 
	{
		auto t=heap.top();//取出堆顶 
		heap.pop();
		
		int ver=t.second,distance=t.first;
		
		if(st[ver]) continue;
		//一个点可能有多个点到达,但是如果已确认最短路径,就不需要后续再去冗余 
		st[ver]=true;//堆顶元素 最短路径已确定 
		
		for(int i=h[ver];i!=-1;i=ne[i])//遍历所有出边 
		{
			int j=e[ver];
			if(dis[j]>dis[ver]+w[i])//更新距离 
			{
				dis[j]=distance+w[i];
				heap.push({dist[j],j});//放入堆中 
			}
				
		}
	}
}

850. Dijkstra求最短路 II - AcWing题库

#include
#include
#include
using namespace std;

typedef pair PII;
const int N=1.5e5+10;
int n,m;
int h[N],w[N],ne[N],idx,e[N];
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++;
}

void dijkstra(int u)
{
    memset(dist,0x3f,sizeof dist);
    dist[u]=0;
    
    priority_queue,greater> heap;
    heap.push({0,u});
    
    while(heap.size())
    {
        auto t=heap.top();
        heap.pop();
        
        int ver=t.second,distance=t.first;
        
        if(st[ver]) continue;  
        st[ver]=true;
        
        for(int i=h[ver];i!=-1;i=ne[i])
        {
            int j=e[i];
            if(dist[j]>distance+w[i])
            {
                dist[j]=distance+w[i];
                heap.push({dist[j],j});
            }
        }
    }
    
}

int main()
{
    scanf("%d%d",&n,&m);
    memset(h,-1,sizeof h);
    while(m--)
    {
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        add(a,b,c);
    }
    
    dijkstra(1);
    
    if(dist[n]==0x3f3f3f3f) printf("-1\n");
    else printf("%d\n",dist[n]);
    return 0;
}

三、Bellman_Ford算法
       

        整体思想:两层循环,外层循环k次(k条边),内层每次遍历所有的边,并进行更新迭代。

        如果外层循环第n次还有更新的话,说明存在负权回路,因为n条边,至少有n+1个点,题目中只有n个点,那么必定有两个点是相同的,那么一定存在回路。

//Bellman_Ford acwing
int n,m;
int dist[N];

struct edge{
	int a,b,w;
}edge[M];//a->b的权重w 

int Bellman_ford(int u)
{
	memset(dist,0x3f,sizeof dist);
	dist[u]=0;
	
	for(int i=0;i0x3f3f3f3f/2) return -1;
	else return dist[n];
}

算法笔记Bellman_Ford算法模板 

//算法笔记模板
int n,m;
int dist[N];

struct edge{
	int a,b,w;
}edge[M];//a->b的权重w 

bool Bellman_ford(int u)
{
	memset(dist,0x3f,sizeof dist);
	dist[u]=0;
	
	for(int i=0;idist[e.a]+e.w))//如果还可以被松弛 
				return false;//说明图中有源点可达的负环 
		}
	return true; 
}

853. 有边数限制的最短路 - AcWing题库

#include
#include
using namespace std;
const int N=510,M=10010;

int dist[N],n,m,k,backup[N];//backup 防止内循环遍历边的时候 发生串联
struct edge{
    int a,b,w;
}edge[M];

void Bellman_ford(int u)
{
    memset(dist,0x3f,sizeof dist);
    dist[u]=0;
    
    for(int i=0;i=0x3f3f3f3f/2) printf("impossible");
    // 例如 5号点和n号点 1号点都无法到达 但是在内循环 遍历边的时候 可能5号点就把n号点给更新了
    else printf("%d\n",dist[n]);
    return 0;
    
}

四、SPFA算法

        整体思想:Bellman_Ford算法每一次外层循环都需要,遍历所有的边,会有大量无意义的操作,我们可以用邻接表,存储每一个点的出边,将内存循环遍历所有边改成遍历所有出边。并且将更改的点放入队列中(只有点更改了,他的出边才有可能更改)。

SPFA模板-acwing

int n;      // 总点数
int h[N], w[N], e[N], ne[N], idx;       // 邻接表存储所有边
int dist[N];        // 存储每个点到1号点的最短距离
bool st[N];     // 存储每个点是否在队列中

// 求1号点到n号点的最短路距离,如果从1号点无法走到n号点则返回-1
int spfa()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;

    queue q;
    q.push(1);
    st[1] = true;

    while (q.size())
    {
        auto 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])     // 如果队列中已存在j,则不需要将j重复插入
                {
                    q.push(j);
                    st[j] = true;
                }
            }
        }
    }

    if (dist[n] == 0x3f3f3f3f) return -1;
    return dist[n];
}

 851. spfa求最短路 - AcWing题库

//spfa 运用队列优化Bellman_Ford算法 发生距离更改的就入队 直到队空
#include
#include
#include
using namespace std;

const int N=1e5+10;
int h[N],ne[N],e[N],w[N],idx;
int n,m;
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++;
}

void spfa(int u)
{
    memset(dist,0x3f,sizeof dist);
    dist[u]=0;
    queue q;
    q.push(u);
    st[u]=true;//st[]防止重复放入队列 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;
                }
                
            }
        }
    }
}

int main()
{
    scanf("%d%d",&n,&m);
    memset(h,-1,sizeof h);
    
    for(int i=0;i=0x3f3f3f3f/2) printf("impossible\n");
    else printf("%d\n",dist[n]);
    return 0;
}

 五、SPFA算法判断是否存在环!

        如果点的边数>=点数,那么一定存在回环。

        原因:抽屉原理,如果有n条边,那么至少有n+1个点,题目中只有n个点,那么必定有两个点相同,那么一定会存在环。

        其实就是用cnt数组来存储边数,然后对边数更新,判断。

//ACwing SPFA模板
int n;      // 总点数
int h[N], w[N], e[N], ne[N], idx;       // 邻接表存储所有边
int dist[N], cnt[N];        // dist[x]存储1号点到x的最短距离,cnt[x]存储1到x的最短路中经过的点数
bool st[N];     // 存储每个点是否在队列中

// 如果存在负环,则返回true,否则返回false。
bool spfa()
{
    // 不需要初始化dist数组
    // 原理:如果某条最短路径上有n个点(除了自己),那么加上自己之后一共有n+1个点,由抽屉原理一定有两个点相同,所以存在环。

    queue q;
    //题目问存在负环 并没有说从具体某个点出发存在负环,所以把所有点都放到队列中
    for (int i = 1; i <= n; i ++ )
    {
        q.push(i);
        st[i] = true;
    }

    while (q.size())
    {
        auto 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;       // 如果从1号点到x的最短路中包含至少n个点(不包括自己),则说明存在环
                if (!st[j])
                {
                    q.push(j);
                    st[j] = true;
                }
            }
        }
    }

    return false;
}

852. spfa判断负环 - AcWing题库

#include
#include
#include
using namespace std;
 
const int N=1e6+10;
int h[N],ne[N],w[N],idx,e[N];//w[i] i点的权值
int n,m;
int dis[N],cnt[N];//dis[i] 1->i 的长度 cnt[] 边数
bool st[N];//st[i] i点是否访问
 
void add(int a,int b,int c)
{
    e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
 
bool spfa()
{
    queue q;
    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(dis[j]>dis[t]+w[i])//更新
            {
                dis[j]=dis[t]+w[i];
                cnt[j]=cnt[t]+1;
                if(cnt[j]>=n) return true;//边数大于点数 一定存在回环
                if(!st[j])//点放入队列
                {
                    q.push(j);
                    st[j]=true;
                }
            }
        }
    }
    return false;
}
 
int main()
{
    scanf("%d%d",&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;
}

 六、Floyd算法

        整体思想:如果存在顶点k,使得k作为中介点时,顶点i和顶点j的当前最短距离缩短。

        三层循环,最外层时枚举终结点k,里层循环分别i,j。

        

初始化:
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= n; j ++ )
            if (i == j) d[i][j] = 0;
            else d[i][j] = INF;

// 算法结束后,d[a][b]表示a到b的最短距离
void floyd()
{
    for (int k = 1; k <= n; k ++ )
        for (int i = 1; i <= n; i ++ )
            for (int j = 1; j <= n; j ++ )
                d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
}

作者:yxc
链接:https://www.acwing.com/blog/content/405/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

854. Floyd求最短路 - AcWing题库

#include
using namespace std;
const int N=210,INF=1e9;

int n,m,Q;
int d[N][N];

void floyd()
{
    for(int k=1;k<=n;k++)//k循环中介点
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
}

int main()
{
    scanf("%d%d%d",&n,&m,&Q);
    
    for(int i=1;i<=n;i++)//初始化d[i][j] i->j的长度
        for(int j=1;j<=n;j++)
            if(i==j) d[i][j]=0;
            else d[i][j]=INF;
            
    for(int i=0;i=INF/2) puts("impossible");
        else printf("%d\n",d[x][y]);
    }
    
    return 0;
}

你可能感兴趣的:(AcWing题解,算法,蓝桥杯,c++,数据结构,图论)