Dijkstra(迪杰斯特拉)算法(C语言)(西工大数据结构)

588 阅读8分钟

今天学了下数据结构,最近国旗班最后一周训练着实使我没什么时间写博客,这也算是挤出点时间把迪杰斯特拉算法好好整了整,我尽量把这个清晰的写出来。这里也参考大佬的代码让我对程序大体有了思路,也算是站在巨人的肩膀上吧。 @TOC

Dijkstra算法

(本质就是贪心)

迪杰斯特拉算法是由荷兰计算机科学家狄克斯特拉于1959年提出
,是从一个顶点到其余各顶点的最短路径算法,解决的是有权图中
最短路径问题。
迪杰斯特拉算法的主要特点是以起始点为中心向外层层扩展(广度
优先搜索思想),直到扩展到终点为止。

迪杰斯特拉算法的成功率是最高的,因为它每次必能搜索到最优路径。
但迪杰斯特拉算法算法的搜索速度是最慢的。迪杰斯特拉算法求一个点
到其他所有点的最短路径时间复杂度为 O ( n 2 )

我这里比较喜欢《小灰的奇妙算法》这本书,感觉比较易懂(啃算法导论的大佬请无视----)这里就把图放在这里了,挺不错的。 步骤如下:

究竟什么是迪杰斯特拉算法?它是如何寻找图中顶点的最短路径呢?

这个算法的本质,是不断刷新起点与其他各个顶点之间的 “距离表”。

让我们来演示一下迪杰斯特拉的详细过程:

第1步,创建距离表。表中的Key是顶点名称,Value是从起点A
到对应顶点的已知最短距离。但是,一开始我们并不知道A到其他
顶点的最短距离是多少,Value默认是无限大:

在这里插入图片描述

2步,遍历起点A,找到起点A的邻接顶点B和C。
从AB的距离是5,从A到C的距离是2。把这一信息刷新到距离表当中:

在这里插入图片描述

3步,从距离表中找到从A出发距离最短的点,也就是顶点C。

第4步,遍历顶点C,找到顶点C的邻接顶点D和F(A已经遍历过,
不需要考虑)。从C到D的距离是6,所以A到D的距离是2+6=8;
从C到F的距离是8,所以从A到F的距离是2+8=10。把这一信息
刷新到表中:

在这里插入图片描述

接下来重复第3步、第4步所做的操作:

第5步,也就是第3步的重复,从距离表中找到从A出发距离最短的点(
C已经遍历过,不需要考虑),也就是顶点B。

第6步,也就是第4步的重复,遍历顶点B,找到顶点B的邻接顶点D和E
(A已经遍历过,不需要考虑)。从B到D的距离是1,所以A到D的距离是
5+1=6,小于距离表中的8;从B到E的距离是6,所以从A到E的距离是5+6=11。
把这一信息刷新到表中:

在这里插入图片描述

(在第6步,A到D的距离从8刷新到6,可以看出距离表所发挥的作用。
距离表通过迭代刷新,用新路径长度取代旧路径长度,最终可以得到从
起点到其他顶点的最短距离)

第7步,从距离表中找到从A出发距离最短的点(B和C不用考虑),也就是顶点D。

第8步,遍历顶点D,找到顶点D的邻接顶点E和F。从D到E的距离是1,
所以A到E的距离是6+1=7,小于距离表中的11;从D到F的距离是2,
所以从A到F的距离是6+2=8,小于距离表中的10。把这一信息刷新到表中:

在这里插入图片描述

9步,从距离表中找到从A出发距离最短的点,也就是顶点E。

第10步,遍历顶点E,找到顶点E的邻接顶点G。从E到G的距离是7,
所以A到G的距离是7+7=14。把这一信息刷新到表中:

在这里插入图片描述

11步,从距离表中找到从A出发距离最短的点,也就是顶点F。

第10步,遍历顶点F,找到顶点F的邻接顶点G。从F到G的距离是3,
所以A到G的距离是8+3=11,小于距离表中的14。把这一信息刷新到表中:

在这里插入图片描述

