数学建模之图论

1 图的基本概念


图论中的图(Graph)是由若干给定的点及连接两点的线所构成的图形,这种图形通常用来描述某些事物之间的某种特定关系,用点代表事物,用连接两点的线表示相应两个事物间具有这种关系。

一个图可以用数学语言描述为:G(V(G),E(G))V(vertex)指的是图的顶点集,E(edge)指的是图的边集。

根据边是否有方向,可将图分为无向图有向图。另外,有些图的边上还可能有权值,这样的图称为有权图

在这里插入图片描述

2 如何做图

2.1 直接做图


在线做图工具的网址

在这里插入图片描述

2.2 编程做图


无向图

  • graph(s,t):可在 st中的对应节点之间创建边,并生成一个图
  • graph(s,t,w):可在 st 中的对应节点之间以 w 的权重创建边,并生成一个图

要做出有向图,只需要将 graph 改为 digraph 就行了。

1️⃣ 无向图

% 无权重,也可以说每条边的权重默认为1
s1 = [1,2,3,4];
t1 = [2,3,1,1];
% 函数graph(s,t):可在 s 和 t 中的对应节点之间创建边,并生成一个图
% s 和 t 都必须具有相同的元素数;这些节点必须都是从1开始的正整数,或都是字符串元胞数组。
G1 = graph(s1, t1);
plot(G1)

% 注意字符串元胞数组是用大括号包起来的哦
s2 = {'学校','电影院','网吧','酒店'};
t2 = {'电影院','酒店','酒店','KTV'};
G2 = graph(s2, t2);
plot(G2, 'linewidth', 2)  % 设置线的宽度
% 下面的命令是在画图后不显示坐标
set( gca, 'XTick', [], 'YTick', [] );  

% 有权重
s = [1,2,3,4];
t = [2,3,1,1];
w = [3,8,9,2];
% 函数graph(s,t,w):可在 s 和 t 中的对应节点之间以w的权重创建边,并生成一个图
G = graph(s, t, w);
plot(G, 'EdgeLabel', G.Edges.Weight, 'linewidth', 2) 
set( gca, 'XTick', [], 'YTick', [] );  

在这里插入图片描述
在这里插入图片描述

❗️注意:

  • 注意哦,编号只能从1开始连续编号(否则会报错),不要自己随便定义编号,因为默认连续编号,不写也有
s = [1,2,3,50];
t = [2,3,1,1];
G = graph(s, t);
plot(G)

在这里插入图片描述

2️⃣ 有向图

% 无权图 digraph(s,t)
s = [1,2,3,4,1];
t = [2,3,1,1,4];
G1 = digraph(s, t);
plot(G1)
set( gca, 'XTick', [], 'YTick', [] );  

% 有权图 digraph(s,t,w)
s = [1,2,3,4];
t = [2,3,1,1];
w = [3,8,9,2];
G2 = digraph(s, t, w);
plot(G2, 'EdgeLabel', G.Edges.Weight, 'linewidth', 2) 
set( gca, 'XTick', [], 'YTick', [] );  

在这里插入图片描述

☀️ 总结:

  • Matlab做出来的图不是很漂亮,要是节点比较少,还是推荐使用在线

3 权重邻接矩阵


3.1 无向图


在这里插入图片描述

3.2 有向图


在这里插入图片描述

4 Dijkstra 算法

4.1 算法概述


图中有 0~8 共九个地点,地点之间若用直线连接,则表明两地可直接到达,直线旁的数值表示两地的距离。

问起点为0,终点为4,怎么走路程最短?


在这里插入图片描述

使用 Dijkstra 算法解决上述问题

1️⃣ 初始化

Visited:所有的节点都是未访问的状态;
Distance:所有节点间的距离都是 Inf
Parent:所有节点的父节点(上一个节点)都是 -1,表示不存在

在这里插入图片描述

1️⃣ 起点是0,更新表格:

  • 节点0的访问状态变为1
  • 节点0对应的距离变为0
  • 节点0的父节点用0表示,当然用其他符号表示都可以

在这里插入图片描述

