图的最短路径--单源、多源最短路径

最短路径

–在网络中,求两个不同顶点之间的所有路径中,边的权值之和最小的路劲。

单源最短路径

–从某固定源点出发的最短路径

无权图的最短路径
  • 按照路径长度递增的顺序找出源点到各个顶点的最短路径

图的最短路径--单源、多源最短路径_第1张图片

  • 类似于BFS-宽度优先遍历,可以通过队列来实现,

    • 先让顶点入队,循环执行下列步骤
    • 出队首元素,访问其所有邻接点
    • 标明源点到这些邻接点的路径长度,并将其入队
有权图的最短路径
Dijkstra算法
  • 令第一组为集合S={源点+已经确定最短路径的顶点vi}

  • 令第二组为未在集合S中的顶点v,定义length成员为源点s到v的最短路径长度,该路径除了终点v以外,所有顶点都必须是集合S中的顶点。即{s—>(vi∈S)—>v}的最小长度

  • 每次对未在第一组集合S的顶点,选一个length最小的顶点(记为v),将其增加至集合S中。

    • 如何选这个顶点V–用一个最小堆H(刚开始只有源点),每次找出并删除一个length值最小的顶点V,这里这个找到并删除的顶点V就是每次加入集合S的顶点,然后找到V的所有邻接点,对其邻接点的length值进行赋值,然后加入最小堆H里,往复循环,直至最小堆空了(最小堆空了,即所有顶点都加入了集合S)–看程序就明白了

    • 选进去的时候要注意什么–增加v进集合S中会影响顶点w(w为v的邻接点)的length值,因为多了个顶点v,可能会改变s—w的走法,原先不经过v,现在最短路径经过v,即需要考虑

      length[w],与length[v] + e.weigth的大小关系--------(1)

  • 直到把所有顶点都送进集合S中

图的最短路径--单源、多源最短路径_第2张图片

设源点为V0,则按照Dijkstra算法的最短路径求解过程如下

图的最短路径--单源、多源最短路径_第3张图片

注:初始的时候只有集合S只有源点s,因此只有到点v1、v2有路径,路径长度用length表示,,没有路径则length为∞,路径的终点的前一个点用pre表示。

