理理错综复杂的关系——图模型

本文介绍了图论中几种重要的算法,包括Dijkstra算法用于求解两点间最短路径,Floyd算法用于求解所有顶点对间最短路径,以及Kruskal和Prim算法用于构建最小生成树。这些算法在解决网络问题、交通路径优化等领域具有广泛应用。
摘要由CSDN通过智能技术生成

联系数计加油站获取论文和代码等资源。
邮箱:MathComputerSharing@outlook.com
微信公众号:数计加油站
致谢:清风数学建模


在这里插入图片描述

关系的抽象化

人们很喜欢抽象。象可能无辜。但人确实能感到便捷。

让我们再一次地扬起鞭子,抽一次象:关系是什么

关系,首先是两个对象的关系。让我们把这两个对象抽象为两个结点,更直白一点,两个圈。关系,是这两个结点所共有的某种性质。比如,“爸爸”结点和“妈妈”结点,他们的关系是“夫妻”。我们用一条直线来展示这种关系,俗称

通常,我们更关心数值关系,于是,关系被特定化为一个数字,即所谓权重

将所有的两两关系都描绘出来,我们就得到了关系

在这里插入图片描述

图的具体化

最常见的图模型,就是交通道路。研究交通道路的问题,能帮助我们把图模型具体化。

结点,就是某个地点。

边,就是某两个地点之间的道路。

权重,就是某两个地点之间的路程,即道路长。

我们可以研究连通问题:两个地点相不相通。有没有哪个地方会绕圈。

我们可以研究最短路径问题:两个地点之间走哪条路线才能最近。

我们可以研究最小生成树问题: 要在路上铺电线,怎样才能兼顾各个地点,而且消耗最小?

图的算法

剪不断,理还乱。图论中的算法很多很多。理解它们的原理,对我们来说,是一种很好的"心智锻炼"。

可以在网络上搜索相关资料,尤其是算法可视化资料,可能会有很大的帮助。

图很复杂,我们的人生又何尝不是?或者说,更为复杂。

一篇关于图的简短论文

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

Matlab代码

Dijkstra.m

function [path, distance] = Dijkstra(G, u0, v0)
% Dijkstra算法求最短路径

    % 初始化参数
    n = G.numnodes;
    nodes = 1:n;
    i = 0;
    d_now = ones(n, 1) * inf;
    d_now(u0) = 0;
    parents = ones(n, 1) * u0;
    in_s = u0;

    history = [];
    % 遍历所有终点或v0进入最短路径集
    while (i ~= n - 1) && ~(ismember(v0, in_s))
        not_in_s = setdiff(nodes, in_s);
        for v = not_in_s
            for u = in_s
                % 查找uv间的边
                uv_index = G.findedge(u, v);
                if uv_index
                    % 边存在则获取权
                    w = min(G.Edges.Weight(uv_index));
                    % 计算新的最短距离
                    d_now(v) = min([d_now(v), d_now(u) + w]);
                    if d_now(u) + w <= d_now(v)
                        % 距离发生改变
                        parents(v) = u;
                        % 记录变化
                        history = [history; [u, v]];
                    end
                end
            end
        end
        % 寻找最短距离的顶点
        tmp = d_now;
        tmp(in_s) = inf;
        [~, new_u] = min(tmp);
        new_u = new_u(1);
        % 添加最短路径顶点
        in_s = union(new_u, in_s);
        i = i + 1;
    end

    % 倒溯最短路径
    last = v0;
    path = v0;

    while last ~= u0
        k = find(history(:, 2) == last);
        last = history(k(end), 1);
        path = [last, path];
    end

    distance = d_now(v0);
end

Floyd.m

function [distances, paths] = Floyd(G)
    % Floyd算法求所有顶点对之间的最短路径

    % 矩阵初始化
    distances = G.adjacency('weighted');
    n = length(distances);
    distances(distances == 0) = inf;
    distances(1:n + 1:end) = 0;
    R = zeros(n);

    for k = 1:n

        for i = 1:n

            for j = 1:n

                % 插点更新
                if distances(i, k) + distances(k, j) < distances(i, j)
                    distances(i, j) = distances(i, k) + distances(k, j);
                    R(i, j) = k;
                end

            end

        end

    end

    distances = full(distances);

    % 复原路径
    paths = returnPaths(R);

    for i = 1:n

        for j = 1:n

            if i ~= j
                % 添加起点和终点
                paths{i, j} = [i paths{i, j} j];
            else
                paths{i, j} = i;
            end

        end

    end

end

function paths = returnPaths(R)
    % 根据路由矩阵返回最短路径矩阵
    n = length(R);
    paths = cell(n);

    for i = 1:n

        for j = 1:n
            paths{i, j} = parse(i, j);
        end

    end
    
    % 递归推导最短路径
    function path = parse(i, j)
        p = R(i, j);

        if p == 0
            path = [];
        else
            left = parse(i, p);
            right = parse(p, j);
            path = [left, p, right];
        end

    end

