前言:刚接触图,很多概念都比较模糊。学最短路的过程中,参考了很多博客,写这篇博客主要是想好好整理一下近期的学习。
在求最短路前,先用邻接矩阵A[MAXN][MAXN]来表示图中点和边的关系。其中MAXN表示无向图中点的个数,A[i][j]表示i和j两个点之间边的权值。如果i和j之间没有直接相连的边,那么权值为无穷大。
Dijkstra算法:
算法分析:
1、将图中的点分到两个集合S和S’, 其中集合S中的点均已经求得与终点V0的最短路。(初始S中只有V0点)
2、从S’中筛选出距离V0距离最近的点i。
3、借助点i对所有的点到V0的距离进行修正。
4、集合S’不为空时,继续执行第二步。
5、算法结束。
代码如下:
#define INF 65535
#define MAXN 10
int prev[MAXN]; //记录最短路前驱,方便路径输出
int dist[MAXN]; //记录最短路
int A[MAXN][MAXN]; //邻接表
void Dijkstra(int V0)
{
int n = MAXN;
int s[MAXN];
//初始化
for(int i = 1; i <= n; i ++)
{
dist[i] = A[V0][i];
s[i] = 0;
if(dist[i] != INF)
{
prev[i] = V0;
}
else
{
prev[i] = -1;
}
}
//以V0为s集合中第一个元素,执行步骤二。
dist[V0] = 0;
s[V0] = 1;
for(int i = 2; i <= n; i ++)
{
int Min = INF;
int u = V0;
for(int j = 1; j <= n; j ++)
{
if(!s[j] && dist[j] < Min)
{
Min = dist[j];
u = j;
}
}
//将u加入新的集合中后,进行所有点的修正。
s[u] = 1;
for(int j = 1; j <= n; j ++)
{
if(!s[j] && A[u][j] != INF)
{
if(dist[u] + A[u][j] < dist[j])
{
dist[j] = dist[u] + A[u][j];
pre[j] = u;
}
}
}
}
}
Floyd算法:
时间复杂度0(n^3)、动态规划、比较经典。
算法分析:
首先要明确的是从任意节点i到达任意节点j,有两种方式。
第一种是直接到达。
第二种是通过中间的某种路径,间接到达。
那么要求a到b的最短路,必定是这两种方式中的一种。
如果是第一种情况,那么dist[a][b] = A[a][b]。
如果是第二种情况,我们理解如果最短路经过k点,那么必定有dist[i][j] > dist[i][k] + dist[k][j]。
Floyd算法就是借助这一个想法来实现。通过把所有点全部遍历一遍,在遍历每个点u的时候。对所有的dist[][]进行修正,修正的前提就是dist[i][j] > dist[i][u] + dist[u][j],这样在所有点都遍历一遍后,得到的结果就是最短路。
代码如下:
#define INF 65535
#define MAXN 10
int path[MAXN][MAXN]; //记录路径。如path[i][j] = k,那么路径i……k……j。具体怎么输出,给读者思考。
int dist[MAXN]; //记录最短路
int A[MAXN][MAXN]; //邻接表
void Floyd()
{
int n = MAXN;
for(int i = 0; i < n; i ++)
{
for(int j = 0; j < n; j ++)
{
dist[i][j] = A[MAXN][MAXN];
}
}
for (int k = 0; k < n; k ++)
{
for (int i = 0; i < n; i ++)
{
for (int j = 0; j < n; j ++)
{
if(dist[i][k] + dist[k][j] < dist[i][j])
{
dist[i][j] = dist[i][k] + dist[k][j];
path[i][j] = k;
}
}
}
}
}
SPFA算法:
算法理解:
这个算法是Bellman-Ford的优化版,是通过队列来实现的。相比前两个算法,我对这一算法的理解比较模糊。但是通过对BFS(队列)的学习,我觉得比较相似。唯一不同的是,BFS的点只能进一次,而这个算法中,点可以多次进入队列,内部的松弛操作就和Dijkstra算法一样了。
至于这个算法本身为什么能够实现最短路,其实可以考虑和Dijkstra算法进行比较,Dijkstra首先是找到距离V0最近的点后进行松弛操作(就是前面说的修正),而SPFA是找到“松弛”后受到影响的点,然后存入队列。直到队列为空,松弛就全部完成。最短路就得到了,似乎有种贪心的意味在里面。如果深入追究其原因,我恐怕也说不出所以然。先大概这么理解。另外SPFA其实还有一种DFS的实现,这里我就先不涉及了,有时间再好好看看。
代码如下:
<pre name="code" class="cpp">#define INF 65535
#define MAXN 10
int vis[MAXN]; //判断是否在队列中。
int dist[MAXN]; //记录最短路
int A[MAXN][MAXN]; //邻接表
int Cnt[MAXN]; //统计每个点进队的次数
bool Spfa(int V0)
{
int n = MAXN;
for (int i = 0; i < n;i ++)
{
dist[i] = INF;
vis[i] = 0;
}
queue<int> Q;
dist[V0] = 0;
vis[V0] = 1;
Q.push(V0);
cnt[V0] ++;
while (!Q.empty())
{
int tmp = Q.front();
Q.pop();
vis[tmp] = 0;
if(Cnt[tmp] > n) //存在负边
return 0;
for (int i = 0; i < n; i ++)
{
if(dist[tmp] + A[tmp][i] < dist[i])
{
dist[i] = dist[tmp] + A[tmp][i];
if(!vis[i])
{
Q.push(i);
vis[i] = 1;
}
}
}
vis[tmp] = 0;
}
return 1;
}
参考文章: http://www.cnblogs.com/biyeymyhjob/archive/2012/07/31/2615833.html
http://blog.youkuaiyun.com/v_july_v/article/details/6181485
http://www.cnblogs.com/scau20110726/archive/2012/11/18/2776124.html
http://www.cnblogs.com/devtang/archive/2011/08/25/spfa.html