权:与每条边相联系的是穿越这条边的代价
赋权路径长:一条路径的值为,无权路径长就是边的条数
单源最短路径问题:给定一个赋权图和一个特定顶点s作为输入,找出从s到G中每个其他顶点的最短赋权路径
负值圈:若图中存在负值圈,那么会有问题,因为可以在负值圈中一直转,让路径无限小
性能:由于图是不可拆分的,因此不存在找从s到某一个顶点比从s到所有顶点快多于常数因子的算法,这也是图的问题无法应用任何并发算法和任何分布式算法的原因
一.无权最短路径
代码如下:
/* 使用队列改进的无权最短路径算法 */
void Unweighted(Table T)
{
Queue Q;
Vertex V, W;
/* 创建队列 */
Q = CreateQueue(NumVertex);
MakeEmpty(Q);
/* 将单发点S入队 */
Enqueue(S, Q);
while (!IsEmpty(Q))
{
V = Dequeue(Q);
T[V].Known = True;
/* 遍历V的邻接顶点,根据距离判断之前是否访问过,若没有,那么入队列 */
for each W adjacent to V
if (T[W].Dist == Infinity)
{
T[W].Dist = T[V].Dist + 1;
T[W].Path = V;
Enqueue(W, Q);
}
}
DisposeQueue(Q);
}
二.赋权最短路径
Dijkstra算法:是贪婪算法,分阶段进行,每个阶段选择一个顶点v,它具有所有未知顶点中最小的,这里是使用已知顶点作为中间顶点,从s到v的最短路径长。
正确性:每个阶段选择的顶点v一定是最优秀的,原因是,设通过一个未知顶点t到达v的路径比当前的短,那么t就是当前阶段最优秀的,而不是v。
代码如下:
typedef int Vertex;
struct TableEntry
{
List Header;
int Known; //标记顶点是否已知
DistType Dist; //通过已知顶点从s到当前顶点的距离
Vertex Path;
};
#define NotAVertex (-1)
typedef struct TableEntry Table[NumVertex];
/* 初始化表,Start是所求的源点 */
void InitTable(Vertex Start, Graph G, Table T)
{
int i;
/* 将图读入table中 */
ReadGraph(G, T);
for (i = 0; i < NumVertex; i++)
{
T[i].Known = False;
T[i].Dist = Infinity;
T[i].Path = NotAVertex;
}
T[Start].dist = 0;
}
/* 打印路径,是递归 */
void PrintPath(Vertex V, Table T)
{
if (T[V].Path != NotAVertex)
{
PrintPath(T[V].Path, T);
printf("to");
}
printf("%v", V);
}
void Dijkstra(Table T)
{
Vertex V, W;
for (;;)
{
/* 从未知顶点中选择一个路径最短的 */
V = smallest unknown distance vertex;
/* 没有这样的顶点,说明所有顶点都遍历到了,结束 */
if (V == NotAVertex)
break;
/* 标记为已知 */
T[V].Known = True;
/* 遍历V的邻接顶点,更新距离信息 */
for each W adjacent to V
if (!T[W].Known)
/* Cvw是从v到w的距离 */
if (T[V].Dist + Cvw < T[W].Dist)
{
Decrease(T[W].Dist to T[V].Dist + Cvw);
T[W].Path = V;
}
}
}
时间分析:
若通过扫描表查找,那么每次都要花费O(V)时间,一共时间,更新是常数时间,一共O(E)时间,加起来就是时间,若图是稠密的,那么就是边的线性时间
若图是稀疏的,那么不适合使用遍历的方式查找最小距离,应该将顶点存放在优先队列中,查找未知顶点中最小距离和标记为已知是一个deletemin的操作,更新T[W]是一个decreasekey操作,一共O(V)次deletemin和O(E)次decreasekey,加起来时间是。问题是使用何种优先队列,若使用二叉堆,由于对find操作支持不好,需要保存每个顶点在优先队列中的位置并更新时保存新的位置,有两种解决方案,一种是使用配对堆,一种是每次将新的插入到二叉堆中,这样会增加深度到,由于,因此不改变时间界
三.具有负边值的图
若图中有负值边,那么dijkstra算法的正确性无法保证,因为当前阶段得到一个最优值,但是后面的未知顶点会产生更短的路径,此时需要改进dijkstra算法让它有回溯功能,消除已知顶点的概念,每次若某个顶点距离被更新了,那么需要重新处理这个顶点,代码如下:
/* 有负值边的图,时间O(EV) */
void WeightedNegative(Table T)
{
Queue Q;
Vertex V, W;
Q = CreateQueue(NumVertex);
MakeEmpty(Q);
Enqueue(S, Q);
while (!IsEmpty(Q))
{
/* 每个顶点可以出队V次 */
V = Dequeue(Q);
for each W adjacent to V
if (T[V].Dist + Cvw < T[W].Dist)
{
T[W].Dist = T[V].Dist + Cvw;
T[W].Path = V;
/* 这个可以用位图实现 */
if (W is not already in Q)
Enqueue(W, Q);
}
}
DisposeQueue(Q);
}
若有负值圈,会出现死循环的情况,可以通过判断每个顶点出队的次数跳出循环,当任何一个顶点出队V+1次,说明有负值圈
四.无圈图
若事先知道了图是无圈的,那么可以应用拓扑排序的思想选择下一个顶点,因为从S到V只有一条路径可走,时间和拓扑排序一样都是,代码如下:
/* 无圈图,使用拓扑排序的思想,时间是O(E + V) */
void Acyclic(Table T)
{
Queue Q;
Vertex V, W;
Q = CreateQueue(NumVertex);
Q = MakeEmpty();
Enqueue(S, Q);
while (!IsEmpty(Q))
{
V = Dequeue(Q);
for each W adjacent to V
if (--Indegree[W] == 0)
{
Enqueue(W, Q);
T[W].Path = V;
T[W].Dist = T[V].Dist + Cvw;
}
}
Dispose(Q);
}
五.所有点对最短路径
由于已经有了单发点最短路径,对稀疏图可以运行次单源最短路径算法,对稠密图可以使用动态规划,时间是