用于流量分辨率的算法的C实现,具有非加权双向边缘和流量为

时间:2018-03-01 08:52:12

标签: c algorithm graph adjacency-matrix edmonds-karp

我试过查看堆栈溢出来回答我的问题。我找到了那些答案,但他们的解决方案并不适用于我的情况,因为我有非定向边缘。我无法创建一个新的顶点,边缘位于Vin,边缘位于Vout,因为在特定方向上没有“进入”或“向外”。

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

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

初始问题

我的问题是我有一个图表,其中节点有容量,所有边都是双向的,我需要找到所有允许我通过图表最大化N个元素流的路径。

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

想象一个迷宫,你可以在隧道中拥有尽可能多的人,但每个房间只有一个人。他们可以在一个回合中从一个房间移动到另一个房间。我怎样才能做到这一点,以便让人们从迷宫的开始到结束都有所有的方式,而不需要在同一个房间里有2个人。

Edmonds-Karp的实施

我已经设法使用邻接矩阵(它是使用位来检查是否存在连接的1d整数数组)来实现Edmonds-Karp(可能非常差)。

我有3个函数,一个运行算法本身的函数(我正在简化代码,例如删除对mallocs的保护,释放等等......这样算法看起来更好)< / EM>:

  1. 主算法循环
  2. 这是主循环。我试图找到一条增强路径。如果我不这样做,那意味着终端房间(接收器)父节点将是初始值(-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);
        }
    }
    
    1. 找到扩充路径
    2. 然后有一个函数可以找到一个扩充路径。我只是使用带队列的BFS,弹出父级,然后检查所有孩子。 如果一个孩子有一个前向连接但仍有容量,我将它添加到路径中,标记它被访问并将其推入队列中。 如果一个孩子有一个反向连接并且流经过它,我将它添加到路径中,标记它被访问并将其推入队列。

      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. 应用扩充路径
      2. 应用扩充路径的函数非常简单,因为在我的情况下,所有边的容量都是1。我们只是从末尾(接收器)返回,直到我们通过使用路径中保存的ID到达开始(点击)。 对于每个房间,我们填写从父母到孩子的容量以及从孩子到父母的免费容量。

        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:由于我没有足够的声誉,我无法嵌入图片:'(

1 个答案:

答案 0 :(得分:2)

让我们从简单的事情开始,假设我们有一个简单的图形,其中包含两个节点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添加了一个方向边 - &gt;具有无限(非常大)容量的SB,类似地,我们从EB添加方向边缘 - &gt; SA

所以,我们的最终图表是:

SA -> EA  
SB -> EB
EA -> SB
EB -> SA

我们意识到,使用类似的过程,这种转换也可以轻松应用于更复杂的图形。

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