就这样,除终点以外的全部顶点都已经遍历完毕,距离表中存储的是从
起点A到所有顶点的最短距离。显然,从A到G的最短距离是11。(路径:A-B-D-F-G)

题目

在这里插入图片描述

代码

#include <stdio.h>
#include <stdlib.h>
#define MAX 100
//定义一个结点
//边表
struct edgeNode
{
    int headVex;//相邻顶点字段
    int weight;//权重
    struct edgeNode *next;//链字段
};
//定义一个头节点
//顶点表
struct vexNode
{
    int vex;//顶点信息
    struct edgeNode *head;//边表头指针
};
//定义图
struct graphList
{
    struct vexNode vex[MAX];
    int vexNum;//顶点数
    int edgeNum;//边个数
};

struct step{
    int flags[1000];
    int stepN[1000];
};

//函数申明防止报warning
void run ();
void createNewGraphList (struct graphList *gList);
void clearVexNodes (struct graphList *gList);
void createNewEdgeNode (int n,struct graphList *gList);
void Dikstra(struct step *S,struct graphList *gList);
void clearStep(struct step *S,struct graphList *gList);
void initializeStep(struct step *S,struct graphList *gList);
int judgeStep(struct step *S,struct graphList *gList);
int findMinStepN(struct step *S,struct graphList *gList);
void updateStepN(struct step *S,struct graphList *gList,int min);
void print(struct step *S,struct graphList *gList);
void swap(int *a,int *b);
void quicksort(int array1[],int array2[],int maxlen,int begin,int end);



void createNewGraphList (struct graphList *gList)//创建图
{
    scanf ("%d%d",&(gList->vexNum),&(gList->edgeNum));

    clearVexNodes (gList);//注意此处不加&

    int i,vex;
    for (i=0;i<gList->edgeNum;i++)//添加结点
    {
        scanf ("%d",&vex);
        gList->vex[vex-1].vex=vex;
        createNewEdgeNode (vex-1,gList);//新建结点
    }
}
void createNewEdgeNode (int n,struct graphList *gList)//新建结点
{
    struct edgeNode *p,*q;
    int headVex;
    p=(struct edgeNode *)malloc(sizeof(struct edgeNode));
    scanf ("%d",&headVex);//输入尾
    p->headVex=headVex-1;//逐一此处,因为我们以0为边表第一个位置,所以要-1
    scanf ("%d",&(p->weight));//输入权重
    p->next=NULL;
    //此用尾插
    if (gList->vex[n].head==NULL)//若边表本来无元素
    {
        gList->vex[n].head=p;
    }
    else//边表有元素,将操作位置移动到此边表尾部
    {
        q=gList->vex[n].head;
        while (q->next)
        {
            q=q->next;
        }
        q->next=p;//尾插
    }
}



void clearVexNodes (struct graphList *gList)//图的初始化
{
    int i;
    for (i=0;i<gList->vexNum;i++)
    {
        gList->vex[i].vex=0;//边表信息设为0
        gList->vex[i].head=NULL;//边表结点设为NULL
    }
}


void Dijkstra(struct step *S,struct graphList *gList)//算法主体
{
    int min;
    clearStep (S,gList);//初始化,即清空步数
    initializeStep (S,gList);//初始化
    while (judgeStep (S,gList))//判断步骤
    {
        min=findMinStepN (S,gList);//找最小步
        updateStepN (S,gList,min);//更新步数

    }
}
void clearStep(struct step *S,struct graphList *gList)//初始化,即清空步数
{
    int i;
    for (i=0;i<gList->vexNum;i++)
    {
        S->flags[i]=-1;//-1无路,0以找到最短路径,1未找到最短路径
        S->stepN[i]=0;//到此节点的步数为0
    }
}

void initializeStep(struct step *S,struct graphList *gList)//初始化,即把首元素放进循环开始判断
{
    struct edgeNode *p;
    p=gList->vex[0].head;
    while (p)//循环边表
    {
        S->flags[p->headVex]=1;//设置为未找到最短路径
        S->stepN[p->headVex]=p->weight;//设置到此节点步数
        p=p->next;
    }
}

