最短路径

最短路

    在图上求最短路径是一类非常常见的问题,根据源点的数目可以分为 单源最短路径和所有点最短路径;根据边的权值可以分为无负权值边最短路径和有负权值边最短路径。

常用算法

    求图中最短路径的方法主要有: 
Dijkstra算法: 求单源、无负权值边最短路径 
BellmanFord算法:求单源、有负权值边,无负权值环最短路径 
SPFA算法:求单源、有负权值边,无负权值环最短路径 
Floyd算法:求所有点之间的最短路径

Dijkstra算法 
只能求单源、非负权边图的最短路径。 
    贪心的思想,将所有的顶点按照是否找到从源点到该点的最短路径划分为两个集合V(已经确定从源点到该点的最短路径的集合)和S-V(没有确定从源点到该点的最短路的集合)。每次从S-V集合中取出距离源点source最近的一个点x加入集合V,同时根据source-x的最短路径和x到S-V中点的路径和来更新源点source到S-V集合中的点的最短路径。 
    用优先队列进行优化,时间复杂度为O(ElogV)。实现如下:

struct Edge{			//边数据结构
	int vertex;			//该边相邻的点
	int dist;			//边的长度
	Edge(int v, int d) :
		vertex(v), dist(d){};
};
vector<vector<Edge> > gEdges;

struct Compare{	//用于priority_queue的比较
	bool operator()(const Edge& e1, const Edge& e2){
		return e1.dist > e2.dist;
	}
};

int gDist[1005];		//存储源点到每个点的最短距离
bool gVisited[1005];	//判断是否已经求出源点到该点的最短距离,即是否位于集合V中

//dijkstra算法
int Dijkstra(int source, int dst, int n){
	priority_queue<Edge, vector<Edge>, Compare> pq;
	Edge e(source, 0);
	for (int i = 0; i <= n; i++){
		gDist[i] = INFINITE;
		gVisited[i] = false;
	}
	gDist[source] = 0;
	pq.push(e);

	while (!pq.empty()){
		e = pq.top();			//优先队列,取出S-V集合中距离源点最近的点
		pq.pop();
		if (gVisited[e.vertex])	//点已经位于V集合中
			continue;
		gVisited[e.vertex] = true;

		for (int i = 0; i < gEdges[e.vertex].size(); i++){ //更新相邻的点
			Edge& ee = gEdges[e.vertex][i];
			if (gDist[ee.vertex] > ee.dist + gDist[e.vertex]){
				gDist[ee.vertex] = ee.dist + gDist[e.vertex];
				pq.push(Edge(ee.vertex, gDist[ee.vertex]));
			}
		}
	}
	return gDist[dst];
}

 


BellmanFord 算法 
可求单源、含负权边的图的最短路径。 
    算法思想是进行N-1次循环,每次循环依次对每条边进行操作:若从源点到边终点的当前最短距离 gDist[d] 大于 从源点到边起点的当前最短距离 gDist[s] + dist(s->d),那么更新 gDist[d] = gDist[s] + dist(s-->d). 
    这样,第k次循环可以确定下来从源点到该点的最短路径上有k条边的点的最短路径。经过N-1次之后,可以得出从源点到该点最短路径经过1,2,...N-1条边的点的最短路径,这样就求出源点到所有点的最最短路径。 
    再第N次进行判断所有边的终点和始点的 gDist[d] ? gDist[s] + dist(s--->d),如果仍然能够继续更新,说明存在负权值环。

Bellman Ford vs Dijkstra 
    Dijkstra算法要求每条边都是非负数,而且是按照从源点到其他点的最短路径从小到大的顺序确定,这样确定了到某点A的最短路径之后,之后确定的点B肯定不会对该点A再产生影响,所以Dijkstra算法在每次循环时,都可以确定下来本次循环中距离源点最近的那个点的最短路径; 
    BellmanFord算法中边的权值可能为负数,这样在有向图中,确定了某点A的当前最短路径之后,之后再确定点B的最短路径,若从B到A有一条负权边,则A的最短路径可以继续更新....所以BellmanFord算法只有在N-1次循环结束之后,才可以确定源点到点的最短路径

SPFA算法——对Bellman Ford算法的改进 
    Bellman Ford 在第k次循环的时候,可以确定源点到这样一些点的最短路径:源点到这些点的最短路径上含有k条边。但是Bellman Ford算法在每次循环的时候遍历所有的边进行更新操作,而从源点到其中有些边关联的点的路径上边的数目可能大于K,那么更新这些边关联的点的当前最短路径并没有意义。 
    而SPFA算法在第k次循环的时候,只是选择从源点经过k条边可达的那些点来更新其当前最短路径,这些数据才是有效的。SPFA算法实现:

struct Edge{
	int vertex;
	int dist;
};
vector<vector<Edge> > gEdges;
int gUpdateTime[105];
double gCurrency[105];

bool Spfa(int source, int n, double init_value){
	queue<int> Q;
	Q.push(source);
	for (int i = 0; i <= n; i++){
		gDist[i] = 0;
		gUpdateTime[i] = 0;
	}
	while (!Q.empty()){
		int v = Q.front();
		Q.pop();
		
		for (int i = 0; i < gEdges[v].size(); i++){
			Edge& e = gEdges[v][i];

		    if(gDist[e.vertex] > gDist[v] + e.dist){
		        gDist[e.vertex] = gDist[v] + e.dist;
		        
				++gUpdateTime[e.vertex];
				if(gU[dateTime[e.vertex] > n){
				    //存在负权环
				}
				Q.push(e.vertex);
			}
		}
	}
	return false;
}

 


Floyd算法 
用于求所有点之间的最短路径。 
    采用区间动态规划的思想,从点i到点j的最短路径上可能经过1.2...N这些点,那么对任意两点最短路径之间经过的点的最大序号进行N次循环:第k次循环表示任意两点之间的当前最短路径上只能经过1.2...k 这些点,而不能经过k+1,k+2...N这些点。 
    那么,经过N次循环,可以求出任意两点之间中间可以经过1,2...N所有点的路径的最短路径,这即为任意两点之间真正的最短路径。 
    Floyd算法实现:

void Floyd(int n){
	for (int k = 1; k <= n; k++){
		for (int i = 1; i <= n; i++){
			for (int j = 1; j <= n; j++){
				if (gDist[i][j] > gDist[i][k] + gDist[k][j]){
					gDist[i][j] = gDist[i][k] + gDist[k][j];
				}
			}
		}
	}
}

 

posted @ 2015-10-10 14:12  农民伯伯-Coding  阅读(341)  评论(0编辑  收藏  举报