end

Kruskal.m

function [resultgraph, minw] = Kruskal(G)
% Kruskal算法求最小生成树

    % 初始化参数
    W = full(G.adjacency("weighted"));
    n = length(W);
    W(W == 0) = inf;
    minw = 0;
    resultgraph = graph();
    i = 1;

    while i < n
        % 寻找最小权值边
        w_min = min(W, [], "all");
        [index_x, index_y] = find(W == w_min, 1);
        % 添加最小权值边
        resultgraph = resultgraph.addedge(index_x, index_y, w_min);
        % 判断添加该边后图是否有圈
        if hascycles(resultgraph)
            % 有圈则删除该边,并标记pass
            resultgraph = resultgraph.rmedge(index_x, index_y);
            W(index_x, index_y) = inf;
            W(index_y, index_x) = inf;
        else
            % 无圈则保持,并标记pass
            W(index_x, index_y) = inf;
            W(index_y, index_x) = inf;
            minw = minw + w_min;
            i = i + 1;
        end

    end

end

Prim.m

function [resultgraph, minw] = Prim(G)
% Prim算法求解最小生成树

    % 初始化参数
    W = full(G.adjacency("weighted"));
    n = length(W);
    W(W == 0) = inf;
    V = 1:n;
    V_added = [1];
    E_added = [];
    minw = 0;

    % 直至V和V_added相等
    while ~isequal(V, sort(V_added))
        tmp = W;
        V_not_added = setdiff(V, V_added);
        % 圈定p,v范围
        tmp(V_not_added, :) = inf;
        tmp(:, V_added) = inf;
        w_min = min(tmp, [], "all");
        minw = minw + w_min;
        [p, v] = find(tmp == w_min, 1);
        % pv边标记pass
        W(p, v) = inf;
        W(v, p) = inf;
        % 更新
        V_added = union(V_added, v);
        E_added = [E_added; p, v, w_min];
    end

    s = E_added(:, 1);
    t = E_added(:, 2);
    w = E_added(:, 3);
    resultgraph = graph(s, t, w);
end

GraphDemo.m

clc,clear

%% 无向赋权图通过邻接矩阵创建
figure(1);
% 输入邻接矩阵A
A = [ 0 0 10 60;
    0 0 5 20;
    10 5 0 1;
    60 20 1 0];	
    
% 输入结点名
nodes=["v_1","v_2","v_3","v_4"];

% 使用邻接矩阵A和结点名nodes创建赋权无向图
G = graph(A,nodes);

% plot函数显示图
plot(G,"EdgeLabel",G.Edges.Weight,"NodeFontSize",12);

%% 无向赋权图通过顶点对创建
figure(2);
% 输入一个顶点
s = [1 1 2 2 3];

% 输入另一个顶点
t = [3 4 3 4 4];

% 输入结点名
nodes = ["v_1","v_2","v_3","v_4"];

% 输入权值
weights = [10 60 5 20 1];

% 使用顶点对s,t和结点名nodes创建赋权无向图
G = graph(s,t,weights,nodes);

% plot函数显示图
plot(G,"EdgeLabel",G.Edges.Weight,"NodeFontSize",12)

%% 最短路径
% 输入图的顶点对
s = [1,1,1,1,2,2,2,3,3,4];
t = [2,3,4,5,3,4,5,4,5,5];
w = [4.5,1.3,3.3,5.6,3.2,1.1,0.9,1.9,4.2,2.2];

figure(3);
% 创建并绘制图
G=graph(s,t,w);
fig1=plot(G,"EdgeLabel",G.Edges.Weight,'EdgeLabelColor','green');

% 使用Dijkstra算法求得最短路径并在图中标出
[path,distance]=Dijkstra(G,1,5);
highlight(fig1,path,'EdgeColor','red','LineWidth',2,"EdgeLabelColor","red");

% 使用Floyd算法求所有最短路径
[distances,paths]=Floyd(G);

%% 最小生成树
s1 = [1,1,1,1,1,1,1,1,2,2,3,4,5,6,7,8];
t1 = [2,3,4,5,6,7,8,9,3,9,4,5,6,7,8,9];
w1 = [2,1,3,4,4,2,5,4,4,1,1,1,5,2,3,5];
G = graph(s1,t1,w1);

% Kruskal算法求解最小生成树
[min_tree, min_w] = Kruskal(G);

figure(4);
% 绘图并加粗最小生成树
fig2 = plot(G, "EdgeLabel", G.Edges.Weight, 'EdgeLabelColor', 'green');
highlight(fig2, min_tree, "EdgeColor", "red", "LineWidth", 4);

figure(5);
% Prim算法求解最小生成树
[tree_min,w_min] = Prim(G);

