图论——拓扑排序及最短路径算法模板

在这里插入图片描述

一、拓扑排序

// 将入度为0的点写入myQueue
vector<int> ans;
queue<int> myQueue;
vector<int> G[N]; // 边
int d[N];	//入度
...
...
int n, m; // 节点个人、边的个数
void init() {
	for(int i = 1; i <= m; ++i) { 
		cin >> x >> y;
		G[x].emplace_back(y);
		++d[y];
	}
}
bool topSort(){
//ans中存储的是拓扑排序的结果
	for(int i = 1; i <= n; ++i) {
		if(d[i] == 0) {
			myQueue.push(i);
			ans.push_back(i);
		}
	}
	while(myQueue.size()){
		auto t = myQueue.front();
		myQueue.pop();
		for(auto next : G[t]) {
			if(--d[next] == 0) {
				myQueue.push(next);
				ans.push_back(next);
			}
		}
	}
	
	return ans.size() == n;
}

二、多源最短路

1. floyd O(n3)

// 节点 1 ~ n 
void floyd(){
	for(int k = 1; k <= n; ++k)
		for(int i = 1; i <= n; ++i)
			for(int j = 1; j <= n; ++ j)
				dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j]);
}

三、单源最短路

1. Dijkstra朴素版 O(n2) —— 权值为正

贪心的思想,每次都从集合中选取距离 1 即源节点距离最短的点,通过这个点来更新所有点到源点距离,同时会有vis来判重。

void dijkstra(){
	memset(dis, 0x3f, sizeof dis);
	dis[1] = 0;
	for(int i = 0; i < n; ++i){
		int t = -1;
		for(int j = 1; j <= n; ++j)
			if(!vis[j] && (t == -1 || dis[t] > dis[j]))
				t = j;
		vis[t] = true;
		for(int j = 1; j <= n; ++j)
			dis[j] = min(dis[j], dis[t] + g[t][j]);
	}
}

2. Dijkstra堆优化版 O(mlogn) —— 权值为正

基于上面朴素版,利用优先队列来替代遍历过程

// typedef pair<int, int> PII;
void dijkstra(){
	memset(dis, 0x3f, sizeof dis);
	priority_queue<PII, vector<PII>, greater<PII>> heap;
	dis[1] = 0;
	heap.push({0, 1});
	while(heap.size()){
		auto t = heap.top();
		heap.pop();
		int curId = t.second, curDis = t.first;
		if(vis[curId])	continue;
		vis[curId] = true;
		for(auto next : G[curId]){
			int nextId = next.second, distance = next.x;
			if(dis[nextId] > dis[curId] + distance){
				dis[nextId] = dis[curId] + distance;
				heap.push({dis[nextId, nextId});
			}
		}
	}
}

3. Bellman-ford O(nm)—— 权值可为负

struct Edge {
	int from, to, w;
	bool operator < (const Edge &t) const {
		return w < t.w;
	}
}edges[N];

void bellmanFord() {
	memset(dis, 0x3f, sizeof dis);
	dis[1] = 0;
	for(int i = 0; i < k; ++i) {//可通过这个k控制选择的边的条数
		memcpy(backup, dis, sizeof dis);
//需要用备份数组,因为在更新时如果不用备份则会用到当前i这一轮的更新结果
//而实际上应该用i-1轮的dis结果去更新
		for(int i = 1; i <= m; ++i) {// m为边数
			int from = edges[i].from, to = edges[i].to, w = edges[i].w;
			dis[to] = min(dis[to], backup[from] + w);
		}
	}
}

4. SPFA O(m) 最坏O(nm)—— 权值可为负

只要没有负环就可以用,也可以用来判断有没有负环
一、思想:更新过谁,就拿谁更新别人
 1. 对bellman-ford算法的优化:用队列、BFS将变小了的dis[from]压入队列中,因为只有dis[from]变小了,dis[to]才有机会更新。所以将变小了的点压入其中。
    (d[to] = min(d[to], backup[from] + w);
 2. 需要借助用st[]数组判断该点是否在队列中,避免重复加入
 3. 一般情况下,dijkstra也可以用 SPFA 如果不行的话就用回dijkstra
 4. 当需要用于判断是否存在负环时,加一个cnt数组:在更新dis中一旦cnt[j] > n 则有负环
二、SPFA算法的优化:
   1. SLF优化 如果dis[nextId]在更新后 
   		< deque.front() 则从push_front队头加入
      > deque.front() 则从push_back队尾加入
     [SLF优化题目](https://www.acwing.com/file_system/file/content/whole/index/content/4428753/)

void spfa(){
	memset(dis, 0x3f, sizeof dis);
	dis[1] = 0, st[1] = true;
	queue<int> myQueue;
	/*如果需要判断负环的话,还需要将所有节点都写入myQueue
		如果不写入所有节点,则只能判断从1开始的负环
		for(int i = 1; i <= n; ++i){
			myQueue.push(i), st[i] = true;
		}
	*/
	myQueue.push(1);
	while(myQueue.size()){
		auto t = myQueue.front();
		myQueue.pop();
		st[t] = false;
		for(auto next : G[t]) {
			int nextId = next.y, distance = next.x;
			if(dis[nextId] > dis[t] + distance) {
				dis[nextId] = dis[t] + distance;
				/*如果需要判断负环
				cnt[nextId] = cnt[t] + 1;
				if(cnt[nextId] > n)	return false;
				*/
				if(!st[nextId]) {
					st[nextId] = true;
					myQueue.push({nextId, dis[nextId]});
				}
			}
		}
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值