用matlab实现Dijkstra算法,内附函数详解

        学习数学建模清风大佬课程时,在图论章节中清风大佬留下了让我们手搓dijkstra算法的任务,笔者翻阅了CSDN和B站视频,再加上自己对代码和matlab的理解,手搓了一版dijkstra算法函数,代码如果有考虑不周,欢迎各位看官指出!!!

1.理论粗讲

        首先,还是来先了解一下dijkstra算法是啥。这个相信大家在点进来之前已经翻阅过相应资料了,毕竟已经到了手搓阶段。不了解的小伙伴们也不要急,我们先看看这个算法到底是个啥,手搓阶段的大佬们可以直接跳过,不过当作复现算法的参考也是不错的啦。

        dijkstra算法解决的是图论中的最短距离问题,从它的解决过程中,可以看到贪心算法的影子,就是只关心最短的距离(也就是程序中的最小值,在最小值所在节点的基础上,加上距离,然后循环往复,最终达到终点)

        就如我们假设从零节点出发,很显然只有两条路,到1距离为4,到7距离为8,取最小也就是1节点,再在1节点基础上加距离,也就是到2距离为12(注意这个12是从起始点开始的距离),到7距离为7,再取最小也就是7再次加距离,依次循环后到达终点。

        这时我们不难看出,从上面这个例子,7这个节点有两个距离,一个8,一个7,不要忘了我们的目的是求最小距离,所有我们直接将7这个节点到初始点的距离由8更新到了7。看到这里了,不妨停下来品一品,是不是可以看出一些这个算法的奥秘?没错,这个算法是在不断循环中更新的,而且更新的距离,是当前节点到初始点的最短距离,也就是说,如果我们设置一个结束节点它满足在运算过程中能访问所有节点的话,所有节点到初始点的最短距离就可以求出来了!是不是很神奇!!!

2.代码前置内容

        好了,我们在之前浅浅讲了一下dijstra算法,那后面就是手搓阶段了。众所周知,学习了理论知识可不够,程序和理论之间可是还有差别滴。在手搓阶段,我参考了清风大佬和B站视频图论最短距离(Shortest Path)算法动画演示-Dijkstra和Floyd。让我们截取其中一帧画面来更好的讲述后续操作。

可以看到这里定义了三个数组:

Visited:用于标志已访问数组,访问后的节点后续遍历是当然不能使用啦,已经是最小距离了,可不能乱加距离。访问后要屏蔽靠的就是这个数组。

Distance:顾名思义,用于标记最短距离

Parent:用来标记从起点开始,此节点的上一级节点是啥。可以看这张截屏的红线(忽略2~3节点的那条和7~6的那条),这条红线表示的就是0-4节点的最短距离,那我们从paraent的4号位置看起,4的上一级是5,5的上一级是2,以此类推,是不是串联起来了一条0-1-7-8-2-5-4的最短距离。

3.代码实现

ok!!那接下来进入function time!

本例题使用例子就是上图截屏中使用的图

D代表此图的权重邻接矩阵

