当前位置:编程学习 > C/C++ >>

图的单源最短路径算法

概述
假如你有一张地图,地图上给出了每一对相邻城市的距离,从一个地点到另一个地点,如何找到一条最短的路?最短路算法要解决的就是这类问题。定义:给定一个有(无)向图,每一条边有一个权值w,给定一个起始点S和终止点T,求从S出发走到T的权值最小路径,即为最短路径。最短路径算法依赖一种性质:一条两顶点间的最短路径包含路径上其他最短路径。最简单的说就是:最短路径的子路径是最短路径。

 


松弛技术
松弛技术本质上是一个贪心操作。松弛操作:对每个顶点v属于V,都设置一个属性d[v],用来描述从源点s到v的最短路径上权值的上界,称为最短路径估计(shortest-path estimate),同时parent[v]代表前趋。初始化伪代码:


[html]
INITIALIZE-SINGLE-SOURCE(G, s) 
    for each vertex v (- V[G] 
    do 
        d[v] <- INF 
        parent[v] <- NULL 
    done 
d[s] <- 0 

INITIALIZE-SINGLE-SOURCE(G, s)
 for each vertex v (- V[G]
 do
  d[v] <- INF
  parent[v] <- NULL
 done
d[s] <- 0

初始化后,对所有v属于V,parent[v] = NULL,对v属于{V - {s}},有d[s] = 0 以及d[v]=无穷。松弛一条边(u, v),如果这条边可以对最短路径改进,则更新d[v]和parent[v]。一次松弛操作可以减少最短路径估计的值d[v],并更新v的前趋域parent[v].下面的伪代码对边(u, v)进行了一步松弛操作:


[html]
RELAX(u, v, w) 
    if d[v] > d[u] + w(u, v) 
    then 
        d[v] <- d[u] + w(u, v) 
        parent[v] <- u 
    fi 

RELAX(u, v, w)
 if d[v] > d[u] + w(u, v)
 then
  d[v] <- d[u] + w(u, v)
  parent[v] <- u
 fi

 \

上图所示中,左边例子,最短路径估计值减小,右边例子,最短路径估计值不变。当发现v到u有更近的路径时,更新d[v]和parent[v]


Dijkstra算法
解决最短路径问题,最经典的算法是Dijkstra算法,它是一种单源最短路径算法,其核心思想是贪心算法(Greedy Algorithm)。Dijkstra算法要求所有边的权值非负


算法思想
设G = (V, E)是一个带权有向图,把图中顶点集合V分成两组,第一组为已求出最短路径的顶点集合(用S表示,初始时S中只有一个源点,以后每求得一条最短路径,就将其加入到集合S中,直到全部顶点都加入到S中,算法就结束了);第二组为其余未确定最短路径的顶点集合(用U表示),按最短路径长度的递增次序把第二组的顶点加入S中。在加入的过程中,总保持从源点v到S中各顶点的最短路径长度不大于从源点v到U中任何顶点的最短路径长度。此外,每个顶点对应一个距离,S中的顶点的距离就是从v到此顶点的最短路径长度,U中的顶点的距离,是从v到此顶点只包括S中的顶点为中间顶点的当前最短路径长度


伪代码
[html]
Dijkstra(G, w, s) 
    INITIALIZE-SINGLE-SOURCE(G, s) 
    S <- 空集 
    Q <- V[G] 
 
    while Q != 空集 
    do 
        u <- EXTRACT-MIN(Q) 
        S <- S && {u} 
        for each vertex v (- Adj[u] 
        do 
            RELAX(u, v, w) 
        done 
    done 

Dijkstra(G, w, s)
 INITIALIZE-SINGLE-SOURCE(G, s)
 S <- 空集
 Q <- V[G]

 while Q != 空集
 do
  u <- EXTRACT-MIN(Q)
  S <- S && {u}
  for each vertex v (- Adj[u]
  do
   RELAX(u, v, w)
  done
 done

Dijkstra算法时间主要消耗在寻找最小权值的边,和松弛所有剩余边,所以EXTRACT-MIN(Q)这一步,更好的方法是使用优先队列,优先队列可以使用二叉堆

 


算法描述
假设用带权的邻接矩阵arcs来表示带权有向图,arcs[i][j]表示弧<vi, vj>上的权值。若<vi, vj>不存在,则置arcs[i][j]为无穷(在计算机上可用允许的最大值代替)。S为已找到从v出发的最短路径的终点的集合,它的初始状态为空集。那么,从v出发到图上其余各顶点(终点)vi可能达到的最短路径长度的初值为:D[I] = arcs[src][i] vi (- V
选择vj,使得D[j] = Min{D[i] | vi (- V - S},vj就是当前求得的一条从v出发的最短路径的终点。令S = S && {j}
修改从v出发到集合V - S上任一顶点vk可达的最短路径长度。如果 D[j] + arcs[j][k] < D[k],则修改D[k] = D[j] + arcs[j][k]
重复操作2、3共n-1次。由此求得从v到图中其余各顶点的最短路径长度是依路径长度递增的序列


实现代码(c语言)
[cpp]
#include <stdio.h>  
#include <stdlib.h>  
 
// 图顶点的最大值  
#define NODE 102  
 
// 边权值的最大值  
#define MAX 10002  
 
// 邻接矩阵存储图  
int map[NODE][NODE]; 
 
// 记录src到其它各个顶点的最短路径长度  
int dis[NODE]; 
 
// 标记数组,判断哪些src到哪些顶点的最短路径已经求出  
int mark[NODE]; 
 
/**
 * 用Dijkstra算法求图map的src到其余顶点的最短路径
 * src为单源顶点,n为图中顶点个数
 */ 
void dijkstra(int src, int n) 

         
    int i, j, min, k, tmp; 
 
    // 初始化  
    for (i = 0; i < n; i ++) { 
        dis[i] = map[src][i]; 
        mark[i] = 0; 
    } 
 
    dis[src] = 0; 
    mark[src] = 1; 
 
    // n-1次主循环,每次循环求得src到某个顶点v的最短路径  
    for (i = 1; i < n; i ++) { 
        min = MAX; 
        k = src; 
        for (j = 0; j < n; j ++) { 
            if (!mark[j] && dis[j] < min) { 
                k = j; 
                min = dis[j]; 
            } 
        } 
        mark[k] = 1; 
 
        // 更新src到其它各顶点的最短路径  
        for (j = 0; j < n; j ++) { 
            tmp = map[k][j] + dis[k]; 
            if (tmp < dis[j] && mark[j] == 0) { 
                dis[j] = tmp; 
            } 
        } 
    } 

#include <stdio.h>
#include <stdlib.h>

补充:软件开发 , C++ ,
CopyRight © 2022 站长资源库 编程知识问答 zzzyk.com All Rights Reserved
部分文章来自网络,