% 绘图并加粗最小生成树
fig3=plot(G,"EdgeLabel",G.Edges.Weight,'EdgeLabelColor','green');
highlight(fig3,tree_min,"EdgeColor","red","LineWidth",4);

Python代码

导库

import networkx as nx
import numpy as np
import pandas as pd

通过顶点对创建无向赋权图

# 输入边和权
s1 = [f"$v_{i}$" for i in [1, 1, 2, 2, 3]]
t1 = [f"$v_{i}$" for i in [3, 4, 3, 4, 4]]
w1 = [10, 60, 5, 20, 1]
edges = list(zip(s1, t1, w1))

# 创建空图并添加顶点和边权
G1 = nx.Graph()
G1.add_weighted_edges_from(edges)

# 计算顶点位置
pos1 = nx.spring_layout(G1)

# 绘制无权图
nx.draw(G1, pos1, with_labels=True, font_size=14)

# 追加绘制权
labels = nx.get_edge_attributes(G1, 'weight')
fig1 = nx.draw_networkx_edge_labels(G1, pos1, edge_labels=labels, font_color="red")

Dijkstra算法

def Dijkstra(G, u0, v0):
    """利用Dijkstra计算最短路径和距离"""

    # 初始化参数
    n = G.number_of_nodes()
    nodes = list(range(1, n + 1))
    d_now = np.ones(n) * np.inf
    d_now[u0 - 1] = 0
    parents = [u0 for i in range(n)]
    s = set()
    s.add(u0)
    history = []
    i = 0

    # 遍历所有终点或v0进入最短路径顶点集
    while (i != n - 1) or (v0 not in s):
        not_s = set(nodes) - s
        for v in not_s:
            for u in s:
                try:
                    # 检查顶点u,v间是否有边
                    w = G.edges[u, v]['weight']
                except:
                    pass
                else:
                    d_now[v - 1] = min([d_now[v - 1], d_now[u - 1] + w])
                    if d_now[u - 1] + w <= d_now[v - 1]:
                        # 顶点最短值改变,记录改变
                        parents[v - 1] = u
                        history.append([u, v])

        # 寻找当前的最小距离点,并加入s集合
        tmp = d_now.copy()
        in_s_index = [i - 1 for i in s]
        tmp[in_s_index] = np.inf
        new_u = np.argmin(tmp) + 1
        s.add(new_u)
        i = i + 1

    # 拼接最终的最短路径
    last = v0
    path = [v0]
    history = np.array(history)
    while last != u0:
        k = np.where(history[:, 1] == last)
        last = history[k[-1][-1], 0]
        path.append(last)

    path = path[-1::-1]
    distance = d_now[v0 - 1]
    return path, distance

# 输入边和权
edges = [(1, 2, 4.5), (1, 3, 1.3), (1, 4, 3.3), (1, 5, 5.6), (2, 3, 3.2), (2, 4, 1.1), (2, 5, 0.9), (3, 4, 1.9),
         (3, 5, 4.2), (4, 5, 2.2)]

# 创建空图并添加顶点和边权
G2 = nx.Graph()
G2.add_weighted_edges_from(edges)

# 计算顶点位置
pos2 = nx.spring_layout(G2)

# 绘制无权图
nx.draw(G2, pos2, with_labels=True, font_size=14)

# 绘制最短路径
path, distance = Dijkstra(G2, 1, 5)
shortest_edges = []
for i in range(np.size(path) - 1):
    shortest_edges.append((path[i], path[i + 1]))
nx.draw_networkx_edges(G2, pos2, edgelist=shortest_edges, edge_color='red')

# 追加绘制权
labels = nx.get_edge_attributes(G2, 'weight')
fig2 = nx.draw_networkx_edge_labels(G2, pos2, edge_labels=labels, font_color="green")

Floyd算法

def returnPaths(R):
    """根据路由矩阵还原路径"""

    def parse(i, j):
        """递归解析i,j顶点最短路径"""
        p = R[i, j]
        if p == 0:
            path = []
        else:
            path = [p]
            left = parse(i, p - 1)
            right = parse(p - 1, j)
            path.extend(right)
            for num in left[-1::-1]:
                path.insert(0, num)
        return path

    n, n = np.shape(R)
    paths = [[] for i in range(n)]
    for i in range(n):
        for j in range(n):
            paths[i].append(parse(i, j))
    return paths

