2、Dijkstra算法

 

1、 算法描述

在一个如图1的有向图中,如何求源点到到其它各点的最短路径呢?

  

图1示

Dijkstra 提出了一个按路径长度递增的次序产生最路径的算法。

    首先,引进一个辅助向量D,它的每个分量D[j]表示当前所找到的从始点v到每个

终点vi的最短路径的长度。它的初态为:若从v到vi有弧,则D[i]为弧上的权值;否则

置D[i]为无穷。显然,长度为

D[j]=Min{D[i]|vi属于V}

的路径就是从v出发的长度最短的一条最短路径。此路径为(v,vj)。

一般储况下,假设S为已求得最短路径的终点的集合,则可证明:下一条员短路径

(设其终点为x)或者是弧(v,x),或者是中间只经过S中的顶点而最后到达顶点x的路径。

我们用C语言描述为:

设邻接矩阵为如下所示,则在求从v0 到其它各点的最短路径中D向量的变化过程如下示:

完整的动态图如上所示

2、 对算法的完整实现

#define MAX_NAME 5 //顶点字符串的最大长度+1

#define MAX_INFO 20 //相关信息字符串的最大长度+1

#define MAX_VERTEX_NUM 20//最大顶点数

#include "string.h"

#include "ctype.h"

#include "malloc.h" //malloc etc

#include "limits.h" //INT_MAX etc

#include "stdio.h" //EOF(=^Z orF6),NULL

#include "stdlib.h" //atio()

//#include "math.h"//floor(),ceil(),abs()

#define TRUE 1

#define FALSE 0

#define OK 1

#define INFINITY INT_MAX //用整形最大值代替无穷

typedef int VRType;

typedef char InfoType;

typedef char VertexType[MAX_NAME];

typedef char InfoType;

typedef int Status;

typedef int Boolean;

typedef intPathMatrix[MAX_VERTEX_NUM][MAX_VERTEX_NUM];

typedef int ShortPathTable[MAX_VERTEX_NUM];

enum GraphKind {DG,DN,AG,AN};

typedef struct 

{

VRType adj; //顶点关系类型,无权图,用1/0表示相邻否,对于带权图,则为权值类型。

InfoType *info;

}AdjMatrix[MAX_VERTEX_NUM][MAX_VERTEX_NUM],ArcCell;

typedef struct 

{

VertexType vexs[MAX_VERTEX_NUM];//顶点向量,注意,这里vexs是二唯阵

AdjMatrix arcs; // 邻接矩阵

int vexnum,arcnum;//图的当前顶点数和弧数

GraphKind kind; //图的种类标志

}MGraph;

//这个定义包含了顶点的名字及个数,邻接矩阵的权值,顶点和弧的数目,及图的类型。

 

int LocateVex(MGraph G,VertexType u)

{

int i;

for(i=0;i<G.vexnum;++i)

       if(strcmp(u,G.vexs[i])==0)

              returni;

       return-1;

}

Status CreateDN(MGraph &G)

{

int i,j,k,w;

VertexType va,vb;

printf("请输入有向图G的顶点数,弧数\n");

scanf("%d%d",&G.vexnum,&G.arcnum);

printf("请输入%d顶点的值(<%d个字符):\n",G.vexnum,MAX_NAME);

for(i=0;i<G.vexnum;++i)

       scanf("%s",G.vexs[i]);//注意,这里G.vexs[i]是顶点字符串的首地址。

for(i=0;i<G.vexnum;++i)

       for(j=0;j<G.vexnum;++j)//初始化邻接矩阵

       {

       G.arcs[i][j].adj=INFINITY;  //任意两个顶点间距离为无穷大。

       G.arcs[i][j].info=NULL;

       }//for

       printf("请输入%d条弧的弧尾 弧头 权值(以空格为间隔)\n",G.arcnum);

       for(k=0;k<G.arcnum;++k)

       {

       scanf("%s%s%d%*c",va,vb,&w);//用%*c吃掉回车符

       i=LocateVex(G,va);  //注意,这里定义的是char va[5],也就是说va是首地址

       j=LocateVex(G,vb);

       G.arcs[i][j].adj=w;//有向网

       }//for

       G.kind=DN;

       returnOK;

}

void ShortestPath_DIJ(MGraph G,intv0,PathMatrix &P,ShortPathTable &D)