int judgeStep(struct step *S,struct graphList *gList)//判断步骤(要使所有元素都进行循环并处理找到最小步数)
{
    int i;
    for (i=1;i<gList->vexNum;i++)//循环所有节点
    {
        if (S->flags[i]==1)//若有节点被设为未找到最短路径
        {
            return 1;//返回1,继续循环
        }
    }
    return 0;//返回0,结束循环
}

int findMinStepN(struct step *S,struct graphList *gList)//找最小步(找这次循环要处理的元素)
{
    int i,min=99999,n=-1;
    for (i=1;i<gList->vexNum;i++)//循环所有节点
    {
        if (S->flags[i]==1)//若被设为未找到最短路径
        {
            if (S->stepN[i]<min)//若比已知最小小,就替换
            {
                min=S->stepN[i];
                n=i;
            }
        }
    }
    return n;//返回得到的最小步数的节点的编号
}

void updateStepN(struct step *S,struct graphList *gList,int min)//更新步数
{
    struct edgeNode *p;
    int minStepN=S->stepN[min];
    p=gList->vex[min].head;
    S->flags[min]=0;//将此次循环的元素列为处理过的,以后的循环就不再处理它)
    while (p)//循环边表(循环要此次处理的元素)
    {
        if (S->flags[p->headVex]==1)//若未找到最短路径(即还未处理过,我们知道进入循环,处理过的元素必定是找到最小路径的)
        {
            if (p->weight+minStepN<S->stepN[p->headVex])//若新的走法的步数较少
            {
                S->stepN[p->headVex]=p->weight+minStepN;//就更新到此节点的步数
            }
        }
        else
        {
            if (S->flags[p->headVex]==-1)//若此节点无路到达
            {
                S->flags[p->headVex]=1;//放到下次循环里
                S->stepN[p->headVex]=p->weight+minStepN;//新的走法有路到达,更新其步数
            }
        }
        p=p->next;
    }
}

void print(struct step *S,struct graphList *gList)//输出步骤
{
    int i;
    int nList[3000]={0};//步数
    int noList[3000]={0};//节点编号
    for (i=1;i<gList->vexNum;i++)
    {
        if (S->flags[i]==-1)//若无路到达,步数就是-1
        {
            nList[i]=-1;
        }
        else
        {
            nList[i]=S->stepN[gList->vex[i].vex-1];//否则就是步数
        }
        noList[i]=i+1;//节点编号赋值
    }
    quicksort (nList,noList,gList->vexNum,1,gList->vexNum-1);//将步数从小到大排,编号随之改变
    for (i=1;i<gList->vexNum;i++)
    {
        if (nList[i]>0)//先输出有路到达的
        {
            printf ("1 %d %d\n",noList[i],nList[i]);
        }
    }
    for (i=1;i<gList->vexNum;i++)
    {
        if (nList[i]==-1)//再输出无路到达的
        {
            printf ("1 %d %d\n",noList[i],nList[i]);
        }
    }
}
void swap(int *a,int *b)//交换数值
{
    int temp;
    temp=*a;
    *a=*b;
    *b=temp;
}

void quicksort(int array1[],int array2[],int maxlen,int begin,int end)//以array1排序,同时改变array2
{
    int i,j;
    if(begin<end)
    {
        i=begin + 1;
        j=end;
        while(i<j)
        {
            if(array1[i]>array1[begin])
            {
                swap(&array1[i],&array1[j]);
                swap(&array2[i],&array2[j]);
                j--;
            }
            else
            {
                i++;
            }
        }
        if(array1[i]>=array1[begin])
        {
            i--;
        }
        swap(&array1[begin],&array1[i]);
        swap(&array2[begin],&array2[i]);
        quicksort(array1,array2,maxlen,begin,i);
        quicksort(array1,array2,maxlen,j,end);
    }
}

int main()
{
    struct graphList gList;
    struct step S;
    createNewGraphList(&gList);
    Dijkstra(&S,&gList);
     print (&S,&gList);

}

结果: 在这里插入图片描述