- 图的应用实在很广,课堂所学实为皮毛
- 考虑基于邻接矩阵的无向带权图,边的权值的典型意义就是路途的长度,从顶点u到顶点v的边权值为w,可以表示城市u到城市v之间路长为w。
- 最短路径问题考虑的就是从某个顶点出发到其他任何一个顶点所经过的最短的路径。
Dijkstra迪杰斯特拉算法
- 根据起点V0,最终得到的是一个从V0到其他顶点的路径按路径长度依次递增的次序产生最短路径,可知一共有(顶点总数-1)条路径,分别对应着V0到其他某一顶点的最短路径。
- n为顶点总数。则有顶点集V[n],起点索引“iv0”,邻接矩阵G[n][n];引入辅助向量D[n],D[iv]表示当前从顶点”iv0”到”iv”的最短路径的长度;引入辅助向量Final[n],Final[iv]为true表示顶点“iv0”到“iv”的最短路径已经找到。
- 循环次数为n-1次,依次递增地得到一条最短路径。如此描述着实难以理解,不知一下说明是否有帮助
Dijkstra最终得到的最短路径集 | 长度 | 经过的边集 |
---|
iv0 -> v1 | L1 | E1 |
iv0 -> v2 | L2 (大于l1) | E2 |
… | … | … |
iv0 -> vn-1 | Ln-1 (最大) | En-1 |
原理
- 1)从“iv0”出发的最短路径集中最短的一条必然是可以从“iv0”出发只经过一条边(设为e)到达。假设存在一条经过m(m>1)条边的更短的路径,那么这条边上从“iv0”出发的第一条边(设为e0)必然比”e”要短,那么”e0”就是最短的只经过一条边的路径。也就是说表中的E1必然只有一条边。
- 2)首次循环将D[i]赋值为G[iv0][i],表示从“iv0”直接到达顶点“i”的路径的长度(无路径则为ManInt表示无穷)。Final除“iv0”为true外其余均为false。
- 3)则D[n]中((值最小)&&(Final为false))的顶点就是上表的“v1”,并将Final[v1]设为true表示从“iv0”到“v1”的最短路径已经找到了。
- 4)下一步寻找v2,也就是最短路径集中第二短的路径。我们已经找到了最短路径集中最短的路径也就是从“iv0”到“v1”这条路径,那么从“iv0”到“v2”的最短路径只能是以下两种情况之一
- 1、从“iv0”直接到达“v2”
- 2、从”iv0”经过“v1”到达“v2”
- 说明:假如存在(iv0)->(v’)->(v2)为最短路径集中第二短的路径,那么也就存在(iv0)->(v’)要比这条“第二短”的路径更短,但是比“第二短”的路径更短的路径只能是我们已经找到了的(iv0)->(v1),所以不存在所谓的(v’)。
- 操作:利用得到的v1来更新D[n]。若((Final[i]==false) && (L1+G[v1][i] < D[i])),也就是说对于顶点(i)存在路径(iv0)->(v1)->(i)要比路径(iv0)->(i)短,则D[i]=L1+G[v1][i] 。这样就得到了所有情况1、2、的路径。找到最短的一条也就得到了v2。别忘了把Final[v2]置为true。
- 5)继续寻找v3,v3也只能是一下两种情况之一
- 1、从“iv0”直接到达“v3”
- 2、从“iv0”中间只经过已经找到的顶点(v1,v2)中的顶点而到达“v3”
- 说明:同理,假如存在一条路径,从(iv0)到达(v3)而经过了不是(v1)或(v2)的顶点(v’),那么也就存在(iv0)到(v’)这条路径要比“第三短”的路径更短,但是比第三短的路径更短的路径只能是“(iv0)到(v1)”这条最短的和“(iv0)到(v2)”这条第二短的,所以同样不存在(v’)。
- 操作:同理,利用得到的v2来更新D[n],若((final[i]==false)&&(L2 + G[v2][i] < D[i] )),则 D[i] = L2+G[v2][i] 。选取其中最短的路径也就得到了v3。Final[v3]=true;
- 6)依次类推……最终得到所有的最短路径。
要点
- D[n]的初始化,将D[i]赋值为G[n][i]
- 每次的循环操作都是先从满足Final==false的顶点(未找到最短路径的顶点)中选取D[v]最小(最短)的顶点(v),设置Final[v]为true并根据顶点(v)来更新D[n]
- D[i]实际只保存了路径(iv0)到(i)的长度信息,并未保存路径的走向
- 1、书本上介绍的方法利用 bool P[n][n] 来保存路径走向,其中P[v][w] 为true表示从“iv0”到“v”的最短路径上经过了“w”。但这种保存方法只保存了路径经过了哪些顶点,而没有保存路径经过顶点的次序。
- 2、后来我想到一种改进方法,利用int P[n][n] 来保存,其中 P[v][w] 若为0,则表示“w”不属于“iv0”到“v”的最短路径,若 P[v][w] 的值为order,则表示“w”属于“iv0”到“v”的最短路径,且是从“iv0“出发经过的第order个顶点。
- 3、用P[v][w] 搞得好乱,后来干脆扩展了D[n],使得每个节点既包含路径的长度信息(int)又包含路径的走向信息(顶点数组)
算法
template<typename ElemType>
struct GraphPath
{
int Length; //路径长度
int NodesCount; //路径经过的顶点个数
ElemType* Path; //路径的走向
//我选择了直接保存顶点信息
//其实也可以只保存顶点的索引信息
//只需要一个顶点集elems来对应即可
};
const int MaxWeight = 2147483647;//两顶点之间无边
int **graph;//边集,邻接矩阵
int total;//顶点个数
ElemType *elems;//顶点集,ElemType为模板的类型,可以是char,string等等。其索引与graph邻接矩阵对应
GraphPath<ElemType> * ShortestPath(ElemType v0);
//ElemType v0 为起始点
//返回从v0到其他顶点的最短路径的集合,长度为total
//失败则返回null
int index = GetIndex(v0);//从elems顶点集获取v0起始点的索引
if(index == -1) return NULL;//如果不存在v0则返回null
GraphPath<ElemType> *paths = new GraphPath<ElemType>[total];//即D[n]
bool *Final = new bool[total];//标志数组
for(int i=0;i<total;++i)
{//初始化
Final[i]=false;//置Final全为false
paths[i].Length = graph[index][i];//保存路径长度
paths[i].Path = new ElemType[total];
if(paths[i].Length != MaxWeight)
{//保存路径走向
paths[i].NodeCount = 1;
paths[i].Path[0] = elems[i];
}
}
Final[index]=true;//不必搜寻v0自己
for(int i=1;i<total;++i)
{//循环n-1次,i只做计数器保证循环次数为n-1
int min = MaxWeight;
int minIndex = -1;
for(int j=0;j<total;++j)
{//寻找paths中最短的路径
if(!Final[j] && paths[j].Length < min)
{
min = paths[j].Length;
minIndex = j;
}
}
if(minIndex == -1) return NULL;//防止非连通图
Final[minIndex]=true;//找到一条最短路径
for(int j=0;j<total;j++)
{//更新当前最短路径
if(!Final[j] && //顶点j未找到最短路径
graph[minIndex][j]!=MaxWeight &&//从刚刚找到的顶点minIndex出发可以到达j
graph[minIndex][j] + min < paths[j].Length)//长度更短
{
paths[j].Length = graph[minIndex][j] + min;//更新长度
paths[j].NodeCount = paths[minIndex].NodeCount + 1;//更新顶点个数
for(int k=0;k<paths[minIndex].NodeCount;++k)
{//更新路径走向
paths[j].Path[k] = paths[minIndex].Path[k];
}
paths[j].Path[paths[j].NodeCount -1] = elems[j];
}
}
return paths;
}
其他
- 依旧是没有画图,尽管我觉得画图是最好的理解复习方法,然而考试没几天了。。。