学习数学建模清风大佬课程时,在图论章节中清风大佬留下了让我们手搓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红色)
库函数效果:
手搓函数效果:文章来源:https://www.toymoban.com/news/detail-767476.html
文章来源地址https://www.toymoban.com/news/detail-767476.html
到了这里,关于用matlab实现Dijkstra算法,内附函数详解的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!