图论算法(最短路、网络流、二分图)

介绍

1. 最短路算法
最短路算法是一类用于在加权有向图中搜索从起点到终点最短路径(或距离)的算法。其中最为经典的算法为 Dijkstra 和 Bellman-Ford 算法,分别适用于没有负权边和存在负权边的情况。此外,还有 Floyd-Warshall 算法,它适用于解决所有节点对之间的最短路问题。最短路算法在计算机网络、路径规划、交通流量控制等领域有着广泛应用。其实还有A*算法,只不过那个在游戏领域用的比较多

2. 网络流算法
网络流算法是用于解决最大流和最小割问题的一种算法家族。最经典的算法有 Ford-Fulkerson 算法和 Edmonds-Karp 算法,它们都使用了增广路思想来不断增加流量,直到达到最大流或最小割。网络流算法广泛应用于资源分配、流量控制、匹配问题等领域。

3. 二分图算法
二分图算法是一类用于处理二分图的算法,其中包括最大匹配、最小点覆盖和最大独立集等问题。常见的算法包括 Hopcroft-Karp 算法、匈牙利算法、KM 算法等。二分图算法主要应用于稳定婚姻问题、职工情感调配问题、数据匹配等领域。

此外,图论算法还包括深度优先搜索、广度优先搜索、拓扑排序、最小生成树算法等。

举例

1. 最短路算法

最短路问题-CSDN博客

2. 网络流算法

当涉及最大流问题时,Ford-Fulkerson算法和Edmonds-Karp算法是两个常用的解决方法。

Ford-Fulkerson算法是一个增广路径法,用于找到网络中的最大流。算法的基本思想是不断在剩余网络中寻找增广路径,通过增加路径上的流量来增加总流量,直到无法再找到增广路径。以下是Ford-Fulkerson算法的基本步骤:

1. 初始化网络中所有边的流量为0。
2. 在剩余网络中寻找一条从源节点到汇节点的增广路径。
3. 如果存在增广路径,则通过该路径增加流量。这相当于在该路径上找到最小的剩余容量,将其作为增加的流量。
4. 重复步骤2和3,直到无法再找到增广路径。

Ford-Fulkerson算法代码:

function maxFlow = fordFulkerson(graph, source, sink)
    % 初始化流量矩阵为0
    flow = zeros(size(graph));

    % 反向图的剩余容量矩阵
    residualCap = graph;

    while true
        % 利用DFS找增广路径
        [path, minCapacity] = dfs(source, sink, residualCap, flow, []);

        % 如果无法找到增广路径,则结束循环
        if isempty(path)
            break;
        end

        % 更新路径上的流量和剩余容量
        for i = 1 : length(path) - 1
            u = path(i);
            v = path(i+1);
            flow(u, v) = flow(u, v) + minCapacity;
            flow(v, u) = flow(v, u) - minCapacity;
            residualCap(u, v) = residualCap(u, v) - minCapacity;
            residualCap(v, u) = residualCap(v, u) + minCapacity;
        end
    end

    % 最大流为源节点流出的总流量
    maxFlow = sum(flow(source, :));
end

function [path, minCapacity] = dfs(source, target, residualCap, flow, path)
    % 深度优先搜索查找增广路径
    path = [path, source];

    if source == target
        % 找到增广路径,计算最小剩余容量
        minCapacity = min(residualCap(path(1:end-1), path(2:end)));
        return;
    end

    % 递归搜索下一个节点
    for i = 1 : size(residualCap, 1)
        if residualCap(source, i) > 0 && flow(source, i) < residualCap(source, i) && ~ismember(i, path)
            [path, minCapacity] = dfs(i, target, residualCap, flow, path);
            if ~isempty(path)
                return;
            end
        end
    end

    % 未找到增广路径,返回空路径
    path = [];
    minCapacity = 0;
end

Edmonds-Karp算法是Ford-Fulkerson算法的一个特殊实现,其中使用BFS(广度优先搜索)来查找增广路径。与Ford-Fulkerson算法不同的是,Edmonds-Karp算法在每一次迭代中都利用BFS找到的最短路径,这样可以保证算法具有多项式时间复杂度。以下是Edmonds-Karp算法的基本步骤:

1. 初始化网络中所有边的流量为0。
2. 在剩余网络中使用BFS查找从源节点到汇节点的最短增广路径。
3. 如果存在最短增广路径,则通过该路径增加流量。这相当于在该路径上找到最小的剩余容量,将其作为增加的流量。
4. 重复步骤2和3,直到无法再找到最短增广路径。

这两种算法都能找到最大流,并且Edmonds-Karp算法相对于Ford-Fulkerson算法有更好的性能保证。它们主要应用于网络流问题,例如在网络通信、交通流量分析以及资源分配等方面的应用。

Edmonds-Karp算法代码 :