2️⃣ 更新与节点0(A)相邻节点(B)的信息,注意,这里的B节点是未访问的

  • 如果A与B的距离 + A的距离小于B的距离,那么我们就将B的距离更新为较小的距离,并将B的父节点更新为A,并建行较小距离的节点纳入访问过的节点中

在这里插入图片描述

在这里插入图片描述

3️⃣ 更新与节点1(A)相邻节点(B)的信息,注意,这里的B节点是未访问的

  • 如果A与B的距离 + A的距离小于B的距离,那么我们就将B的距离更新为较小的距离,并将B的父节点更新为A,并建行较小距离的节点纳入访问过的节点中

在这里插入图片描述

在这里插入图片描述

4️⃣ 重复上述步骤,最终得到:

在这里插入图片描述

根据以上结果,我们可以得到节点0到节点4的最短路径:

在这里插入图片描述

Dijkstra 算法一般用于无向图求最短路径,也可以用于有向图,但是Dijkstra 算法的一个缺点即不能用于处理有负权重的图

在这里插入图片描述


Bellman‐Ford 算法


为了解决Dijkstra 算法不能用于处理有负权重图的缺点,提出了 Bellman‐Ford 算法

事实上,Bellman‐Ford 算法不再将节点区分为是否已访问的状态,因为Bellman‐Ford 算法是利用循环来进行更新权重的,且每循环一次,Bellman‐Ford 算法都会更新所有的节点的信息。

Bellman‐Ford 算法不支持含有负权回路的图(Floyd算法也不可以)

在这里插入图片描述

4.2 代码实现


[P,d] = shortestpath(G,start,end [,'Method',algorithm])

  • 功能:返回图 Gstart 节点到 end 节点的最短路径

  • 输入参数:

    • G: 输入图(graph 对象 或 digraph 对象)
    • start :起始的节点
    • end :目标的节点
    • [,‘Method’,algorithm]:是可选的参数,表示计算最短路径的算法。一般我们不用手动设置,默认使用的是 auto
      在这里插入图片描述
  • 输出参数:

    • P 最短路径经过的节点
    • d 最短距离

示例代码:

%% 注意:以下代码需要较新版本的matlab才能运行(最好是2016版本及以上)
% 如果运行出错请下载新版的matlab代码再运行

% 注意哦,Matlab中的图节点要从1开始编号,所以这里把0全部改为了9
% 编号最好是从1开始连续编号,不要自己随便定义编号
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);
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红色)

% 求出任意两点的最短路径矩阵
D = distances(G)   %注意:该函数matlab2015b之后才有
D(1,2)  % 1 -> 2的最短路径
D(9,4)  % 9 -> 4的最短路径

% 找出给定范围内的所有点  nearest(G,s,d)
% 返回图形 G 中与节点 s 的距离在 d 之内的所有节点
[nodeIDs,dist] = nearest(G, 2, 10)   %注意:该函数matlab2016a之后才有

输出图形:

在这里插入图片描述

5 Floyd 算法

5.1 算法概述


Floyd 算法是解决任意两点间的最短路径的一种算法,可以正确处理无向图或有向图(可以有负权重,但不可存在负权回路)的最短路径问题。

Floyd 算法与 Dijkstra 算法或Bellman‐Ford 算法相比,能够一次性的求出任意两点之间的最短路径,后两种算法运行一次只能计算出给定的起点和终点之间的最短路径。当然,Floyd 算法计算的时间也要高于后两种算法,其算法核心的步骤由三层循环构成。

算法动画展示

在这里插入图片描述
从上面观察到的两个结论中,我们不难提炼出下面这个思想:

  • 假设现在有一个起点A和终点B,那么对于其他任意的中间点M:
    D(A,B) ≤ D(A,M) + D(M,B) ,这里,D(X,Y)表示X和Y两点之间的最短距离。

因此,Floyd算法实际上核心在于一个三层循环

5.2 代码实现


☀️ 实现计算任意两点之间的最短路径的距离


在这里插入图片描述


☀️ 求最短路径(记录最短路径经过的点)


在这里插入图片描述

