图论:拓扑排序讲解,以及 Dijkstra算法,Bellman-Ford算法,spfa算法,Floyd算法模板大全

🎬 博主名称个人主页

🔥 个人专栏 : 《算法通关》《Java讲解》

⛺️心简单,世界就简单

序言

今天讲一下拓扑排序,解决最短路问题的 Dijkstra算法,Bellman-Ford算法,spfa算法,Floyd算法

目录

序言

🐲拓扑排序

🐲最短路问题

🐔1,单源最短路

🥚[1,所有边权都是正数 ----- 朴素的Djkstra算法,堆优化的Dijkstra算法 2,存在](#1,所有边权都是正数 ----- 朴素的Djkstra算法,堆优化的Dijkstra算法 2,存在)🥚[负边权 ----- Bellman -Ford, SPFA](#负边权 ----- Bellman -Ford, SPFA)

🥚2,多源汇最短路

🥚Floyd算法

🐲[最短路问题 ----- 朴素Dijkstra算法](#最短路问题 ----- 朴素Dijkstra算法)

🐲[最短路问题 ----- 堆优化Dijkstra算法](#最短路问题 ----- 堆优化Dijkstra算法)

🐲Bellman-Ford算法

🐲spfa算法

🐔判断负环

🐲Floyd算法


拓扑排序

首先是针对有向图来说的,有向图才有拓扑序列,当然如果有向图里有环,那也就一定不存在拓扑排序,只有有向无环图,一定存在拓扑序列,也成为拓扑图。里面有两个名词,入度和出度

入度:就是别人指向你的路

出度:你指向别人的路

1 入度 :0; 1 出度 :2

2 入度 :1; 2 出度 :1

3 入度 :2; 3 出度 : 0


所有的边都是从前指向后的,所以当前入度为0的点都是可以作为起点的,入度为0,就意味着没有任何一条边指向我,没有点在我前面。所以我们先把入度为0的点都入队列,后面就是一个宽搜的过程,我们每一次取出队头t,枚举他的出边 j ,然后删掉 t 到 j 的边,其实就是让d[ j ] --这个就是j的入度,如果等于0了,就让他也入队

如果我们存在一个环,我们肯定不会有任何一个边都入不了队列 ,一个有向无环图,一定至少存在一个入度为0的点 如果我们沿着路径去遍历,发现找到了n + 1个节点,但我们总共就只有n个节点,那我们就肯定是多遍历了一个,就说明有环

下面这个代码就是相当于模板了,怎样判断是否有若谱序列,以及求出拓扑序列

cpp 复制代码
#include<iostream>
#include<cstring>
#include<algorithm>

using namespace std;

const int N = 1e5 + 10;

int n, m;
int h[N], e[N], ne[N], idx;
int q[N], d[N];

void add(int a, int b){
	e[idx] = b;
	ne[idx] = h[a];
	h[a] = idx ++;
}

bool topsort(){
	int hh =0, tt = -1;
	
	for(int i = 1; i <= n; i++){
		if(!d[i]){
			q[ ++tt] = i;
		}
	}
	while(hh <= tt){
		int t = q[hh ++];
		for(int i = h[t]; i != -1; i = ne[i]){
			int j = e[i];
			d[j] --;
			if(d[j] == 0) q[ ++tt] = j;
		}
	}
	//tt = n - 1说明所有的点都进入过队列了,也就说明有拓扑序列 
	return tt == n -1;
}

int main(){
	cin >> n >> m;
	
	memset(h, -1, sizeof h);
	
	for(int i = 0; i < m; i++){
		int a, b;
		cin >> a >> b;
		add(a, b);
		d[b] ++;
	}
	
	if(topsort()){
		//我们还可以发现出队的顺序就是拓扑序列
		for(int i = 0; i < n; i ++) {
			printf("%d ", q[i]);
		//	puts("");
		}
	} 
	
	else{
		puts("-1");
	}
}

最短路问题

1,单源最短路

1,所有边权都是正数 ----- 朴素的Djkstra算法,堆优化的Dijkstra算法

2,存在负边权 ----- Bellman -Ford, SPFA

2,多源汇最短路

Floyd算法


最短路问题 ----- 朴素Dijkstra算法

1,设初始化距离,dist数组,dist[ 1 ] = 0也就是1号点到起始点距离为0,其余点dist[ i ] = +∞

2,for(i = 0 ; i < n; i ++),现在有个集合s,里面放着当前已经确定了最短距离的点

1),我们现在循环中找到第一个不在s中的距离最近的点t

2),用t来更新其他点的距离,更新的方式是从 t 出去的所有边能都

cpp 复制代码
#include<iostream>
#include<cstring>
#include<algorithm>

using namespace std;

const int N = 510;

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

int dijkstra(){
	memset(dist, 0x3f, sizeof dist);
	dist[1] = 0; 
	for(int i = 0; i < n; i++){
		int t = -1;//当前走到的距离最近的点,等于-1时说明还没开始延申点 
		for(int j = 1; j <= n;j ++){
			//如果这个点没走过,并且t=-1或者当前有更短的路可以走另一个节点,就换t
			//这一步就是在找我们走哪个路比较近 
			if(!st[j] && (t == -1 || dist[t] > dist[j])){
			    t = j;
			}
		}
		st[t] = true;
		//迭代n次,然后每次就是从我们现在的最近的点t来找之后的路 
		for(int j = 1; j <= n; j++){
			dist[j] = min(dist[j], dist[t] + g[t][j]);
		}
	} 
	
	if(dist[n] == 0x3f3f3f3f) return -1;
	return dist[n];
	
	
}

int main(){
	scanf("%d%d", &n, &m);
	
	memset(g, 0x3f, sizeof g);
	
	while(m --){
		int a, b, c;
		scanf("%d%d%d", &a, &b, &c);
		g[a][b] = min(g[a][b], c);
	}
	int t = dijkstra();
	cout<<t;
	 
}

最短路问题 ----- 堆优化Dijkstra算法

我们朴素排序虽然看着简单,但是如果节点数到了1e4我们就包超时的,所以我们有了堆优化的方法

我们用一个堆来维护最短的距离和节点

cpp 复制代码
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue> 
using namespace std;

const int N = 3e5 + 10;

typedef pair<int, int> PII; 
int n, m;
int h[N], e[N], ne[N], idx, w[N];
int dist[N];
bool st[N];

void add(int a, int b, int c){
	e[idx] = b;
	ne[idx] = h[a];
	w[idx] = c;
	h[a] = idx ++;
	
}

int dijkstra(){
	memset(dist, 0x3f, sizeof dist);
	dist[1] = 0; 
	
	priority_queue<PII, vector<PII>, greater<PII> > heap;
	heap.push({0, 1});
	
	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});
			}
			
		}
	}
	if(dist[n] == 0x3f3f3f3f) return -1;
	return dist[n];
	
	
}

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);
	}
	int t = dijkstra();
	cout<<t;
	 
}

