最短路算法

最短路径问题旨在寻找图中两节点之间的最短路径,常用的算法有以下四种。注意是把图处理成无向还是有向

Dijkstra's (权值非负)
1 Dijkstra's算法解决的是图中单个源点到其它顶点的最短路径。只能解决权值非负
2 Dijkstral只能求出任意点到达源点的最短距离(不能求出任意两点之间的最短距离),同时适用于有向图和无向图,复杂度为O(n^2).
3算法的过程:
1设置顶点集合S并不断的作贪心选择来选择扩充这个集合。一个顶点属于集合S当且仅当从源点到该点的最短路径长度已知
2 初始时,S中仅含有源。设U是G的某一个顶点,把从源到U且中间只经过S中的顶点的路称为从源到U的特殊路径,并用dis数组距离当前每一个顶点所对应的最短特殊路径
3Dijkstra算法每一次从V-S中取出具有最短特殊长度的顶点u,将u添加到S中,同时对dis数组进行修改。一旦S包含了所有的V中的顶点,dis数组就记录了从源点到其它顶点的最短路径长度。

4 Dij是按照最短路长度的递增的顺序来逐次产生各条最短路的。 

5 模板:
没有优化,时间复杂度o(n^2)

#define MAXN 1010
#define INF 0xFFFFFFF
int  value[MAXN][MAXN];/*保存的是边权值*/
int  dis[MAXN];/*保存源点到任意点之间的最短路*/
int  father[MAXN];/*保存i点的父亲节点*/
int  vis[MAXN];/*记录哪些顶点已经求过最短路*/ 

