Loading

最短路回顾

准备开图论,复习一下基本算法。

1. 最短路算法

\(V\) : 点集

\(E\) : 边集

Floyd

洛谷模板

应该从动态规划的角度去考虑这个算法,设 f[i][j] 表示从 ij最短路径 (指某条路,而不是最小距离这样一个数字,距离是路径的一个属性)。

其实所有的最短路算法都应该落实到路径上

则很显然,有如下转移

\[f[i][j] = min(f[i][j],f[i][k] + f[k][j]) \]

这个 \(+\)理解成路径的拼接,而 min 表示按数值比较

复杂度分析

对于每一个f[i][j] 都需要 1 <= k <= n 去更新

\(O(V^3)\)

抽象出来

struct node {
	vector<int>path;
	int dis;
	bool operator < (const node& b)const {
		return dis < b.dis;
	}
	node operator + (node b) {
		node tmp = *this;
		tmp.path.insert(tmp.path.end(), b.path.begin(), b.path.end());
		tmp.dis += b.dis;
		return tmp;
	}
}f[N][N];

实例代码

/*
 * @Author: zhl
 * @Date: 2020-10-19 18:31:45
 */
#include<bits/stdc++.h>
using namespace std;
const int N = 5e2 + 10;

struct node {
	vector<int>path;
	int dis;
	bool operator < (const node& b)const {
		return dis < b.dis;
	}
	node operator + (node b) {
		node tmp = *this;
		tmp.path.insert(tmp.path.end(), b.path.begin(), b.path.end());
		tmp.dis += b.dis;
		return tmp;
	}
}f[N][N];

int n, m;
int main() {
	cin >> n >> m;
	for (int i = 1; i <= n; i++)for (int j = 1; j <= n; j++)f[i][j] = node{ vector<int>(),0x3f3f3f3f };
	for (int i = 1; i <= n; i++)f[i][i] = node{ vector<int>(),0 };
	for (int i = 1; i <= m; i++) {
		int a, b, c;
		cin >> a >> b >> c;
		//sb题目会有重边
		if (c < f[a][b].dis)f[a][b] = node{ vector<int>(1,b),c };
		if (c < f[b][a].dis)f[b][a] = node{ vector<int>(1,a),c };
	}
	for (int k = 1; k <= n; k++) {
		for (int i = 1; i <= n; i++) {
			for (int j = 1; j <= n; j++) {
				if (f[i][k] + f[k][j] < f[i][j]) {
					f[i][j] = f[i][k] + f[k][j];
				}
			}
		}
	}

	int q;
	cin >> q;
	while (q--) {
		int a, b;
		cin >> a >> b;
		cout << "Min_Dis : " << f[a][b].dis << endl;
		cout << a;
		for (int i : f[a][b].path)cout << "->" << i;
		cout << endl;
	}
}

做题直接存数值就完事了

Bellman-ford

\(O(VE)\)

用边去进行松弛操作

Floyd 不同,Bellman-ford 处理的是 ‘单源最短路’ 。处理的是从源点 s 出发到任意一点 t 的最短路径

对于一条边 E[i] ,该边连接 a , b ,边权是 w

f[b] = min(f[b],f[a] + w)

n-1 次,每次都遍历所有的边进行松弛

再跑一次,如果松弛成功,则说明有负环。

n 个点的图上的路径最多 n-1 条边

代码懒得写了(跑

Dijkstra

时间复杂度 \(O(VlgV)\) , 一般正权图用 Dij就完事

struct Node{
    double d;
    int id;
    bool operator < (const Node& b)const{
        return d > b.d;
    }
};
int n,m,s,t;
void Dijkstra(){
    rep(i,0,n){
        dis[i] = INF;
        vis[i] = 0;
    }
    dis[s] = 0;
    priority_queue<Node>Q;
    Q.push(Node{0,s});
    
    while(!Q.empty()){
        Node tp = Q.top();Q.pop();
        int u = tp.id;
        double d = tp.d;
        if(vis[u])continue;
        vis[u] = 1;
        repE(i,u){
            int v = E[i].to;
            if(dis[v] > d + E[i].x + E[i].y * A){
                dis[v] = d + E[i].x + E[i].y * A;
                Q.push(Node{dis[v],v});
            }
        }
    }
}

三种状态

  1. vis[u] == True ,表示 su 的最短路径已经被处理出来

  2. 在队列中,表示该点目前可达但是不确定是否已经是最短路径

  3. 目前不可达

每次取队列中距离最短的那个点,给他打上标记。

因为这个点必定已经是最短路径。

因为 u是目前所有可达的点中距离最短的一个,若还有另一条更短的路径,则肯定有一条路径 s->tt->us->t 的最短距离必定小于目前 u 的最短路径,且 t 必定会在队列中(这里想的也不是特别清楚,好像不是很严谨)

需要注意的是,这里的 visBFS 中的标记 vis 不一样

BFS 中需要入队的时候打上标记,不能取队首的时候打标记,否则会多次入队

而这里的 vis 表示的是已经处理完毕

SPFA

一种对 Bellman-ford 的优化

很显然 Bellman-ford 中做了很多次无用的松弛,例如第一次的时候只需要松弛和源点相连的边就可以。

每一个点入队,就表示有一个点的 dis 下降了,而在无负环的图中,必然存在最短路径。所以该算法一定能在有限次的执行后停止。

复杂度 : \(O(kV)\)\(k\) 是平均每个点的入队次数

在极端条件下可以被卡成朴素 bellman-ford \(O(EV)\)

void spfa() {
	queue<int> q;
	memset(dis, 0x3f, sizeof(int) * (n + 10));
	memset(vis, 0, sizeof(int) * (n + 10));

	q.push(s); dis[s] = 0; vis[s] = 1; //第一个顶点入队,进行标记
	while (!q.empty()) {
		int u = q.front();
		q.pop(); vis[u] = 0;
		for (int i = head[u]; ~i; i = E[i].next) {
			int v = E[i].to;
			if (dis[v] > dis[u] + E[i].w) {
				dis[v] = dis[u] + E[i].w;
				if (vis[v] == 0) {
                    //此处判环
                    //if(++cnt[v] >= n) 有负环
					vis[v] = 1;
					q.push(v);
				}
			}
		}
	}
}
posted @ 2020-10-19 19:50  —O0oO-  阅读(100)  评论(0编辑  收藏  举报