图论算法(一):最短路径

基本介绍

1736年,瑞士数学家Euler(欧拉)在他的一篇论文中讨论了哥尼斯(Knigsberg)七桥问题,由此诞生了一个全新的数学分支——图论(Graph Theory)。在经历了200多年的发展之后,图论已经积累了大量的理论和结果,其应用领域也逐步扩大。

1. 最短路径

1.1 Dijkstra算法

1. 基本思想
如果v0至u的最短路径经过v1,那么v0到v1的路径也是v0到v1的最短路径。

按路径长度递增次序,逐步产生最短路径。

Dijkstra算法的本质是贪心法。

2. 步骤
(1)首先求出v0为源点长度最短的一条最短路径,即具有最小权的边<v0, v>。

(2)求出源点到各个顶点下一个最短路径:设其终点是u,则v0到u的最短路径或者是边<v0, u>,或者由一条已求得的最短路径(v0···v)和边<v, u>构成。

(3)重复(2)直到从顶点v0到其他各顶点的最短路径去不求出为止。

3. 样例代码

//dijkstra
//the nodes numbered from 1 to n, s is the start point
//map[i][j] == -1 means no direct path from i to j
//dis == -1 means no path to get i from s

void dijkstra()
{
	int i, k, p, min;
	static bool cov[MAXN + 1];
	memset(dis, 255, sizeof(dis));
	memset((cov, 0, sizeof(cov));
	dis[s] = 0;
	path[s] = 0;
	for(k = 1; k < n; k++)
	{
		for(min = maxint, i = 1; i <= n; i++)
			if(!cov[i] && dis[i] >= 0 && dis[i] < min)
				p = i, min = dis[i];
			if(min >=maxint)
				break;
		for(cov[p] = 1, i = 1; i <=n; i++)
			if(!cov[i] && map[p][j] >= 0)
				if(min + map[p][i] < dis[i])
					dis[i] = min + map[p][i], path[i] = p;
	}		//for k
}		//dijkstra

4. 注意事项
Dijkstra算法要求图上的权是非负数,否则结果不正确。

Dijkstra算法同样适用于无向图,此时一个无向边次相当于两个有向边。

计算复杂度O(n2)。

1.2 Floyd算法

1. 基本思想
求解所有点间的路径需要进行n次试探,对于顶点i到顶点j的路径长度,首先考虑让路径经过顶点1,比较路径(i,j)和(i,1,j)的长度取其短者为当前求得的最短路径长度。

Floyd算法的本质是动态规划。递归方程如下:
dp[i][j][k] = min{dp[i][j][k -1], dp[i][k][k] + dp[k][j][k]}

特殊的有:当k = 0时,dp[i][j][0] = w(i, j),其中dp[i][j][k]表示从i到j经过k的最短路径。

2. 样例代码

//Floyd

void Floyd()
{
	for(int i = 0; i < sz; i++)
	{
		for(int j = 0; j < sz; j++)
			for(int k = 0; k < sz; k++) {
				int t_dis = distances[j][i] + distances[i][k];
				if(distances[j][k] > t_dis)
					distances[j][k] = t_dis;
			}
	}
}

3. 注意事项
Floyd可以处理负权边,但无法处理负环。

Floyd算法适用于无向图、有向图,此时一个无向边次相当于两个有向边。

求出全源最短路径的计算复杂度为O(n3)。

1.3 Bellman-Ford算法

1. 基本思想
它是最优性原理的直接应用,算法基于以下事实:
(1)如果最短路径存在,则每个顶点最多经过一次,因此不超过n - 1条边。

(2)长度为k的路径由长度为k - 1的路加一条边得到。

(3)由最优性原理,只需依次考虑长度为1,2,···,k - 1的最短路径。

2. 步骤
对每条边进行|V| - 1次Relax(松弛)操作。

如果存在(u,v)∈\in E使得dis[u] + w < dis[v],则存在负权回路;否则dis[v]即为s到v的最短距离,pre[v]为前驱。

Bellman-Ford实质上就是一个迭代处理过程。

3. 样例代码

//Bellman-Ford map[i][j] = =MaxInt means no direct path from i to j
void Bellman_Ford()
{
	bool notfinish = false;
	memset(checked, 0, sizeof(checked));
	checked[s] = true;
	for(k = 0; k <= n && !notfinish; k++) {
		notfinish = true;
		for(i = 0; i < n; i++) {
			if(checked[i]) {
				checked[i] = false;
				for(j = 0; j < n; j++) {
					if(dis[i] + map[i][j] < dis[j]) {
						dis[j] = dis[i] + map[i][j];
						checked[j] = true, notfinish = false;
					}
				}
			}
		}
	}
	return ;
}

4. 注意事项
Bellman-Ford可以处理负边、负环。当最外层循环结束但是迭代还没有结束时,说明图中存在负环。

Bellman-Ford算法适用于无向图,有向图,此时一个无向边次相当于两个人有向边。

计算复杂度为O(n2),如果使用邻接链表表示图可使复杂度降至O(E)。

SPFA算法是Bellman-Ford的一个优化算法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值