具有非加权、双向边和具有流容量的节点的流解析算法的 C 实现

标签 c algorithm graph adjacency-matrix edmonds-karp

我试过在堆栈溢出中寻找我的问题的答案。我找到了这些答案,但他们的解决方案并不真正适用于我的情况,因为我有非定向边缘。我无法创建一个边在 Vin 处进入而边在 Vout 处退出的新顶点,因为在特定方向上没有“进入”或“退出”。

Edmonds-Karp Algorithm for a graph which has nodes with flow capacities

(我找不到第二个堆栈问题,但答案相同)

初始问题

我的问题是我有一个图,其中节点有容量,所有边都是双向的,我需要找到所有路径,使我能够最大化 N 个元素在图中的流量。

基本上它是一个容纳容量为 1 的房间和无限容量的双向边缘的 lem-in。

想象一个迷宫,你可以在隧道中容纳尽可能多的人,但每个房间只能容纳一个人。他们可以一转身从一个房间移动到另一个房间。我如何才能做到让人们从迷宫开始到结束的所有方式都可以走,而不会让两个人在同一个房间里。

Edmonds-Karp 的实现

我设法使用邻接矩阵(它是一个一维整数数组,使用位来检查是否存在连接)实现了 Edmonds-Karp(可能非常糟糕)。

我有 3 个函数,一个运行算法本身的函数(我稍微简化了代码,例如删除对 malloc、frees 等的保护......以便算法看起来更好):

  • 主算法循环

  • 这是主循环。我试图找到一条增强路径。如果我不这样做,这意味着最后房间的(接收器)父级将是初始值 (-1)。
    否则我应用路径,打印路径并继续。
    void edmonds_karp(t_map *map)
    {
        t_deque     *deque;
        uint32_t    *flow;
        int64_t     *path;
        t_way       *way;
    
        flow = ft_memalloc(sizeof(uint32_t) * map->size_rooms);
    
        while (TRUE)
        {
            deque = ft_deque_create();
            find_augmenting_path(deque, map, &flow, &path);
            if (path[get_end_room(map)->id] == -1)
                break ;
            apply_augmenting_path(map, &flow, path);
            way = build_way_from_path(path, map);
            print_way(way);
            ft_deque_delete(deque);
        }
    }
    
  • 找到增广路径

  • 然后有一个函数可以找到增广路径。我只是使用带有队列的 BFS,弹出父级,然后检查所有子级。
    如果 child 有前向连接并且仍有容量,我会将其添加到路径中,将其标记为已访问并将其推送到队列中。
    如果一个 child 有一个向后的连接并流过它,我将它添加到路径中,将它标记为已访问并将其推送到队列中。
    static int64_t  find_augmenting_path(t_deque *deque, t_map *map, uint32_t **flow, int64_t **path)
    {
        uint32_t    child_id;
        uint8_t     *visited;
        t_room      *parent;
        t_room      *child;
    
        visited = ft_memalloc(sizeof(uint8_t) * map->size_rooms);
        ft_deque_push_back(deque, get_start_room(map));
        *path = init_path(map->size_rooms);
    
        while (deque->head)
        {
            parent = ft_deque_pop_front(deque);
            child_id = 0;
    
            while (child_id < map->size_rooms)
            {
                if (!visited[child_id] && !map->rooms[child_id]->visited)
                    if ((((map->adj_matrix[parent->id] & (1ULL << child_id)) && !((*flow)[parent->id] & (1ULL << child_id))) // There is a forward connection and we still have capacity
                        || ((map->adj_matrix[child_id] & (1ULL << parent->id)) && ((*flow)[child_id] & (1ULL << parent->id))))) // There is a backward connection and we have reverse capacity
                    {
                        child = get_room_by_id(map, child_id);
                        visited[child_id] = TRUE;
                        (*path)[child_id] = parent->id;
                        ft_deque_push_back(deque, (void*)child);
                        if (child->type == END)
                            return (SUCCESS);
                    }
                ++child_id;
            }
        }
        return (ERROR);
    }
    
  • 应用增广路径

  • 应用增广路径的函数非常简单,在我的例子中,所有边的容量都是 1。我们只是从结束(接收器)返回,直到我们使用保存在路径中的 ID 到达开始(点击)。
    对于每个房间,我们填充从 parent 到 child 的容量和从 child 到 parent 的免费容量。
    static void     apply_augmenting_path(t_map *map, uint32_t **flow, int64_t *path)
    {
        t_room  *start;
        t_room  *parent;
        t_room  *child;
    
        start = get_start_room(map);
        child = get_end_room(map);
        while (child->id != start->id)
        {
            parent = get_room_by_id(map, path[child->id]);
            (*flow)[parent->id] |= 1ULL << child->id;
            (*flow)[child->id] |= 0ULL << parent->id;
            child = parent;
        }
    }
    

    我在以下条件下添加了一个检查:
    if (!visited[child_id] && !map->rooms[child_id]->visited)
    这张支票!map->rooms[child_id]->visited)是我在从我找到的路径构建我的方式时添加的访问标志。它让我可以避免在某些情况下多次进入同一个房间。

    如果我有多个边缘进入,在 Edmond-Karps 中,流动将受到边缘的限制。这意味着如果我有 4 条边到一个节点,我可以有 2 个元素进入,只要我有 2 个其他边缘让元素出去。这种检查避免了这种情况。

    但是,这就是我的主要问题,通过这样做,我阻止了一些可能穿过迷宫的路径。

    下面的图片会告诉你问题所在。
    没有我添加的检查,Edmonds-Karp 运行良好,但使用边缘来找到最佳流程:

    Edmonds-Karp

    这是我添加支票以避免两次使用同一个房间时的解决方案:

    Modified Edmonds-Karp

    这是我想找到的:

    Needed paths

    有什么方法可以修改我的 Edmonds-Karp 实现以获得我想要的?
    如果没有,我可以使用任何其他算法吗?

    非常感谢大家的耐心等待!

    PS:我不能嵌入图片,因为我没有足够的声誉:'(

    最佳答案

    让我们从一些简单的事情开始,假设我们有一个简单的图,其中有两个节点 A 和 B,A 连接到 B:A <-> B
    对于每个节点,添加一对节点,A 为 SA 和 EA,B 为 SB 和 EB。(S 表示开始,E 表示结束)

  • 从 SA,向节点 EA 添加一条定向边,其容量等于节点 A 的容量。
  • 对节点 B 应用相同的步骤,

  • 现在,我们有一个看起来像这样的图:
    SA -> EA  
    SB -> EB
    

    为了表示 A 和 B 之间的连接,我们添加了一条从 EA -> SB 具有无限(非常大)容量的有向边,类似地,我们添加了一条从 EB -> SA 的有向边

    所以,我们的最终图是:
    SA -> EA  
    SB -> EB
    EA -> SB
    EB -> SA
    

    我们意识到,使用类似的过程,这种转换也可以很容易地应用于更复杂的图。

    应用变换后,现在我们可以使用标准的最大流算法来解决这个问题。干杯!

    关于具有非加权、双向边和具有流容量的节点的流解析算法的 C 实现,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49045897/

    相关文章:

    c++ - 适应 Boyer-Moore 实现

    Python 网络 x : edge contraction

    c - 具有动态链表的图形表示

    c - 如何在 Shell FFmpeg 中转义单引号?

    c - 调用结构成员时出现段错误 11

    c - 扫描 double 和字符

    algorithm - 单源最短双子路径

    algorithm - 生成均匀随机排列

    c - C:位字段和按位运算符

    java - 轴标签的 x-y 图和旋转文本