Bellman-Ford算法

这就比较暴力了,就直接把边存在一个结构体里,然后遍历,然后看题上有没有用限制边的个数,没的话就迭代 n 次,然后去找最短路,有限制,就迭代 k 条边,k次

具体看代码就行

cpp 复制代码
#include<iostream>
#include<cstring>
#include<algorithm>

using namespace std;

const int N = 510, M = 10010;

int n, m, k;
int dist[N], backup[N];//备份dist数组

struct Edge{
	int a, b, w;
}edges[M];

int Bellman_ford(){
	memset(dist, 0x3f, sizeof dist);
	dist[1] = 0;
	
	for(int i = 0; i < k; i++){
		memcpy(backup, dist, sizeof dist);
		for(int j = 0; j < m; j++){
			int a = edges[j].a, b =edges[j].b,w = edges[j].w;
			dist[b] = min(dist[b], backup[a] + w);
		}
	}
	//防止最后一个节点的的前一个边时负值 
	if(dist[n] > (0x3f3f3f3f / 2)) return -1;
	return dist[n];
}


int main(){
	scanf("%d%d%d", &n, &m, &k);
	
	for(int i = 0; i < m; i++){
		int a, b, w;
		scanf("%d%d%d", &a, &b, &w); 
		edges[i] = {a, b, w};
	}
	int t = Bellman_ford();
	
	if(t == -1) puts("impossible");
	else{
		printf("%d", t);
	}
}

spfa算法

这个有点类似dijkstra和bfs了,他这个是用一个队列,往里面放节点,用st来标记这个节点是否在队列里,然后我们就其实是按照bfs那样去遍历,但是bfs我们走过后就不再走了,但是用了队列后,就能让这个dist的数组进行更新,