```C++
class Dist { // Dist类,用于保存最短路径信息
 public:
  int index; // 结点的索引值
  int length; // 当前最短路径长度
  int pre; // 路径最后经过的结点
};

void Dijkstra(Graph& G, int s, Dist* &D) { // s是源点
 D = new Dist[G. VerticesNum()]; // 开辟空间给类Dist,用来记录当前最短路径长度
    

    
 for (int i = 0; i < G.VerticesNum(); i++) { // 初始化,将图中所有顶点的标志位记为未访问,将Dist类的索引值记为顶点号,将顶点到源点的length置为∞。
     G.Mark[i] = UNVISITED; 
     D[i].index = i; 
     D[i].length = INFINITE;
     D[i].pre = s;
   }
    D[s].length = 0; // 初始化,源点自身的路径长度置为0
    
    
 MinHeap<Dist> H(G. EdgesNum()); // 最小堆用于找出集合S中到源点的路径最短的点,最小堆存放Dist类元素,共有G.EdgesNum个长度
 H.Insert(D[s]);  //将源点放入最小堆
    
    
 
 for (i = 0; i < G.VerticesNum(); i++) {
   bool FOUND = false;
   Dist d;
   while (!H.isEmpty()) {
     d = H.RemoveMin(); //获得集合S中到源点s路径长度最小的结点,并删除
     if (G.Mark[d.index] == UNVISITED) { //如果未访问过则跳出循环
        FOUND = true; break;
     } 
    }
    if (!FOUND) break; // 若没有符合条件的最短路径则跳出本次循环
    int v = d.index; 
    G.Mark[v] = VISITED; // 将标记位设置为 VISITED
    for (Edge e = G.FirstEdge(v); G.IsEdge(e); e = G.NextEdge(e)) // 对最小堆中到源点路径最短的顶点v,求他的每个邻接点,并考虑是否需要改变其最小距离--刷新最短路,然后将其加入最小堆
      if (D[G.ToVertex(e)].length > (D[v].length+G.Weight(e))) {//这里第一次执行时,因为刚开始最小堆里只有源点,其他顶点到源点的length都为∞,执行完后V1、V2的length值才改变
        D[G.ToVertex(e)].length = D[v].length + G.Weight(e);
        D[G.ToVertex(e)].pre = v;
        H.Insert(D[G.ToVertex(e)]);
    } 
 }
} 

Dijkstra算法时间复杂度

T = O ( ∣ V ∣ l o g ∣ V ∣ + ∣ E ∣ l o g ∣ V ∣ ) = O ( ∣ E ∣ l o g ∣ V ∣ ) T = O( |V| log|V| + |E| log|V| ) = O( |E| log|V| ) T=O(VlogV+ElogV)=O(ElogV)

前半部分为在最小堆里找V次,依次复杂度为logV,后半部分是往最小堆里插入Dist,一次最多有可能插E次(极限情况,一个顶点有E条边)

Dijkstra使用条件
  • 图中不能出现有总权值为负值的回路
  • 如果存在负值边也有可能发生计算错误
  • 持负权值的最短路径算法有Ford算法、SPFA算法

多源最短路径

–求每对顶点间的最短路径

Floyd算法
  • Dk[i] [j]表示路径{ i —> { l<= k } —> j }的最小长度(i、j、l、k表示顶点编号)

  • 首先用矩阵D0表示该图的邻接矩阵

  • 在矩阵D0上做n次迭代

  • 如何迭代呢–当Dk-1已经完成,递推到Dk时,

    • 若k∉最短路径{ i —> { l<= k } —> j },(即i到j的最短路径不经过k)则Dk=Dk-1

    • 若k∈最短路径{ i —> { l<= k } —> j }(即i到j的最短路径经过k),则该最短路径由两条路径组成:
      D ( k ) [ i ] [ j ] = D ( k − 1 ) [ i ] [ k ] + D ( k − 1 ) [ k ] [ j ] D(k)[i][j]=D(k-1)[i][k]+D(k-1)[k][j] D(k)[i][j]=D(k1)[i][k]+D(k1)[k][j]

cpp
void Floyd(Graph& G, Dist** &D) {
    int i,j,v;
    D = new Dist*[G.VerticesNum()]; // 申请空间,申请一个与邻接矩阵等大的D
    for (i = 0; i < G.VerticesNum(); i++)
      D[i] = new Dist[G.VerticesNum()];
      
      // 初始化数组D
    for (i = 0; i < G.VerticesNum(); i++) 
     for (j = 0; j < G.VerticesNum(); j++) {
      if (i == j) { 
          D[i][j].length = 0;
          D[i][j].pre = i; }
      else { 
          D[i][j].length = INFINITE; 
          D[i][j].pre = -1; 
      } 
     }
    //对D0矩阵进行赋值,让其与邻接矩阵相同
   for (v = 0; v < G.VerticesNum(); v++)
    for (Edge e = G.FirstEdge(v); G.IsEdge(e); e = G.NextEdge(e)) {
        D[v][G.ToVertex(e)].length = G.Weight(e);
        D[v][G.ToVertex(e)].pre = v; }
   
      
      
      // 加入新结点后,更新那些变短的路径长度
    for (k = 0; k< G.VerticesNum(); k++)
     for (i = 0; i < G.VerticesNum(); i++)
      for (j = 0; j < G.VerticesNum(); j++)
          
       if (D[i][j].length > (D[i][k].length+D[k][j].length)) {
           D[i][j].length = D[i][k].length+D[k][j].length; 
           D[i][j].pre = D[k][j].pre;//第k次迭代,考虑在加入编号为k的顶点的情况下,是否需要更新i、j之间的路径长度
       } 
  }
  
Floyd算法时间复杂度

T = O ( ∣ V ∣ 3 ) T = O( |V|3 ) T=O(V3)

你可能感兴趣的:(图的最短路径--单源、多源最短路径)