function [P1,d1] = Dijkstra(D,start,end_)
    % clear;clc
    % load D.mat
    % start = 9
    % end_ = 4
    n = size(D,1);                                                         %确定节点个数      
    Dis = zeros(2,n);                                                      %创建一个二维数组,第一行表示当前循环下节点序号,第二行表示对应距离
    %e.g:
    %以此次权重邻接矩阵第一次循环为例:
    %—————————————————————————————————————————————————
    %|  1    2    3    4    5    6    7    8    9    |
    %|———————————————————————————————————————————————|
    %|  4   inf  inf  inf  inf  inf   8   inf  inf   |
    %|———————————————————————————————————————————————|
    % Dis数组表示从9号节点出发,每个节点与之距离的数组(注意在循环内有另外的含义,在后续循环中有注释!)
    Dis(1,:) = [1:n];                                                      %这一行将Dis第一行设置成1到n的节点序号
    Dis(2,:) = inf;                                                        %这一行将Dis第二行设置成无限大小(初始化:因为后续要有比小操作,设置成inf不会对后续操作有影响)
    Visited = zeros(1,n);                                                  %Visited数组用来标记对应节点是否已访问
    Distance = inf(1,n);                                                   %这是记录距离的数组,与Dis的区别在循环中体现
    Parents = inf(1,n);                                                    %Parents数组用来标记对应节点父节点,也就是该节点与哪一个节点串联                                    
    k = [start];                                                           %k数组用于存放对应节点是否已访问
    % 初始化
    Visited(start) = 1;                                                    %将初始节点设置成已访问
    for i = 1:n                                                            %此循环为初始化循环,用于从初始点开始第一次遍历
        Distance(i) = D(start,i);                                          %之所以和大循环分开主要考虑了这句语句,由于此程序Distan数组设置成inf数组,所以若用大循环中的处理会导致第一次遍历无效
        Parents(i) = start
    end
    Dis(2,:) = Distance;                                                   %将变换后的Distance数组录入Dis数组第二行
    Dis(:,start) = []                                                      %删去已访问的节点(为什么要删去?因为我们之后操作要取最小距离,若不删去已访问节点,会干扰取值时的准确性)
    while Visited(end_) ~= 1
        [~,min_index] = min(Dis(2,:));                                    
        min_index = Dis(1,min_index);                                      %这句话就很好的体现了Dis数组第一行的作用,由于每次循环,Dis数组都会删去已访问节点,Dis数组第二行下标并不对应对应节点序号,第一行的目的就是为了存放最小距离对应节点序号
        Visited(min_index) = 1
        for i = 1:n
            if D(min_index,i) ~= inf && Visited(i) ~= 1                    %防止程序对已访问节点的距离做变更,D(min_index,i) ~= inf条件可省略
                    if D(min_index,i) + Distance(min_index) < Distance(i)  %判断距离大小
                        Distance(i) = D(min_index,i) + Distance(min_index);%若小于已有距离,更新Distance数组,并更新Parents数组
                        Parents(i) = min_index
                    end
            end
        end
        k = [k,Dis(1,find(Dis(1,:) == min_index,1))];                      %k的更新逻辑和Visited数组一样,已访问的节点存入k数组
        Dis = [[1:n];Distance];                                            %更新Dis数组,先更新距离,用重新赋值数组的方式
        Dis(:,k) = [];                                                     %再删除已访问的节点,防止对min操作产生影响
    end
    %输出  最短路径经过的节点 和 最短距离
    node = Parents(end_);
    P1 = [end_];
    while node ~= start
        P1 = [P1;node];
        node = Parents(node);
    end
    P1 = [P1;start];
    d1 = Distance(end_);
end

再在主函数调用一下,和matlab库函数compete一下

clear;clc
load D.mat

s = [9 9 1 1 2 2 2 7 7 6 6  5  5 4];
t = [1 7 7 2 8 3 5 8 6 8 5  3  4 3];
w = [4 8 3 8 2 7 4 1 6 6 2 14 10 9];
G = graph(s,t,w);
figure(1)
plot(G, 'EdgeLabel', G.Edges.Weight, 'linewidth', 2) 
set( gca, 'XTick', [], 'YTick', [] );  
[P,d] = shortestpath(G, 9, 4)  %注意:该函数matlab2015b之后才有哦

% 在图中高亮我们的最短路径
myplot = plot(G, 'EdgeLabel', G.Edges.Weight, 'linewidth', 2);  %首先将图赋给一个变量
highlight(myplot, P, 'EdgeColor', 'r')   %对这个变量即我们刚刚绘制的图形进行高亮处理(给边加上r红色)


%手搓的Dijkstra算法
figure(2)
plot(G, 'EdgeLabel', G.Edges.Weight, 'linewidth', 2) 
set( gca, 'XTick', [], 'YTick', [] );  
[P1,d1] = Dijkstra(D,9,4)
myplot = plot(G, 'EdgeLabel', G.Edges.Weight, 'linewidth', 2);  %首先将图赋给一个变量
highlight(myplot, P1, 'EdgeColor', 'r')   %对这个变量即我们刚刚绘制的图形进行高亮处理(给边加上r红色)

库函数效果:

手搓函数效果:

  • 15
    点赞
  • 51
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值