【图论】最短路

最短路最常用的算法有:

   单源最短路:      Bellman-Ford 算法,Dijkstra 算法,SPFA 算法。

   任意两点间最短路:Floyd算法。

Bellman-Ford 可以处理有负边的情况,也可以处理负圈。最多进行V - 1次迭代操作,如果第V次还进行更新操作,说明存在负圈。

Dijkstra 不仅不能处理负圈,连有负边都不能。

Floyd 也可以处理负边的情况。判断负圈只需检查最dp[i][i]是否存在负数的顶点i即可。

SPFA 可以处理负边的情况,是Bellman-Ford的一种队列实现。判断负圈只需判断顶点入队列次数,超过V次就存在负圈。

Bellman-Ford 算法:时间复杂度:O(VE)

模板:

const int INF = 0x3f3f3f3f;
int d[MAX_V];//最大顶点数,设置为题目中顶点数的最大值
int V, E;//顶点数;边数
struct Edge{
	int from;
	int to;
	int cost;
     Edge(int _from = 0, int _to = 0, int _cost = 0) : from(_from), to(_to), cost(_cost){}  } Edge es[MAX_E]; bool bellman_ford(int s){//true:有负圈 false:没有负圈 for(int i = 1; i <= V; ++i){//定点编号从1开始 d[i] = INF; } d[s] = 0;//d[i]为从s到i的最短路 for(int i = 1; i < V; ++i){//最多做V-1次 bool flag = false; for(int j = 0; j < E; ++j){ Edge e = es[i]; if(d[e.to] > d[e.from] + e.cost){ d[e.to] = d[e.from] + e.cost; flag = true; } } if(!flag) return false; } for(int i = 0; i < E; ++i){ Edge e = es[i]; if(d[e.to] > d[e.from] + e.cost) return true; } return false; }

  

Dijkstra 算法:O(V2)或O(ElogV)

未优化:O(V2)

const int INF = 0x3f3f3f3f;
int cost[MAX_V][MAX_V];
bool used[MAX_V];//使用过的顶点
int d[MAX_V];//最大顶点数,设置为题目中顶点数的最大值
int prev[MAX_V];//最短路上的前驱顶点
int V;//顶点数

void dijkstra(int s){
	for(int i = 0; i < V; ++i){
		d[i] = INF;
		used[i] = false;
		pre[i] = -1;
	}
	d[s] = 0;
	for(int i = 1; i < V; ++i){//最多走
		int k = -1;
		for(int j = 0; j < V; ++j){
			if(!used[j] && d[j] < d[k]){
				k = j;
} } if(k == -1) break; used[k] = true; for(int j = 0; j < V; ++j){ if(d[j] > d[k] + cost[k][j]){ d[j] = d[k] + cost[k][j]; prev[j] = k; } } } }

  

 堆优化:O(ElogV)

const int INF = 0x3f3f3f3f;
int d[MAX_V];//最大顶点数,设置为题目中顶点数的最大值
int V;//顶点数
struct Edge{
	int to;
	int cost;
	Edge(int _v = 0, int _cost = 0) : v(_v), cost(_cost){}
}
typedef pair<int, int> P;
vector<Edge> G[MAX_V];

void dijkstra(int s){
	priority_queue<P, vector<P>, greater<P>> que;
	for(int i = 0; i < V; ++i){
		d[i] = INF;
	}
	d[s] = 0;
	que.push(P(0, s));
	while(!que.empty()){
		P p = que.top();
		que.pop();
		int v = p.second;
		if(d[v] < p.first) 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)); } } } } void addEdge(int s, int t, int cost){ G[s].push_back(Edge(t, cost)); }

  

Floyd 算法:O(V3)

  Floyd实质上为dp问题。假设dp[i][j]为i与j之间的最短路,然后依次取集合中剩余点k,比较dp[i][j]和dp[i][k] + dp[k][j],即最短路要么不经过点k,要么经过点k。得到递推式:dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j])。这是一个O(n3)时间复杂度的算法。

模板:

const int INF = 0x3f3f3f3f;//巧妙设置无穷大值
int dp[MAX_V][MAX_V];//邻接矩阵表 int V;//顶点数 void floyd(){ for(int k = 0; k < V; ++k){ for(int i = 0; i < V; ++i){ for(int j = 0; j < V; ++j){ dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j]); } } } }

  

SPFA 算法:O(KE) K为所有顶点进入队列的平均次数,一般小于等于2

const int INF = 0x3f3f3f3f;
struct Edge{
	int to;
	int cost;
	Edge(int _v = 0, int _cost = 0) : v(_v), cost(_cost){}
}
vector<Edge> G[MAX_V];
int d[MAX_V];//最大顶点数,设置为题目中顶点数的最大值
bool used[MAX_V];
int cnt[MAX_V];
int pre[MAX_V]; int V;//顶点数 bool SPFA(int s){ for(int i = 0; i < V; ++i){ d[i] = INF; cnt[i] = 0; used[i] = false;
pre[i] = -1; } d[s] = 0; cnt[i] = 1;
pre[i] = s; used[s] = true; queue<int> que; que.push(s); while(!que.empty()){ int v = que.front(); que.pop(); used[v] = false; 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;
pre[e.to] = v; if(!used[v]){ used[v] = true; que.push(v); if(++cnt[v] >= V)//入队列次数超过顶点数,则存在负圈 return true; } } } } return false; } void addEdge(int s, int t, int cost){ G[s].push_back(Edge(t, cost)); }

  

 

posted @ 2017-10-11 22:29  Vincent丶丶  阅读(263)  评论(0编辑  收藏  举报