在这里插入图片描述

将伪代码转换为 Matlab 代码

在这里插入图片描述


1️⃣ 定义Floyd 算法的函数Floyd_algorithm.m

function [dist,path] = Floyd_algorithm(D)
%% 该函数用于求解一个权重邻接矩阵任意两个节点之间的最短路径
% 输入:
%        D是权重邻接矩阵
% 输出:
%        dist是最短距离矩阵,其元素dist_ij表示表示i,j两个节点的最短距离
%        path是路径矩阵,其元素path_ij表示起点为i,终点为j的两个节点之间的最短路径要经过的节点

n = size(D,1);  % 计算节点的个数

% 初始化dist矩阵
dist = D;

% 下面我们来初始化path矩阵
path = zeros(n);
for j = 1:n
    path(:,j) = j;   % 将第j列的元素变为j
end
for i = 1:n
    path(i,i) = -1;  % 将主对角线元素变为-1
end

% 下面开始三个循环
for k=1:n    % 中间节点k从1- n 循环
   for i=1:n     % 起始节点i从1- n 循环
      for j=1:n    % 终点节点j从1-n 循环
          if dist(i,j)>dist(i,k)+dist(k,j)  % 如果i,j两个节点间的最短距离大于i和k的最短距离+k和j的最短距离
             dist(i,j)=dist(i,k)+dist(k,j);  % 那么我们就令这两个较短的距离之和取代i,j两点之间的最短距离
             path(i,j)=path(i,k);   % 起点为i,终点为j的两个节点之间的最短路径要经过的节点更新为path(i,k)
             % 注意,上面一行语句不能写成path(i,j) = k
          end
      end
   end
end

end

2️⃣ 定义打印任意两节点间的最短路径的函数 print_path.m

function [] = print_path(path,dist,i,j)
%% 该函数的作用是打印从i到j经过的最短路径
% 输入:
%        path是使用floyd算法求出来的路径矩阵
%        dist是使用floyd算法求出来的最短距离矩阵
%        i是起始节点的编号
%        j是终点节点的编号
% 输出:无

if i == j
    warning('起点和终点相同,请检查后重新输入')  % 在屏幕中提示警告信息
    return;  % 不运行下面的语句,直接退出函数
end
if path(i,j) == j   % 如果path(i,j) = j,则有两种可能:
% (1)如果dist(i,j) 为 Inf , 则说明从i到j没有路径可以到达
    if dist(i,j) == Inf
        disp(['从',num2str(i),'到',num2str(j),'没有路径可以到达'])
% (2)如果dist(i,j) 不为 Inf , 则说明从i到j可直接到达,且为最短路径
    else
        disp(['从',num2str(i),'到',num2str(j),'的最短路径为'])
        disp([num2str(i),' ---> ',num2str(j)])
        disp(['最短距离为',num2str(dist(i,j))])
    end
else  % 如果path(i,j) ~= j,则说明中间经过了其他节点:
    k = path(i,j);
    result = [num2str(i),' ---> '];  % 初始化要打印的这个字符串
    while k ~= j  % 只要k不等于j, 就一直循环下去
        result = [result , num2str(k) , ' ---> ' ];  % i先走到k这个节点处
        k = path(k,j);
    end
    result = [result , num2str(k)];
    disp(['从',num2str(i),'到',num2str(j),'的最短路径为'])
    disp(result)
    disp(['最短距离为',num2str(dist(i,j))])
end

end

在这里插入图片描述


3️⃣ 定义打印所有的任意两节点间的最短路径的函数 print_all_path.m

function [] = print_all_path(D)
%% 该函数的作用是求解一个权重邻接矩阵任意两个节点之间的最短路径,并打印所有的结果出来
% 输入:
%        D是权重邻接矩阵
% 输出:无

[dist,path] = Floyd_algorithm(D);   % 调用之前的Floyd_algorithm函数
n = size(D,1);
if n == 1
    warning('请输入至少两阶以上的权重邻接矩阵')   % 在屏幕中提示警告信息
    return;   % 不运行下面的语句,直接退出函数