{

//求有向网络G的v0到其它各个顶点v的最短路径P[v]及带权长度D[v]。

//如果P[v][w]是TRUE,则w是从v0到v当前求得最短路径上的顶点。

//final[v]是TRUE,当且仅当v属于S,即已经求得从v0到v的最短路径。

//D[i]或者是弧(V0到Vi)的权值,或者是D[k](Vk belong to S)和弧(Vk,Vi)上的权值之和。

//用final[]来标识集合S.

int w,v,i,j,min;

Status final[MAX_VERTEX_NUM];

for(v=0;v<G.vexnum;++v)

  {

  final[v]=FALSE;

  D[v]=G.arcs[v0][v].adj; //用邻接矩阵初始化带权路径。

  for(w=0;w<G.vexnum;++w)

          P[v][w]=FALSE; //设空路径,开始时假设任何两点间都不存在路径。

  if(D[v]<INFINITY)

   {

  P[v][v0]=TRUE;  //表达式成立,说明v和v0间存在一条路径,指的是直接线,

                   //这句话的意思是从V0到V点,要经过V0,

  P[v][v]=TRUE;  //也要经过V点,很显然成立,所以初始化。

  }//if 1

  }//for 1

  D[v0]=0; 

  final[v0]=TRUE; //初始化,v0顶点属于S集。

  for(i=1;i<G.vexnum;++i) //求其它G.vexnum-1个顶点

   {

   //开始主循环,每次求得v0到某个顶点的最短路径,并add v to Set of S

          min=INFINITY; //当前所知的离v0最近的距离

          for(w=0;w<G.vexnum;++w)

          if(!final[w]) //w 在V——S集中

                 if(D[w]<min) //存在一条路从V0到w.

                 {

                 v=w;

                 min=D[w];

                 }//if,经过这一轮循环,得出w离v0更近

          final[v]=TRUE; //离v0最近的v加入S集中

          for(w=0;w<G.vexnum;++w)//更新当前最短路径

         if(!final[w]&&min<INFINITY&&G.arcs[v][w].adj<INFINITY&&(min+G.arcs[v][w].adj)<D[w])

                 //修改D[w] and P[w],w属于V——S

          {

          D[w]=min+G.arcs[v][w].adj;

          for(j=0;j<G.vexnum;++j)  //到v要走的路,到w也肯定要走

                 P[w][j]=P[v][j]; 

          P[w][w]=TRUE;    //到W当然要经过w了。 **

          }//if of final[w] 1

  }//for 1

  }

int main()

{

int i,j,v0=0; //vo作为源点

MGraph g;

PathMatrix p;

ShortPathTable d;

CreateDN(g);

ShortestPath_DIJ(g,v0,p,d);

printf("最短路径数组P如下\n");

for(i=0;i<g.vexnum;++i)

  {

  for(j=0;j<g.vexnum;++j)

          printf("%2d",p[i][j]);

   printf("\n");

  }//for

printf("%s到各顶点的最短路径长度是:\n",g.vexs[0]);

for(i=1;i<g.vexnum;++i)

       printf("%s-%s:%d\n",g.vexs[0],g.vexs[i],d[i]);

}//main

3、 需要我们注意的地方

实现过程中要注意的细节,已在注意的注释中作了较为详尽的描述。

1) 算法的核心是找到当前最新的最短路径后,对所有不在S集中的顶点进行更新,好像虚拟出一条条直通路一样,使得V0 到其它可达的节点貌似有一条直通线,而由P[][]来记录中间经过的节点。

2) %*c 的应用

scanf("格式串" ,地址1,地址2,···);

其中的格式符这

syntax: %[*][width][h|l|L]type 

其中的*号表示数据输入项要按指定格式进行转换,但不保存变量,即该%没有对应的变量。常用来吃掉其它字符.

width表示读入多少个字符就结束本数据项的转换。如果没有指定width,则遇到空格 、TAB回车/换行符、非法输入则结束数据项的转换(%c格式除外)。 

比如:

scanf("%d%*c%c%f",&age,&sex,&height);

如输入为:19 W 1.62   /*正确输入,9和W之间只能间隔一个空格,W和1.62之间允许多个空格*/

注意,一个%*c只能吸收掉一个字符。

3) 字符数组及字符串

(1)字符数组定义格式如下:

类型名 数组名[数组元素个数];

其中的“类型名”必须是char

(2)初始化

1)按单个字符的方式赋初值,其中必须有一个字符是字符串的结束标记。例如: 

char s[]={'1', '2','3','\0'};

如果花括号中数据元素的个数小于字符数组定义的数据元素的个数, C语言规定:初始化中未给出数据元素值得对应元素被自动赋值为空字符'\0'

2)直接在初值表中写一个字符串常量。例如:

char s[]={"123"};

其中的“{}”可以省略,如:

char s[]="123";

当需要处理多个字符串时,可以采用二维数组。如

chars[3][4]={"123","ab","A"}; 此时s[0],s[1],s[2]分别为指向字符串的指针了。

(3)输入/输出

关于字符数组的输入/输出,C语言有如下规定:

(A)逐个字符输入/输出。用格式符"%c"输入/输出一个字符。

(B)将整个字符串一次性输入/输出。用格式符"%s"对整个字符串进行一次性输入/输出。

注意:使用"%s"格式从键盘上向字符数组中输入字符串时,回车换行符或空格符号均作为字符串的结束标记。当用scanf函数输入字符串时,字符串中不能含有空格,否则将以空格作为串的结束符。

(C)scanf(“格式串”,字符串首地址)

     printf(“%s”,字符串首地址)

     gets(字符串首地址).

(D)char s[5];

可以用printf(“%s”,s)来输出,也可以用for(i=0;i<strlen(s);i++) printf(“%c”,s[i])来输出。

(4) 涉及的函数

     字符串输出函数puts

      字符串输入函数gets

      测试字符串长度函数strlen

字符串连接函数strcat

字符串比较函数strcmp

字符串拷贝函数strcpy

小写变大写函数strupr

大写变小写函数strlwr
4、算法的改进:
       我们不仅要求记录从源节点到达目的节点的最短路径,我们还要求记录从源节点到达目的节点当中所走过路的先后顺序;此时,在源程序
    P[w][w]=TRUE;    //到W当然要经过w了。 ** 
       这一句后面插入如下函数 void InsertNode(int v, int w)即可。对于InsertNode函数的定义如下所示:
void InsertNode(int v, int w)
{
int i,j,Rec;
for(i=0;Path[w][i] != -1;i++)
    Path[v][i]=Path[w][i];
Rec=i;
for(j=Rec;j<20;j++)
    Path[v][j]=-1;
Path[v][Rec]=w;
}
                        

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值