function maxFlow = edmondsKarp(graph, source, sink)
    % 初始化流量矩阵为0
    flow = zeros(size(graph));

    % 反向图的剩余容量矩阵
    residualCap = graph;

    while true
        % 利用BFS找最短路径(最小剩余容量路径)
        [path, minCapacity] = bfs(source, sink, residualCap, flow);

        % 如果无法找到最短路径,则结束循环
        if isempty(path)
            break;
        end

        % 更新路径上的流量和剩余容量
        for i = 1 : length(path) - 1
            u = path(i);
            v = path(i+1);
            flow(u, v) = flow(u, v) + minCapacity;
            flow(v, u) = flow(v, u) - minCapacity;
            residualCap(u, v) = residualCap(u, v) - minCapacity;
            residualCap(v, u) = residualCap(v, u) + minCapacity;
        end
    end

    % 最大流为源节点流出的总流量
    maxFlow = sum(flow(source, :));
end

function [path, minCapacity] = bfs(source, target, residualCap, flow)
    % 广度优先搜索查找最小剩余容量路径
    queue = source;
    visited = zeros(1, size(flow, 1));
    visited(source) = 1;
    parent = zeros(1, size(flow, 1));
    minCapacity = Inf;

    % 遍历队列,直到找到汇节点或遍历完成
    while ~isempty(queue)
        u = queue(1);
        queue(1) = [];

        for v = find(residualCap(u, :) & ~visited)
            % 如果v没有被访问过且剩余容量不为0
            capacity = min([residualCap(u, v), flow(u, v)]);
            if capacity > 0
                visited(v) = 1;
                parent(v) = u;
                queue(end+1) = v;

                % 如果找到汇节点,则返回最小容量和路径
                if v == target
                    path = reconstructPath(source, target, parent);
                    for i = 1 : length(path) - 1
                        u = path(i);
                        v = path(i+1);
                        minCapacity = min([minCapacity, residualCap(u, v)]);
                    end
                    return;
                end
            end
        end
    end

    % 未找到最短路径,返回空路径和最小容量0
    path = [];
    minCapacity = 0;
end

function path = reconstructPath(source, target, parent)
    % 根据父节点列表重建最短路径
    path = [target];
    while path(1) ~= source
        path = [parent(path(1)), path];
    end
end

3. 二分图算法

Hopcroft-Karp算法、匈牙利算法和KM算法都是解决二分图的最大匹配问题的经典算法。

1. Hopcroft-Karp算法:
Hopcroft-Karp算法是一种高效的求解二分图最大匹配的算法,时间复杂度为 O(sqrt(V)*E),其中V是节点数,E是边数。
该算法基于增广路径的思想,通过不断在已有匹配中寻找增广路径来更新最大匹配。具体步骤如下:

   - 初始化:将所有节点分为左侧顶点集合U和右侧顶点集合V,将匹配M初始化为空。
   - 通过BFS构建层次图:从U的未匹配节点开始,利用BFS构建层次图,记录每个节点的层次。
   - 使用DFS寻找增广路径:从U的未匹配节点开始,通过DFS寻找增广路径,如果找到,则更新匹配,并将层次图重置。
   - 重复以上两步,直到无法找到增广路径。

function [matching, max_match] = hopcroft_karp(adj_matrix)
% 求解二分图最大匹配的Hopcroft-Karp算法
% 输入:
% adj_matrix:二分图邻接矩阵,其中0表示没有边,1表示有边。矩阵的行和列分别对应左右两边的节点。
% 输出:
% matching:二分图匹配结果,每个元素对应左边一个节点的匹配结果,其值为其匹配的右边节点编号,如果该节点没有匹配则为0。
% max_match:二分图最大匹配数

n_left = size(adj_matrix, 1);
n_right = size(adj_matrix, 2);
matching = zeros(1, n_left); % 初始化匹配结果
max_match = 0; % 初始化最大匹配数
while true
    % 使用BFS查找增广路
    queue = find(matching == 0); % 找到没有匹配的左边节点
    dist = -ones(1, n_left); % 初始距离都为-1
    for i = queue
        dist(i) = 0;
    end
    while ~isempty(queue)
        u = queue(1);
        queue(1) = [];
        for v = find(adj_matrix(u, :))
            if dist(matching(v)) < 0 % 右边节点没有被访问过
                dist(matching(v)) = dist(u) + 1;
                queue(end+1) = matching(v);
            end
        end
    end
    if dist(matching == 0) < 0 % 无增广路
        break;
    end
    % 使用DFS扩展增广路
    for u = find(matching == 0)
        if dfs(u)
            max_match = max_match + 1;
        end
    end
end

    function b = dfs(u)
        if u == 0 % 左边节点没有匹配,找到增广路
            b = true;
            return
        end
        for v = find(adj_matrix(u, :))
            if dist(matching(v)) == dist(u) + 1 && dfs(matching(v))
                matching(u) = v;
                matching(v) = u;
                b = true;
                return
            end
        end
        dist(u) = -1; % 没找到增广路,将该节点标记为无效
        b = false;
    end
end