end

for i = 1:n
    for j = 1:n
        if i ~= j  % 不等号用~=表示
            print_path(path,dist,i,j);   % 调用之前的print_path函数
            disp('-------------------------------------------')
            disp('  ')
        end
    end
end

end

在这里插入图片描述


4️⃣ 将图转换为权重邻接矩阵D,并调用 Floyd 算法的函数

%% 首先将图转换为权重邻接矩阵D
n = 5;  %一共五个节点
D = ones(n) ./ zeros(n);  % 全部元素初始化为Inf【有向图】
for i = 1:n
    D(i,i) = 0;  % 主对角线元素为0
end
D(1,2) = 3;
D(1,3) = 8;
D(1,5) = -4;
D(2,5) = 7;
D(2,4) = 1;
D(3,2) = 4;
D(4,3) = -5;
D(5,4) = 6;
D(4,1) = 2;

%% 调用Floyd_algorithm函数求解
[dist,path] = Floyd_algorithm(D)

print_path(path,dist,1,5)
print_path(path,dist,1,4)
print_path(path,dist,3,1)

clc
disp('下面我们打印任意两点之间的最短距离:')
print_all_path(D)

6 思考题


求出任意两点间的最短路径

在这里插入图片描述

参考答案:

%% 首先将图转换为权重邻接矩阵D
n = 9; %一共9个节点
D = zeros(n); % 全部元素初始化为0 【无向图】
% 因为是无向图,所以权重邻接矩阵是一个对称矩阵
D(1,2) = 4; D(1,8) = 8;
D(2,8) = 3; D(2,3) = 8;
D(8,9) = 1; D(8,7) = 6;
D(9,7) = 6; D(9,3) = 2;
D(7,6) = 2; D(3,4) = 7;
D(3,6) = 4; D(6,4) = 14;
D(4,5) = 9; D(6,5) = 10;
D = D+D'; % 这个操作可以得到对称矩阵的另一半
for i = 1:n
for j = 1:n
if (i ~= j) && (D(i,j) == 0) 
D(i,j) = Inf; % 将非主对角线上的0元素全部变为Inf
end
end
end
%% 调用Floyd_algorithm函数求解
[dist,path] = Floyd_algorithm(D)
数学建模图论是指使用数学模型和图论的方法来解决实际问题。在这个方法中,我们使用图的概念来表示问题中的元素和它们之间的关系,并使用数学模型来描述这些关系。而Matlab是一个用于数值计算和科学工程应用的高级编程语言和环境。在Matlab中,我们可以使用图论算法来解决数学建模中的图论问题,如最短路径问题。通过使用Matlab中的函数和编程技巧,我们可以实现Dijkstra算法来计算最短路径和最短距离。下面是一个使用Matlab编写的Dijkstra算法的示例代码: ```matlab function [min,path]=dijkstra(w,start,terminal) n=size(w,1); label(start)=0; f(start)=start; for i=1:n if i~=start label(i)=inf; end end s(1)=start; u=start; while length(s)<n for i=1:n ins=0; for j=1:length(s) if i==s(j) ins=1; end end if ins==0 v=i; if label(v)>(label(u)+w(u,v)) label(v)=(label(u)+w(u,v)); f(v)=u; end end end v1=0; k=inf; for i=1:n ins=0; for j=1:length(s) if i==s(j) ins=1; end end if ins==0 v=i; if k>label(v) k=label(v); v1=v; end end end s(length(s)+1)=v1; u=v1; end min=label(terminal); path(1)=terminal; i=1; while path(i)~=start path(i+1)=f(path(i)); i=i+1; end path(i)=start; L=length(path); path=path(L:-1:1); ``` 这个代码使用了Dijkstra算法来计算从起点到终点的最短路径和最短距离。输入参数w是一个带权邻接矩阵,start和terminal分别是起点和终点的索引。函数返回值min是最短距离,path是最短路径。你可以根据自己的具体问题,将带权邻接矩阵和起点终点索引替换为实际的数值进行计算。希望这个例子能够帮助你理解数学建模图论和Matlab的应用。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值