基本介绍
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的一个优化算法。