2. 匈牙利算法(Hungarian Algorithm):
匈牙利算法是一种经典的解决二分图最大匹配问题的算法,时间复杂度为O(V*E),其中V是节点数,E是边数。
算法通过不断尝试匹配和调整匹配,最终找到一个最大匹配。具体步骤如下:


  - 初始化:将所有节点分为左侧顶点集合U和右侧顶点集合V,将匹配M初始化为空。
   - 选择未匹配的左节点,通过DFS寻找增广路径。
   - 如果存在增广路径,则更新匹配。
   - 重复以上步骤,直到无法找到增广路径。

function [matching, max_weight] = hungarian_algorithm(cost_matrix)
% 二分图最大权匹配的匈牙利算法
% 输入:
% cost_matrix:二分图的权重邻接矩阵,矩阵的行和列表示左右两边的节点,值为节点之间的权重。
% 输出:
% matching:二分图匹配结果,每个元素对应左边一个节点的匹配结果,其值为其匹配的右边节点编号,如果该节点没有匹配则为0。
% max_weight:二分图最大权匹配的权重和。

n_left = size(cost_matrix, 1);
n_right = size(cost_matrix, 2);
matching = zeros(1, n_left); % 初始化匹配结果
slack = inf * ones(1, n_right); % 初始化松弛变量

for u = 1:n_left
    visited = false(1, n_right);
    while true
        visited(:) = false;
        if dfs(u)
            break;
        else
            delta = min(slack(~visited));
            matching(visited) = matching(visited) + delta;
            matching(~visited) = matching(~visited) - delta;
            slack(~visited) = slack(~visited) - delta;
        end
    end
end

max_weight = sum(cost_matrix(sub2ind([n_left, n_right], 1:n_left, matching)));

    function b = dfs(u)
        visited(u) = true;
        for v = 1:n_right
            if ~visited(v)
                tmp = matching(u) + matching(v) - cost_matrix(u, v);
                if tmp == 0
                    visited(v) = true;
                    if matching(v) == 0 || dfs(matching(v))
                        matching(u) = v;
                        b = true;
                        return
                    end
                elseif tmp < slack(v)
                    slack(v) = tmp;
                end
            end
        end
        b = false;
    end
end

3. KM算法(Kuhn-Munkres Algorithm):
KM算法是一种经典的解决完全二分图最大权匹配(最大权完美匹配)问题的算法,时间复杂度为O(V^3),其中V是节点数。
该算法通过构建二部图和对权重矩阵进行优化,找到一个最大权匹配。具体步骤如下:

   - 初始化:将所有节点分为左侧顶点集合U和右侧顶点集合V,将权重矩阵初始化。
   - 对权重矩阵进行优化:通过减去每行的最小值和减去每列的最小值,得到新的权重矩阵。
   - 尝试找到一个完美匹配:通过DFS尝试匹配,并记录标记集合S和T。
   - 如果存在完美匹配,则结束;否则,调整标记集合S和T,使图中的更多顶点能够进入增广路径。
   - 重复以上步骤,直到找到完美匹配。

function [matching, max_weight] = KM_algorithm(cost_matrix)
% 二分图最大权匹配的KM算法
% 输入:
% cost_matrix:二分图的权重邻接矩阵,矩阵的行和列表示左右两边的节点,值为节点之间的权重。
% 输出:
% matching:二分图匹配结果,每个元素对应左边一个节点的匹配结果,其值为其匹配的右边节点编号,如果该节点没有匹配则为0。
% max_weight:二分图最大权匹配的权重和。

n_left = size(cost_matrix, 1);
n_right = size(cost_matrix, 2);
cost_matrix = -cost_matrix; % 转换为最小权匹配问题
label_left = -inf(1, n_left);
label_right = zeros(1, n_right);

% 初始化 label_left
for u = 1:n_left
    label_left(u) = max(cost_matrix(u, :));
end

matching = zeros(1, n_left); % 初始化匹配结果
slack = inf * ones(1, n_right); % 初始化松弛变量

for u = 1:n_left
    visited = false(1, n_right);
    while true
        visited(:) = false;
        if dfs(u)
            break;
        else
            delta = min(slack(~visited));
            label_left(visited) = label_left(visited) - delta;
            label_right(~visited) = label_right(~visited) + delta;
            slack(~visited) = slack(~visited) - delta;
        end
    end
end

max_weight = -sum(cost_matrix(sub2ind([n_left, n_right], 1:n_left, matching)));

    function b = dfs(u)
        visited(u) = true;
        for v = 1:n_right
            if ~visited(v)
                delta = label_left(u) + label_right(v) + cost_matrix(u, v) - max(label_left(u), label_right(v));
                if delta == 0
                    visited(v) = true;
                    if matching(v) == 0 || dfs(matching(v))
                        matching(u) = v;
                        b = true;
                        return
                    end
                elseif delta < slack(v)
                    slack(v) = delta;
                end
            end
        end
        b = false;
    end
end

这些算法在解决二分图最大匹配问题上各有特点和适用范围,选择合适的算法取决于具体问题的规模和特征。

你可能感兴趣的:(数学建模应当掌握的十类算法,图论,算法)