void input(){
      int star , end , v;
      scanf("%d%d" , &n , &m);
      /*初始化value数组*/
     for(int i = 1 ; i <= n ; i++){
         for(int j = 1; j <= n ; j++)
             value[i][j] = INF;
         value[i][i]  = 0 ;
    }
    for(int i = 0 ; i < m ; i++){
        scanf("%d%d%d" ,  &star , &end , &v);
        if(value[star][end] > v) value[star][end] = value[end][star] = v;/*注意这个地方是处理成无向图还是有向图*/
}

void dijkstra(int s){
     memset(vis , 0 , sizeof(vis));
     memset(father , 0 , sizeof(father));
     /*初始化dis数组*/
     for(int i = 1 ; i<= n ; i++)
          dis[i] = INF;
     dis[s] = 0;
     for(int i = 1 ; i <= n ; i++){/*枚举n个顶点*/
        int pos;
        pos = -1;
        for(int j = 1 ; j <= n ;j++){/*找到未加入集合的最短路点*/
           if(!vis[j] && (pos == -1 || dis[j] < dis[pos]))
              pos = j;
        }
        vis[pos] = 1;/*把这个点加入最短路径集合*/
        for(int j = 1 ; j <= n ; j++){/*更新dis数组*/
           if(!vis[j] && (dis[j] > dis[pos] + value[pos][j])){ //找没有求出最短路的点求最短路
             dis[j] = dis[pos] + value[pos][j];
             father[j] = pos;
             }
        }
     }
}
优化过的,时间复杂度为o(mlogn);
/*利用邻接表来优化*/
#include<utility>
typedef pair<int , int>pii;/*pair专门把两个类型捆绑在一起的*/
priority_queue<pii,vector<pii>,greater<pii> >q;/*优先队列默认使用“<”,那么优先队列的元素是从大到小,所以自己定义“>”比较,STL中可以用greater<int>来表示">",这样就可以来声明一个小整数先出队的优先队列*/
#define MAXN 1010
#define INF 0xFFFFFFF
int n , m;/*有n个点,m条边*/
int first[MAXN] , next[MAXN];/*first数组保存的是节点i的第一条边,next保存的是边e的下一条边*/
int u[MAXN] , v[MAXN] , value[MAXN];
int vis[MAXN];
int dis[MAXN];

/*读入这个图*/
void input(){
     scanf("%d%d" , &n , &m);
     /*初始化表头*/
     for(int i = 1 ; i <= n ; i++)
        first[i] = -1;
     for(int i = 1 ; i <= m ; i++){
        scanf("%d%d" , &u[i] , &v[i] , &value[i]);
        next[i] = first[u[i]];/*表头往后移动*/
        first[u[i]] = i;/*更新表头*/
     }
}

/*Dijkstra*/
void Dijkstra(int s){
     memset(vis , 0 , sizeof(vis));
     /*初始化点的距离*/
     for(int i = 1 ; i <= n ; i++)
          dis[i] = INF;
    dis[s] = 0;
     while(!q.empty())
        q.pop();
     q.push(make_pair(dis[s] , s));
     while(!q.empty()){
          pii u = q.top();
          q.pop();
          int x = u.second;
          if(vis[x])
            continue;
          vis[x] = 1;
          for(int i = first[x] ; i != -1 ; i = next[i]){
             if(dis[v[e]] > dis[x] + value[i]){
                dis[v[i]] = dis[x] + value[i];
                q.push(make_pair(dis[v[i] , v[i]));
             }
          }
     }
}

floyd(权值非负)适用于有向图和无向图

1 floyd 的思想就是通过枚举n个点利用DP的思想来更新最短距离的,假设当前枚举到第k个点,那么就有任意的两个点i , j ,如果i k 相连 j k 相连 那么就可以知道这个时候dis[i][j] = min(dis[i][j] , dis[i][k] + dis[k][j]);,那么只要枚举完n个点,那么就说明已经完全更新完所有两点直间的最短路。

2 floyd算法是最简单的最短路径的算法,可以计算图中任意两点间的最短路径。floyd算法的时间复杂度为o(n^3),如果是一个没有边权的图,把相连的两点间的距离设为dis[i][j]=1.不相连的两点设为无穷大,用floyd算法可以判断i j两点是否相连。
3 floyd 算法不允许所有的权值为负的回路。可以求出任意两点之间的最短距离。处理的是无向图
4 缺点是时间复杂度比较高,不适合计算大量数据

5 如果dis[i][i] != 0,说明此时存在环。

6 如果利用floyd求最小值的时候,初始化dis为INF , 如果是求最大值的话初始化为-1.

7 模板:

#define INF 0xFFFFFFF
#define MAXN 1010
int dis[MAXN][MAXN];

/*如果是求最小值的话,初始化为INF即可*/
void init(){
     for(int i = 1 ; i <= n ; i++){
        for(int j = 1 ; j <= n ; j++)
             dis[i][j] = INF;
        dis[i][i] = 0;
     }
}

/*如果是求最大值的话,初始化为-1*/
void init(){
     for(int i = 1 ; i <= n ; i++){
        for(int j = 1 ; j <= n ; j++)
             dis[i][j] = -1;
     }
}

/*floyd算法*/
void folyd(){
        for(int k = 1 ; k <= n ; k++){/*枚举n个点来更新dis*/
            for(int i = 1 ; i <= n ; i++){
                for(int j = 1 ; j <= n ; j++)
                  if(dis[i][k] != -1 && dis[j][k] != -1)/*如果在求最大值的时候加上这一句*/
                     dis[i][j] = min(dis[i][k]+dis[k][j] , dis[i][j]);
            }
       }
 }
 

floyd扩展:

如何用floyd找出最短路径所行经的点: 1 这里要用到另一个矩阵P,它的定义是这样的:p(ij)的值如果为p,就表示i到j的最短行经为i->p...->j,也就是说p是i到j的最短行径中的j之前的第1个点。

2 P矩阵的初值为p(ij) = j。有了这个矩阵之后,要找最短路径就轻而易举了。对于i到j而言找出p(ij),令为p,就知道了路径i->p....->j;再去找p(pj),如果值为q,p到j的最短路径为p->q...->j;再去找p(qj),如果值为r,i到q的最短路径为q>r...->q;所以一再反复,就会得到答案。

3  但是,如何动态的回填P矩阵的值呢?回想一下,当d(ij)>d(ik)+d(kj)时,就要让i到j的最短路径改为走i->...->k->...->j这一条路,但是d(ik)的值是已知的,换句话说,就是i->...->k这条路是已知的,所以i->...->k这条路上k的第一个城市(即p(ik))也是已知的,当然,因为要改走i->...->k->...->j这一条路,p(ij)的第一个城市正好是p(ik)。所以一旦发现d(ij)>d(ik)+d(kj),就把p(ik)存入p(ij).

4 代码:

int dis[MAXN][MAXN];
int path[MAXN][MAXN];

void floyd(){
      int  i, j, k;
      /*先初始化化为j*/
      for (i = 1; i <= n; i++){
           for (j = 1; j <= n; j++)
               path[i][j] = j;
      }
      for (k = 1; k <= n; k++){/*枚举n个点*/
          for (i = 1; i <= n; i++){
              for (j = 1; j <= n; j++){
                   if (dis[i][j] > dis[i][k]+dis[k][j]){
                         path[i][j] = path[i][k];/*更新为path[i][k]*/
                         dis[i][j] = dis[i][k]+dis[k][j];
                    }
              }
           }
      }
}

2  floyd求解环中的最小环

1 为什么要在更新最短路之前求最小环:
在第k层循环,我们要找的是最大结点为k的环,而此时Dist数组存放的是k-1层循环结束时的经过k-1结点的最短路径,也就是说以上求出的最短路是不经过k点的,这就刚好符合我们的要求。为什么呢?假设环中结点i,j是与k直接相连,如果先求出经过k的最短路,那么会有这样一种情况,即:i到j的最短路经过k。这样的话就形成不了环 。

2最小环改进算法的证明:
一个环中的最大结点为k(编号最大),与他相连的两个点为i,j,这个环的最短长度为g[i][k]+g[k][j]+dis[i][j] (i到j的路径中,所有结点编号都小于k的最短路径长度)。根据floyd的原理,在最外层循环做了k-1次之后,dist[i][j]则代表了i到j的路径中,所有结点编号都小于k的最短路径, 综上所述,该算法一定能找到图中最小环。

3 为什么还要value数组:
因为dis数组时刻都在变动不能表示出原来两个点之间的距离。

4 形成环至少要有3点不同的点,两个点是不能算环的。
5 代码:

int mincircle = INF;
int dis[MAXN][MAXN];
int value[MAXN][MAXN];

void floyd(){
     memcpy(value , dis , sizeof(value));
     for(int k = 1 ; k <= n ; k++){
          /*求最小环,不包含第k个点*/
          for(int i = 1 ; i < k ; i++){/*到k-1即可*/
               for(int j = i+1 ; j < k ; j++)/*到k-1即可*/
                   mincircle = min(mincircle , dis[i][j]+value[i][k]+value[k][j]);/*无向图*/
                   mincircle = min(mincircle , dis[i][j] +value[i][k]+value[k][j]);/*表示i->k , k->j有边*/
           }
          /*更新最短路*/
          for(int i = 1 ; i <= n ; i++){
               for(int j = 1 ; j <= n ; j++)
                   dis[i][j] = min(dis[i][k]+dis[k][j] , dis[i][j]);
          }
     }
}

Bellman_Ford(权值可正可负),用来判断负环

1 Bellman_Frod可以计算边权为负值的最短路问题,适用于有向图和无向图.用来求解源点到达任意点的最短路。

2 在边权可正可负的图中,环有零环,正环,负环3种。如果包含零环和正环的话,去掉以后路径不会变成,如果包含负环则最短路是不存在的。那么既然不包含负环,所以最短路除了源点意外最多只经过n-1条边,这样就可以通过n-1次的松弛得到源点到每个点的最短路径。

3 时间复杂度o(n*m);
4 如果存在环的话就是经过n-1次松弛操作后还能更新dis数组。
5 模板:

/*
适用条件:
1单源最短路径
2有向图和无向图
3边权可正可负
*/

#define INF 0xFFFFFFF
#define MAXN 1010*2
int dis[MAXN];/*dis[i]表示的是源点到点i的最短路*/
strucu Edge{
   int x;
   int y;
   int value;
}e[MAXN];

/*处理成无向图*/
void input(){
     for(int i = 0 ; i < m ; i++){
        scanf("%d%d%d" , e[i].x , &e[i].y , &e[i].value);
        e[i+m].x = e[i].y;/*注意地方*/
        e[i+m].y = e[i].x;/*注意地方*/
        e[i+m].value = e[i].value;
     }
}

/*假设现在有n个点,m条边*/
void Bellman_Ford(int s){
    /*初始化dis数组*/
    for(int i = 1 ; i <= n ; i++)
         dis[i] = INF;
    dis[s] = 0;
    for(int i = 1 ; i < n ; i++){/*做n-1次松弛*/
       for(int j = 0 ; j < 2*m ; j++){/*每一次枚举2*m条边,因为是无向图*/
          if(dis[e[j].y] > dis[e[j].x] + e[j].value)
           dis[e[j.].y] = dis[e[i].x]+e[j].value;/*更新dis[e[j].y]*/
          }
       }
    }
}

SPFA(权值可正可负),判断负环.
1用来求解单源最短路径,源点到任意点的最短路。
2 算法介绍:建立一个队列q,初始时队列里只有一个起始点,在建立一个数组dis记录起始点到所有点的最短路径,并且初始化这个数组。然后进行松弛操作,用 队列里面的点去刷新起始点到所有点的最短路,如果刷新成功且刷新点不再队列中则把该点加入到队列最后,重复执行直到队列为空。
3 时间复杂度:O(ke),k指的是所有顶点的进队的平均次数,可以证明k<=2,e为边数

4 可以用SPFA来存在是否存在环,如果是的话就是存在一条边的松弛操作大于等于n。
5 模板:

/*
1 n个点 m条边
2 邻接表存储图: first[i]存储的是节点i的第一条边的编号,nest[i]存储的是编号为i的边的下一条边的编号。
3 star[i]表示编号为i的边的起点,end[i]表示编号为i的边的终点,value[i]表示编号为i的边的权值。
4 dis[i] 表示的源点到i的最短路
*/

#define MAXN 1010
#define INF 0xFFFFFFF

int n , m;
int first[MAXN] , next[MAXN];
int star[MAXN] , end[MAXN] , value[MAXN];
int dis[MAXN];
queue<int>q;

/*输入*/
void input(){
     scanf("%d%d" , &n , &m);
     /*初始化表头*/
     for(int i = 1 ; i <= n ; i++){
        first[i] = -1;
        next[i] = -1;
     }
     /*读入m条边*/
     for(int i = 0 ; i < m ; i++){
        scanf("%d%d%d" , &star[i] , &end[i] , &value[i]);
        star[i+m] = end[i];
        end[i+m] = star[i];
        value[i+m] = value[i];

        next[i] = first[star[i]];/*由于要插入表头,所以将原先表头后移*/
        first[star[i]] = i;/*插入表头*/
        next[i+m] = first[star[i+m]];
        first[star[i+m]] = i+m;
     }
}

/*SPFA*/
void SPFA(int s){
     while(!q.empty())
          q.pop();
     int vis[MAXN];
     memset(vis , 0 , sizeof(vis));
     /*初始化dis*/
     for(int i = 1 ; i <= n ; i++)
          dis[i] = INF;
     dis[s] = 0;
     q.push(s);/*将源点加入队列*/
     vis[s] = 1;/*源点标记为1*/
     while(!q.empty()){
          int x = q.front();
          q.pop();
          vis[x] = 0;/*注意这里要重新标记为0,说明已经出队*/
          /*枚举和点x有关的所有边*/
          for(int i = first[x] ; i != -1 ; i = next[i]){
             if(dis[end[i]] > dis[x] + value[i]){/*松弛操作,利用三角不等式*/
               dis[end[i]] = dis[x] + value[i];
               if(!vis[end[i]]){/*如果该点不再队列里面*/
                  vis[end[i]] = 1;
                  q.push(end[i]);
               }
             }
          }
     }
时间: 2025-01-04 00:47:16

最短路算法的相关文章

Dijkstra最短路算法

上周我们介绍了神奇的只有五行的Floyd最短路算法,它可以方便的求得任意两点的最短路径,这称为"多源最短路".本周来来介绍指定一个点(源点)到其余各个顶点的最短路径,也叫做"单源最短路径".例如求下图中的1号顶点到2.3.4.5.6号顶点的最短路径. 与Floyd-Warshall算法一样这里仍然使用二维数组e来存储顶点之间边的关系,初始值如下. 我们还需要用一个一维数组dis来存储1号顶点到其余各个顶点的初始路程,如下. 我们将此时dis数组中的值称为最短路的&q

只有五行的Floyd最短路算法

暑假,小哼准备去一些城市旅游.有些城市之间有公路,有些城市之间则没有,如下图.为了节省经费以及方便计划旅程,小哼希望在出发之前知道任意两个城市之前的最短路程. 上图中有4个城市8条公路,公路上的数字表示这条公路的长短.请注意这些公路是单向的.我们现在需要求任意两个城市之间的最短路程,也就是求任意两个点之间的最短路径.这个问题这也被称为"多源最短路径"问题. 现在需要一个数据结构来存储图的信息,我们仍然可以用一个4*4的矩阵(二维数组e)来存储.比如1号城市到2号城市的路程为2,则设e[

vb.net 中的短路

我问"你学过vb.net吗?"你说:"学过,而且用的特别熟!"我问:"那你知道vb.net的短路概念吗?"你会说:"当然了,不就是逻辑与或上那些#$%^&*?......" 我说:"对,就是那些东西!我给你一段程序看看你能搞定吗?" "No problem !"you said . 下面是段程序,很简单,看你能不能搞定. If Not Equals(txtAge.Text, St

HDU 1535 Invitation Cards:多源点到单点最短路

链接: http://acm.hdu.edu.cn/showproblem.php?pid=1535 题目: Invitation Cards Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others) Total Submission(s): 1044    Accepted Submission(s): 459 Problem Description In the age of te

HDU 3339 In Action:最短路+背包

链接: http://acm.hdu.edu.cn/showproblem.php?pid=3339 题目: Problem Description Since 1945, when the first nuclear bomb was exploded by the Manhattan Project team in the US, the number of nuclear weapons have soared across the globe. Nowadays,the crazy bo

HDU 1595 find the longest of the shortest(枚举,最短路)

链接: http://acm.hdu.edu.cn/showproblem.php?pid=1595 题目: find the longest of the shortest Time Limit: 1000/5000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submission(s): 667    Accepted Submission(s): 220 Problem Description Ma

HDU 1385 Minimum Transport Cost:最短路,打印字典序路径

链接: http://acm.hdu.edu.cn/showproblem.php?pid=1385 题目大意: 有N个城市,然后直接给出这些城市之间的邻接矩阵,矩阵中-1代表那两个城市无道路相连,其他值代表路径长度. 如果一辆汽车经过某个城市,必须要交一定的钱(可能是过路费). 现在要从a城到b城,花费为路径长度之和,再加上除起点与终点外所有城市的过路费之和. 求最小花费,如果有多条路经符合,则输出字典序最小的路径. 分析与总结: 1.   这题的关键在于按照字典序输出路径. 假设有 1---

Dijkstra算法 c语言实现

 Dijkstra(迪杰斯特拉)算法是典型的最短路径路由算法,用于计算一个节点到其他所有节点的最短路径.主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止.Dijkstra算法能得出最短路径的最优解,但由于它遍历计算的节点很多,所以效率低. Dijkstra算法是很有代表性的最短路算法,在很多专业课程中都作为基本内容有详细的介绍,如数据结构,图论,运筹学等等. 其基本思想是,设置顶点集合S并不断地作贪心选择来扩充这个集合.一个顶点属于集合S当且仅当从源到该顶点的最短路径长度已知. 初始时,

HDU 1245 Saving James Bond

链接: http://acm.hdu.edu.cn/showproblem.php?pid=1245 题目: Saving James Bond Time Limit: 6000/3000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total Submission(s): 1066    Accepted Submission(s): 186 Problem Description This time let us