43. 数据结构笔记之四十三最短路径之迪杰斯特拉(Dijkstra )算法
“辛勤的蜜蜂永没有时间悲哀。— 布莱克”
这次来看下Dijkstra )算法。还是老方法,先原理,后实现。代码来自网络。
1. 最短路径
最短路径问题是图论研究中的一个经典算法问题, 旨在寻找图(由结点和路径组成的)中两结点之间的最短路径。 管道铺设、线路安排、厂区布局、设备更新等
Dijkstra算法能得出最短路径的最优解,但由于它遍历计算的节点很多,所以效率低。
2. Dijkstra算法
迪杰斯特拉算法是由荷兰计算机科学家狄克斯特拉于1959 年提出的,因此又叫狄克斯特拉算法。是从一个顶点到其余各顶点的最短路径算法,解决的是有向图中最短路径问题。迪杰斯特拉算法主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止。
3. 问题描述
在无向图 G=(V,E)中,假设每条边 E[i] 的长度为 w[i],找到由顶点 V0 到其余各点的最短值。
Dijkstra(迪杰斯特拉)算法是典型的单源最短路径算法,用于计算一个节点到其他所有节点的最短路径。主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止。Dijkstra算法是很有代表性的最短路径算法,在很多专业课程中都作为基本内容有详细的介绍,如数据结构,图论,运筹学等等。注意该算法要求图中不存在负权边。
问题描述:在无向图 G=(V,E)中,假设每条边 E[i] 的长度为 w[i],找到由顶点 V0 到其余各点的最短路径。(单源最短路径)
4. 算法描述
设G=(V,E)是一个带权有向图,把顶点集合V分成两组,第一组为已求出最短路径的顶点集合(用S表示,初始时S中只有一个源点,以后每求得一条最短路径 , 就将加入到集合S中,直到全部顶点都加入到S中,算法就结束了),第二组为其余未确定最短路径的顶点集合(用U表示),按最短路径长度的递增次序依次把第二组的顶点加入S中。在加入的过程中,总保持从源点v到S中各顶点的最短路径长度不大于从源点v到U中任何顶点的最短路径长度。此外,每个顶点对应一个距离,S中的顶点的距离就是从v到此顶点的最短路径长度,U中的顶点的距离,是从v到此顶点只包括S中的顶点为中间顶点的当前最短路径长度。
5. 算法步骤
a) 初始时,S只包含源点,即S={v},v的距离为0。U包含除v外的其他顶点,即:U={其余顶点},若v与U中顶点u有边,则<u,v>正常有权值,若u不是v的出边邻接点,则<u,v>权值为∞。
b) 从U中选取一个距离v最小的顶点k,把k,加入S中(该选定的距离就是v到k的最短路径长度)。
c) 以k为新考虑的中间点,修改U中各顶点的距离;若从源点v到顶点u的距离(经过顶点k)比原来距离(不经过顶点k)短,则修改顶点u的距离值,修改后的距离值的顶点k的距离加上边上的权。
d) 重复步骤b和c直到所有顶点都包含在S中。
6. 代码实现
6.1 定义
定义两个结构体,通用的顶点数据结构体和顶点元素信息。
typedefstruct_vertex{ //通用的顶点数据结构体
intid;//id
struct_vertex*pLinkList[MAX_EDGE_NUM]; //相邻顶点的指针数组
intnCost[MAX_VERTEX_NUM]; //与相邻顶点的权重数组
struct_vertex**next; //与剩余顶点之间的最短路径
int*pmincost; //与剩余顶点之间的最小代价
}vertex;
typedefstruct_node { //组成图的顶点元素信息进行封装
intnID;
struct_vertex*pVer;
}node;
6.2 Main
注意:运行时候需要指定1个参数。
通过InitGraphic函数来初始化图。
调用ViewGraphic函数来打印图。
调用Dijkstra函数来调用迪杰特斯拉算法。
如果调用失败,则调用UnitGraphic函数。
然后调用循环打印最优路径。
输入1打印最优路径(输入起始和结束点的ID,然后调用MinRoute函数打印最优路径。)
输入2 退出程序
其他则重新输入
退出时候调用UnitGraphic函数。
6.3 InitGraphic
根据图的信息文件,初始化数组。
文件内容如下:
1:2-2:3-4:5-4;
2:1-2:3-3;
3:1-4:2-3:5-6:6-7;
4:6-8;
5:1-4:3-6;
6:3-7:4-8;
主要内容是:
打开参数指定的文件。
先调用memset函数初始化数组内容都为0。
打开文件为只读模式。将文件内容一行读到buf中(文件每行不能超过256个字符)。
根据:来分割字符串,获取第一个字符,并转换为整型,如果小于100则继续处理,否则提示超过界限。
如果小于100,则设置数组的下一个值为该值。
根据顶点的数量,逐个获取顶点,以及顶点后方的字符串2-2 3-4 5-4等(2-2第一个2表示和第2个节点相邻,第二个2表示到第2个节点的代价)。
设置该顶点的相邻指针。
设置该顶点到该相邻顶点的cost.
将与剩余顶点之间的最小代价为NULL
将与剩余顶点之间的最路径设置为NULL
于该顶点相邻的指数自加1,遇到每行的“;”表示该点处理完毕。
循环处理完所有的顶点。
然后关闭文件。
6.4 UnitGraphic
释放在InitGraphic函数中分配的空间。
6.5 ViewGraphic
打印图的结构信息
根据所有的顶点总数进行打印。
打印该顶点的ID号,然后依次打印和该顶点链接的顶点,以及代价。
打印完毕一个顶点后,继续打印其他的顶点。
6.6 Dijkstra
该函数实现依次将每个节点作为起始节点,计算剩余节点与其之间的最短路径
具体如下:
先分配一块和顶点数量一样多的整型空间用于存放其余及诶单到起始节点的最小代价tcost。
再分配一块和顶点数量一样多的BOOL空间用于判断节点是否计算完毕pbDone。
然后从第一个节点开始,依次将每个顶点作为起始节点
先初始化其他节点到起始节点的代价为无限大(每个顶点开始都要操作该步骤)。
然后malloc储存每个顶点最优的前驱顶点的id的数组
接着malloc储存每个顶点到起始顶点的最小代价数组
考虑到本节点和本节点的特殊性(先处理之)
接着开始循环获取其余节点的代价。得到其余顶点到该节点的代价数组。
然后找出更新后,所有顶点中,代价最小的顶点(去掉无限远和距离为0的点)
如果除起始顶点外的所有节点都已经被处理完毕,则退出进入到下一个顶点的寻找旅程。
记录该点到下一层的最小代价,然后从最短路劲的那个点继续开始寻找下一层的最短路径。
处理完一个顶点后,进入到顶一个顶点处理,直到结束。
最后释放临时分配的存储空间。
其中pbegin为每次的起始顶点,在从一个顶点开始寻找最短路径的时候该值一直不变,直到以下一个顶点为开始顶点。Arr为图的数组。Nidtemp为与当前节点相邻的其它节点中,cost最小的顶点序号。
BOOL Dijkstra(nodearr[])
{
UINTi, j, k;
vertex*pbegin, *ptemp, *ptemp1;
int*tcost;//用于储存其余节点到起始节点的最小代价
BOOL*pbDone;//用于判断节点是否计算完毕的数组
intnidtemp;//与当前节点相邻的其它节点中,cost最小的顶点序号
intnmixcost = INFINITE;
tcost = (int*)malloc(g_node_num* sizeof(int));
pbDone = (BOOL*)malloc(g_node_num* sizeof(BOOL));
if(NULL== tcost || NULL == pbDone) {
printf(“out of memory\n”);
returnFALSE;
}
for(i=0;arr[i].pVer!=0;i++) {//依次将每个顶点作为起始节点
for(j=0;j<g_node_num; j++) {//初始化数组
tcost[j] = INFINITE;//其它节点到起始节点的代价
pbDone[j] = 0;
}
pbegin= arr[i].pVer;//起始顶点
pbegin->next = (vertex**)malloc(g_node_num* sizeof(vertex*));//储存每个顶点最优的前驱顶点的id的数组
pbegin->pmincost = (int*)malloc(g_node_num* sizeof(int));//储存每个顶点到起始顶点的最小代价数组
tcost[i] = 0;//初始化
pbDone[i] = 1;
pbegin->pmincost[i] = 0;
ptemp= pbegin;//设定起始顶点为当前顶点
while(1){
for(j=0;ptemp->pLinkList[j]!=0; j++) {//遍历当前顶点的相邻节点,更新最小代价(松弛边)
ptemp1 = ptemp->pLinkList[j];
if(tcost[ptemp1->id-1] >tcost[ptemp->id-1] + ptemp->nCost[ptemp1->id-1] \
&& pbDone[ptemp1->id-1] == 0) {
tcost[ptemp1->id-1] = tcost[ptemp->id-1] +ptemp->nCost[ptemp1->id-1];
pbegin->next[ptemp1->id-1] = ptemp;//设定顶点更新后的前驱顶点
}
}
nmixcost = INFINITE;
for(j=0;j<g_node_num; j++) {//找出更新后,所有顶点中,代价最小的顶点,重新作为当前顶点。这一步可以优化。
if(pbDone[arr[j].nID-1]!= 1 && tcost[arr[j].nID-1]< nmixcost && tcost[arr[j].nID-1]!= 0) {
nmixcost = tcost[arr[j].nID-1];
nidtemp = arr[j].nID;
}
}
if(nmixcost== INFINITE) {//除起始顶点外的所有节点都已经被处理完毕,退出
break;
}
pbegin->pmincost[nidtemp-1] = nmixcost;
ptemp = arr[nidtemp-1].pVer;//重新设定当前顶点
pbDone[nidtemp-1] = 1;//表示当前顶点已经被处理过了,其路径已经最短,代价最小
}
}
free(pbDone);
free(tcost);
returnTRUE;
}
6.7 MinRoute
根据设定的起始和终止节点序号,打印最小代价。
最小代价直接通过起始点的pmincost数组中提取即可。
最优路径通过回溯方法进行,通过next数组获得前驱顶点,然后前驱顶点再获得前驱顶点直到召开开始的点。
7. 源码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#defineMAX_VERTEX_NUM100 //最大顶点数
#defineMAX_EDGE_NUM50 //相邻最大节点数
#defineINFINITE1E5 //表示节点之间无连接的一个较大的数
#defineMAX_STRLEN256 //最大字符串字节数
#defineFALSE 0
#defineTRUE 1
typedefint BOOL;
typedefunsignedint UINT;
#defineSAFEFREE(p){if(NULL!=(p))free(p);}
typedefstruct_vertex{ //通用的顶点数据结构体
intid;//id
struct_vertex*pLinkList[MAX_EDGE_NUM]; //相邻顶点的指针数组
intnCost[MAX_VERTEX_NUM]; //与相邻顶点的权重数组
struct_vertex**next; //与剩余顶点之间的最短路径
int*pmincost; //与剩余顶点之间的最小代价
}vertex;
typedefstruct_node{ //组成图的顶点元素信息进行封装
intnID;
struct_vertex*pVer;
}node;
int g_node_num;/*用于计算实际节点数的全局变量*/
/****************************************
*函数名:InitGraphic
*参数:path-图的信息文件路径;arr-储存图的数组;nsize-数组大小
*返回值:BOOL-成功返回1,错误返回0
*说明:根据图的信息文件,初始化数组
*****************************************/
BOOL InitGraphic(charpath[],nodearr[],UINTnsize)
{
charbuf[MAX_STRLEN];
char*pos;
charctemp;
intncost;
inti;
UINTnid;//临时顶点ID
UINTncid;//临时连接顶点的ID
UINTnlinkpos;//连接顶点数组中的位置
memset(arr,0, sizeof(node)*nsize);//赋值0
FILE*pfile = fopen(path, “r”);
if(NULL== pfile) {
printf(“Error opening file.\n”);
returnFALSE;
}
while(NULL!= fgets(buf, MAX_STRLEN, pfile)) {
pos =strtok(buf, “:”);//读取一行,解析第一个冒号之前的标号,即第几个节点
nid =atoi(pos);
if(nid< nsize) {
arr[nid-1].nID= nid;
arr[nid-1].pVer= (vertex*)malloc(sizeof(vertex));//申请一个顶点struct
if(NULL== arr[nid-1].pVer) {
printf(“out of memory!\n”);
returnFALSE;
}
memset(arr[nid-1].pVer, 0, sizeof(vertex));//赋值0
arr[nid-1].pVer->id= nid;
g_node_num++;//节点数加1
} else {
fprintf(stderr, “accessthe boundary of setting:%d\n”, nid);
}
}
rewind(pfile);//文件指针跳转到开始处,读取各顶点的相邻节点
for(i=0;i<g_node_num; i++) {
fscanf(pfile, “%d”,&nid);//读取第一个节点标号
nlinkpos = 0;//指示其相邻节点结构体的当前位置
while((ctemp=fgetc(pfile))!= ‘;’) {
fscanf(pfile, “%u-%d”,&ncid, &ncost);
if(ncid> nsize || ncost < 0) {
fprintf(stderr, “accessthe boundary of setting or find negative cost:%u-%d\n”,ncid, ncost);
returnFALSE;
}
arr[nid-1].pVer->pLinkList[nlinkpos]= arr[ncid-1].pVer;/*相邻节点指针数组赋值*/
arr[nid-1].pVer->nCost[ncid-1]= ncost;/*此节点到相邻节点的cost*/
arr[nid-1].pVer->pmincost= NULL;
arr[nid-1].pVer->next= NULL;
nlinkpos++;
}
}
fclose(pfile);
returnTRUE;
}
/*******************************************
*函数名:ViewGraphic
*参数:arr-图的数组
*返回值:无
*说明:打印图的结构信息
*******************************************/
void ViewGraphic(nodearr[])
{
inti, j;
intnidtemp;//临时节点序号
printf(“\nID\tConncetedto-ID:cost”);
for(i=0;i<g_node_num; i++) {
printf(“\n%d\t”,arr[i].nID);
for(j=0;arr[i].pVer->pLinkList[j]!= NULL; j++) {
nidtemp = arr[i].pVer->pLinkList[j]->id;
printf(“%d:”, nidtemp);
printf(“%d “,arr[i].pVer->nCost[nidtemp-1]);
}
}
}
/*************************************************
*函数名:Dijkstra
*参数:arr-图的数组
*返回值:TRUE-成功;FALSE-失败
*说明:依次将每个节点作为起始节点,计算剩余节点与其之间的最短路径
*************************************************/
BOOL Dijkstra(nodearr[])
{
UINTi, j, k;
vertex*pbegin, *ptemp, *ptemp1;
int*tcost;//用于储存其余节点到起始节点的最小代价
BOOL*pbDone;//用于判断节点是否计算完毕的数组
intnidtemp;//与当前节点相邻的其它节点中,cost最小的顶点序号
intnmixcost = INFINITE;
tcost = (int*)malloc(g_node_num* sizeof(int));
pbDone = (BOOL*)malloc(g_node_num* sizeof(BOOL));
if(NULL== tcost || NULL == pbDone) {
printf(“out of memory\n”);
returnFALSE;
}
for(i=0;arr[i].pVer!=0;i++) {//依次将每个顶点作为起始节点
for(j=0;j<g_node_num; j++) {//初始化数组
tcost[j] = INFINITE;//其它节点到起始节点的代价
pbDone[j] = 0;
}
pbegin= arr[i].pVer;//起始顶点
pbegin->next = (vertex**)malloc(g_node_num* sizeof(vertex*));//储存每个顶点最优的前驱顶点的id的数组
pbegin->pmincost = (int*)malloc(g_node_num* sizeof(int));//储存每个顶点到起始顶点的最小代价数组
tcost[i] = 0;//初始化
pbDone[i] = 1;
pbegin->pmincost[i] = 0;
ptemp= pbegin;//设定起始顶点为当前顶点
while(1){
for(j=0;ptemp->pLinkList[j]!=0; j++) {//遍历当前顶点的相邻节点,更新最小代价(松弛边)
ptemp1 = ptemp->pLinkList[j];
if(tcost[ptemp1->id-1] >tcost[ptemp->id-1] + ptemp->nCost[ptemp1->id-1] \
&& pbDone[ptemp1->id-1] == 0) {
tcost[ptemp1->id-1] = tcost[ptemp->id-1] +ptemp->nCost[ptemp1->id-1];
pbegin->next[ptemp1->id-1] = ptemp;//设定顶点更新后的前驱顶点
}
}
nmixcost = INFINITE;
for(j=0;j<g_node_num; j++) {//找出更新后,所有顶点中,代价最小的顶点,重新作为当前顶点。这一步可以优化。
if(pbDone[arr[j].nID-1]!= 1 && tcost[arr[j].nID-1]< nmixcost && tcost[arr[j].nID-1]!= 0) {
nmixcost = tcost[arr[j].nID-1];
nidtemp = arr[j].nID;
}
}
if(nmixcost== INFINITE) {//除起始顶点外的所有节点都已经被处理完毕,退出
break;
}
pbegin->pmincost[nidtemp-1] = nmixcost;
ptemp = arr[nidtemp-1].pVer;//重新设定当前顶点
pbDone[nidtemp-1] = 1;//表示当前顶点已经被处理过了,其路径已经最短,代价最小
}
}
free(pbDone);
free(tcost);
returnTRUE;
}
/**********************************************************
*函数名:MinRoute
*参数:arr-图的数组;nSrID-起始节点序号;nDsID-目的节点序号
*返回值:无
*说明:给定图的数组,利用Dijkstra函数处理之后,根据设定的起始和终止节点序号,打印
*最短路径和最小代价。
***********************************************************/
void MinRoute(nodearr[],UINTnSrID, UINTnDsID)
{
if(nSrID<0|| nSrID>g_node_num || nDsID<0|| nDsID>g_node_num) {
printf(“Invalid node number!\n”);
}
intnid;
vertex*ptemp = arr[nSrID-1].pVer;
printf(“thetotal cost is: %d\n”, ptemp->pmincost[nDsID-1]);
printf(“thepath is:”);
nid = nDsID;
printf(“%d->”,arr[nid-1].nID);
while(ptemp->next[nid-1]->id!= arr[nSrID-1].nID){
nid =ptemp->next[nid-1]->id;//回溯路径
printf(“%d->”,nid);
}
printf(“%d\n”,arr[nSrID-1]);
}
/*****************************************
*函数名:UnitGraphic
*参数:arr-图的数组
*返回值:无
*说明:释放内存
*****************************************/
void UnitGraphic(nodearr[])
{
UINTi;
for(i=0;i<g_node_num; i++) {
if(arr[i].pVer!= NULL) {
SAFEFREE(arr[i].pVer->next);
SAFEFREE(arr[i].pVer->pmincost);
}
}
}
int main(intargc, char *argv[])
{
charfilepath[MAX_STRLEN];//图的信息文件
nodegraphic[MAX_VERTEX_NUM] = {0};//图的数组
intsid, did;
intselnum;
if(argc< 2) {
printf(“usage:*.exe input\n”);
exit(1);
}
strcpy(filepath, argv[1]);
/***********初始化图***************/
if(!InitGraphic(filepath,graphic, MAX_VERTEX_NUM)) {
UnitGraphic(graphic);
exit(1);
}
printf(“**** Print The Graphic information ****”);
ViewGraphic(graphic);//打印图
/************dijkstra运算***********/
if(!Dijkstra(graphic)){
UnitGraphic(graphic);
exit(1);
}
printf(“\n****Findthe shortest path between nodes****”);
printf(“\n1.Viewminimum route between nodes.”);
printf(“\n2.Exit.”);
for(;;){
printf(“\nEnter Your Selection:”);
scanf(“%d”,&selnum);
switch(selnum) {
case1: {
printf(“\nEnter source node ID:”);
scanf(“%d”,&sid);
printf(“\nEnter destination node ID:”);
scanf(“%d”,&did);
MinRoute(graphic, sid, did);//打印最优路径
break;
}
case 2:
exit(1);
default:{
printf(“\nEnter proper choice.\n”);
break;
}
}
}
UnitGraphic(graphic);
return0;
}