数学建模之图论

目录

  • 1 图的基本概念
  • 2 如何做图
    • 2.1 直接做图
    • 2.2 编程做图
  • 3 权重邻接矩阵
    • 3.1 无向图
    • 3.2 有向图
  • 4 Dijkstra 算法
    • 4.1 算法概述
    • 4.2 代码实现
  • 5 Floyd 算法
    • 5.1 算法概述
    • 5.2 代码实现
  • 6 思考题

1 图的基本概念


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

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

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

数学建模之图论_第1张图片

2 如何做图

2.1 直接做图


在线做图工具的网址

数学建模之图论_第2张图片

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', [] );  

数学建模之图论_第3张图片
数学建模之图论_第4张图片

❗️注意:

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

数学建模之图论_第5张图片

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', [] );  

数学建模之图论_第6张图片

☀️ 总结:

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

3 权重邻接矩阵


3.1 无向图


数学建模之图论_第7张图片

3.2 有向图


数学建模之图论_第8张图片

4 Dijkstra 算法

4.1 算法概述


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

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


数学建模之图论_第9张图片

使用 Dijkstra 算法解决上述问题

1️⃣ 初始化

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

数学建模之图论_第10张图片

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

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

数学建模之图论_第11张图片

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

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

数学建模之图论_第12张图片

数学建模之图论_第13张图片

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

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

数学建模之图论_第14张图片

数学建模之图论_第15张图片

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

数学建模之图论_第16张图片

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

数学建模之图论_第17张图片

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

数学建模之图论_第18张图片


Bellman‐Ford 算法


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

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

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

数学建模之图论_第19张图片

4.2 代码实现


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

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

  • 输入参数:

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

    • 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之后才有

输出图形:

数学建模之图论_第21张图片

5 Floyd 算法

5.1 算法概述


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

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

算法动画展示

数学建模之图论_第22张图片
从上面观察到的两个结论中,我们不难提炼出下面这个思想:

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

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

5.2 代码实现


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


数学建模之图论_第23张图片


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


数学建模之图论_第24张图片

数学建模之图论_第25张图片

将伪代码转换为 Matlab 代码

数学建模之图论_第26张图片


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

数学建模之图论_第27张图片


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

数学建模之图论_第28张图片


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 思考题


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

数学建模之图论_第29张图片

参考答案:

%% 首先将图转换为权重邻接矩阵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)

你可能感兴趣的:(数学建模,数学建模,图论)