cpp 复制代码
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;

const int N = 1e5 + 10;
int dist[N], h[N], e[N], ne[N], w[N], idx;
bool st[N];
int n, m;

void add(int a, int b, int c){
	e[idx] = b;
	w[idx] = c;
	ne[idx] = h[a];
	h[a] = idx ++;
}

int spfa(){
	memset(dist, 0x3f, sizeof dist);
	dist[1] = 0;
	queue<int> q;
	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(){
	cin >> n >> m;
	memset(h, -1, sizeof h);
	
	while(m --){
		int a, b, c;
		cin >> a >> b >> c;
		add(a, b, c);
	}
	int t = spfa();
if(t == 0x3f3f3f3f) puts("impossible");
else printf("%d\n", t);
}

判断负环

这有区别的,这里是假设有一个虚拟源点,他和所有节点都连的有一个边权为0的边,我们就把所有的点都扔进队列,还有就是dist数组不用初始化为无穷,因为这里我们的dist数组只是用来判断从 t 节点 到 j 距离会不会变小就行

cpp 复制代码
#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>

using namespace std;

const int N = 2010, M = 10010;

int n, m;
int h[N], w[M], e[M], ne[M], idx;
int dist[N], cnt[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 ++ ;
}

bool spfa()
{
    queue<int> q;
    
    // 步骤1:所有节点入队
    for (int i = 1; i <= n; i ++ )
    {
        st[i] = true;      // 标记节点i在队列中
        q.push(i);         // 节点i入队
    }
    // 此时队列中有所有n个节点
    
    while (q.size())
    {
        int t = q.front();  // 取出队首节点
        q.pop();            // 移除队首
        st[t] = false;      // 标记t不在队列中
        
        // 遍历节点t的所有邻边
        for (int i = h[t]; i != -1; i = ne[i])
        {
            int j = e[i];   // 邻居节点j
            int weight = w[i];  // 边权重
            
            // 核心判断:能否通过t到达j获得更短路径?
            if (dist[j] > dist[t] + weight)
            {
                // 更新j的距离
                dist[j] = dist[t] + weight;
                
                // 更新到达j的路径边数
                cnt[j] = cnt[t] + 1;
                
                // 关键判断:如果到达j的路径边数≥n
                if (cnt[j] >= n) 
                    return true;  // 检测到负环!
                
                // 如果j不在队列中,加入队列
                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算法

用邻接矩阵来存图d[ i ][ j ],然后三层循环,第一层k 从1 ~ n,第二层 i 从1 ~ n,第三层 j 从1 ~ n,从 i 这个点,只经过1到 k的这些中间点到达 j 这个节点,

cpp 复制代码
#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>

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 ++){
		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++)
	    for(int j = 1; j <=n; j++)
	       if(i == j) d[i][j] = 0;
	       else d[i][j] = INF;
	 while(m --){
	 	int a, b, w;
	 	scanf("%d%d%d", &a, &b, &w); 
	 	d[a][b] = min(d[a][b], w);
	 }
	 Floyd();
	 while(Q --){
	 	int a, b;
	 	cin >> a >> b;
	 	if(d[a][b] > INF / 2) cout<<"impossible"<<endl;
	 	else printf("%d\n", d[a][b]);
	 }
} 

主要其实还是讲了模板,不算是很细

相关推荐
爱学习的阿磊2 小时前
模板编译期排序算法
开发语言·c++·算法
皮卡蛋炒饭.2 小时前
动态规划-多重背包
数据结构·算法·动态规划
ZPC82102 小时前
opencv 实现图像拼接
人工智能·python·算法·机器人
爱学习的阿磊2 小时前
C++代码动态分析
开发语言·c++·算法
WWZZ20252 小时前
C++:STL(容器deque)
开发语言·c++·算法·大模型·具身智能
AI科技星2 小时前
加速运动正电荷产生加速度反向引力场的详细求导过程
人工智能·线性代数·算法·机器学习·矩阵·概率论
近津薪荼2 小时前
优选算法——双指针专题3(快慢双指针)
c++·学习·算法
shengli7222 小时前
C++与硬件交互编程
开发语言·c++·算法
tobias.b12 小时前
408真题解析-2010-7-数据结构-无向连通图
数据结构·算法·图论·计算机考研·408真题解析