📜  反转边缘的最低成本,使得每对节点之间都有路径(1)

📅  最后修改于: 2023-12-03 15:07:25.351000             🧑  作者: Mango

反转边缘的最低成本,使得每对节点之间都有路径

简介

本文主题为“反转边缘的最低成本,使得每对节点之间都有路径”。这是一个经典的图论问题,称为“反向边缘覆盖问题”。

给定一张有向图,可以选择将其某些边反向,从而使得每对节点之间都存在至少一条路径。目标是在花费最小的情况下实现这个目标。

算法

这个问题可以使用最小路径覆盖算法解决。最小路径覆盖问题要求在有向图中找到一组路径,使得每个节点恰好属于一条路径。具体来说,我们将每个节点拆分为入点和出点,然后将每条边表示为一个连接其起点的出点和连接其终点的入点的有向边通常情况下是指一条边有起点和终点的有向边。反向边的概念是将有向边的方向颠倒过来,即将有向边的起点变成终点,终点变成起点,成为反向边。。

然后,我们尝试均衡被这些路径“拆分”的入点和出点的数量。但是,如果我们在同一位置使用多个节点,我们可能会在最小路径覆盖问题中遇到麻烦:实际上,通过将它们链接在一起,我们可以在更少的路径上覆盖它们。因此,我们需要使用一些技巧来最大化覆盖(即,最小化需要反向的边的数量)。这种方法是采用网络流技术,并通过建立源和汇点以及流量约束来实现找到满足所有限制条件(此处指每对节点之间至少存在一条路径)的最小反向边缘集。

代码示例
from collections import defaultdict
from functools import reduce
from typing import List, Tuple


class DirectedGraph:
    def __init__(self, num_vertices: int):
        self.num_vertices = num_vertices
        self.adj_list = defaultdict(list)
        self.rev_adj_list = defaultdict(list)
        self.edges = []
        self.matching = []

    def add_edge(self, from_vertex: int, to_vertex: int):
        self.edges.append((from_vertex, to_vertex))
        self.adj_list[from_vertex].append(to_vertex)
        self.rev_adj_list[to_vertex].append(from_vertex)

    def find_min_cost_reversed_edges(self) -> List[Tuple[int, int]]:
        path_cover_digraph = self.build_path_cover_digraph()
        max_matching = path_cover_digraph.find_max_matching()
        min_cost_reversed_edges = self.edges.copy()
        for i in range(self.num_vertices):
            for j in range(self.num_vertices):
                if max_matching[i] == j:
                    continue
                reverse_edge = (j, i)
                if reverse_edge in self.edges:
                    min_cost_reversed_edges.remove(reverse_edge)
        return min_cost_reversed_edges

    def build_path_cover_digraph(self):
        k = 2 * self.num_vertices
        source = k
        sink = k + 1
        path_cover_digraph = DirectedGraph(k + 2)
        for i in range(self.num_vertices):
            path_cover_digraph.add_edge(source, i)
            path_cover_digraph.add_edge(i + self.num_vertices, sink)
            for j in self.adj_list[i]:
                path_cover_digraph.add_edge(i, j + self.num_vertices)
        return path_cover_digraph

    def find_max_matching(self) -> List[int]:
        free_vertices = set(range(0, self.num_vertices))
        matched_vertices = [-1] * self.num_vertices
        while len(free_vertices) > 0:
            root = next(iter(free_vertices))
            edges_in_augmenting_path = self._find_augmenting_path(root, free_vertices, matched_vertices)
            if edges_in_augmenting_path is None:
                free_vertices.remove(root)
                continue
            else:
                for edge in edges_in_augmenting_path:
                    if edge[0] in free_vertices:
                        free_vertices.remove(edge[0])
                    matched_vertices[edge[0]] = edge[1]
                    matched_vertices[edge[1]] = edge[0]
        return matched_vertices

    def _find_augmenting_path(self, root: int, free_vertices: set, matched_vertices: List[int]) \
            -> List[Tuple[int, int]]:
        def dfs(curr_vertex: int):
            visited.add(curr_vertex)
            neighbors = self.adj_list[curr_vertex] if curr_vertex not in matching_a else self.rev_adj_list[curr_vertex]
            for neighbor in neighbors:
                edge = (curr_vertex, neighbor)
                if neighbor not in visited:
                    if edge in unused_edges or edge[::-1] in unused_edges:
                        unused_edges.remove(edge)
                        unused_edges.remove(edge[::-1])
                        if neighbor in matching_b:
                            if dfs(matching_b[neighbor]):
                                return True
                            else:
                                unused_edges.append(edge)
                                unused_edges.append(edge[::-1])
                        else:
                            augmenting_path_edges.append(edge)
                            return True
                    elif neighbor == root:
                        augmenting_path_edges.append(edge)
                        return True
            return False

        matching_a = free_vertices
        matching_b = {matched_vertices[i]: i for i in free_vertices if matched_vertices[i] >= 0}
        augmenting_path_edges = []
        unused_edges = [(i, j) for i in matching_a for j in self.adj_list[i]]
        while len(unused_edges) > 0:
            start_edge = unused_edges.pop()
            visited = {start_edge[0]}
            if dfs(start_edge[1]):
                for edge in augmenting_path_edges:
                    if edge[0] in matching_a:
                        matching_b[edge[1]] = edge[0]
                        matching_b[edge[0]] = edge[1]
                        matching_a.remove(edge[0])
                        matching_a.add(edge[1])
                    else:
                        matching_a[edge[1]] = edge[0]
                        matching_a[edge[0]] = edge[1]
                        matching_b.pop(edge[0], None)
                return augmenting_path_edges
            else:
                augmenting_path_edges = []
        return None

以上是Python的实现。代码接受有向图的邻接列表作为输入,并返回一个反向边的列表,以实现“每对节点之间都存在至少一条路径”的目标。