def Floyd(G):
    """Floyd算法求所有顶点间的最短路径"""

    # 初始化矩阵
    distances = nx.to_numpy_matrix(G)
    n, n = np.shape(distances)
    distances[distances == 0] = np.inf
    row, col = np.diag_indices_from(distances)
    distances[row, col] = 0
    R = np.zeros((n, n), dtype=int)

    # 插点更新
    for k in range(n):
        for i in range(n):
            for j in range(n):
                if distances[i, k] + distances[k, j] < distances[i, j]:
                    distances[i, j] = distances[i, k] + distances[k, j]
                    R[i, j] = k + 1

    # 复原路径
    paths = returnPaths(R)

    # 路径添加起点和终点
    for i in range(n):
        for j in range(n):
            if i != j:
                paths[i][j].append(j + 1)
                paths[i][j].insert(0, i + 1)
            else:
                paths[i][j] = [i + 1]
    return distances, paths

distances, paths = Floyd(G2)
# 最短路径长
n = G2.number_of_nodes()
d_df = pd.DataFrame(distances, index=range(1, n + 1), columns=range(1, n + 1))
d_df
# 最短路径集
p_df = pd.DataFrame(paths, index=range(1, n + 1), columns=range(1, n + 1))
p_df

Kruskal算法

def Kruskal(G):
    """Kruskal算法求解最小生成树"""
    # 初始化参数
    W = nx.to_numpy_matrix(G)
    n, n = np.shape(W)
    W[W == 0] = np.inf
    minw = 0
    resultgraph = nx.Graph()
    i = 1

    while i < n:
        # 寻找最小权值边
        w_min = np.min(W)
        index_x, index_y = [
            np.where(W == w_min)[0][0],
            np.where(W == w_min)[1][0]
        ]
        # 添加最小权值边
        resultgraph.add_edge(index_x + 1,
                             index_y + 1,
                             weight=G.edges[index_x + 1,
                                            index_y + 1]['weight'])
        try:
            # 判断添加该边后图是否有圈
            nx.find_cycle(resultgraph)
        except:
            # 无圈则保持,并标记pass
            W[index_x, index_y] = np.inf
            W[index_y, index_x] = np.inf
            minw = minw + w_min
            i = i + 1
        else:
            # 有圈则删除该边,并标记pass
            resultgraph.remove_edge(index_x + 1, index_y + 1)
            W[index_x, index_y] = np.inf
            W[index_y, index_x] = np.inf

    return resultgraph, minw

# 输入边和权
edges = [(1, 2, 2), (1, 3, 1), (1, 4, 3), (1, 5, 4), (1, 6, 4), (1, 7, 2), (1, 8, 5), (1, 9, 4),
         (2, 3, 4), (2, 9, 1), (3, 4, 1), (4, 5, 1), (5, 6, 5), (6, 7, 2), (7, 8, 3), (8, 9, 5)]

# 创建空图并添加顶点和边权
G3 = nx.Graph()
G3.add_weighted_edges_from(edges)

# 计算顶点位置
pos3 = nx.spring_layout(G3)

# 绘制无权图
nx.draw(G3, pos3, with_labels=True, font_size=14)

# Kruskal算法求解最小生成树
min_tree, wmin = Kruskal(G3)

# 绘制最小生成树
nx.draw_networkx_edges(G3, pos3, edgelist=min_tree.edges, edge_color="red", width=3)

# 追加绘制权
labels = nx.get_edge_attributes(G3, 'weight')
edges = nx.draw_networkx_edge_labels(G3,
                                     pos3,
                                     edge_labels=labels,
                                     font_color="green")

Prim算法

def Prim(G):
    """Prim算法求解最小生成树"""
    # 初始化参数
    W = nx.to_numpy_matrix(G)
    n, n = np.shape(W)
    W[W == 0] = np.inf
    V = set(range(1, n + 1))
    V_added = {1}
    E_added = []
    minw = 0

    # 直至V和V_added相等
    while V != V_added:
        tmp = W.copy()
        V_not_added = V - V_added
        # 圈定p,v范围
        index_x = [i - 1 for i in V_not_added]
        tmp[index_x, :] = np.inf
        index_y = [i - 1 for i in V_added]
        tmp[:, index_y] = np.inf
        w_min = np.min(tmp)
        minw = minw + w_min
        p, v = np.where(tmp == w_min)[0][0] + 1, np.where(
            tmp == w_min)[1][0] + 1
        # pv边标记pass
        W[p - 1, v - 1] = np.inf
        W[v - 1, p - 1] = np.inf
        # 更新
        V_added.add(v)
        E_added.append((p, v, w_min))

    resultgraph = nx.Graph()
    resultgraph.add_weighted_edges_from(E_added)
    return resultgraph, minw

# 绘制无权图
nx.draw(G3, pos3, with_labels=True, font_size=14)

# Prim算法求解最小生成树
min_tree, minw = Prim(G3)

# 绘制最小生成树
nx.draw_networkx_edges(G3, pos3, edgelist=min_tree.edges, edge_color="red", width=3)

# 追加绘制权
labels = nx.get_edge_attributes(G3, 'weight')
p_edges = nx.draw_networkx_edge_labels(G3,
                                       pos3,
                                       edge_labels=labels,
                                       font_color="green")
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值