С реализация алгоритма разрешения потока с невзвешенными двунаправленными ребрами и узлами с пропускной способностью

Я попытался найти переполнение стека для ответа на мой вопрос. Я нашел эти ответы, но их решение не относится к моему делу, так как у меня есть ненаправленные края. Я не могу создать новую вершину с ребрами, идущими в Vin, и ребрами, идущими в Vout, так как нет "входящего" или "выходящего" в определенном направлении.

Алгоритм Эдмондса-Карпа для графа, который имеет узлы с пропускной способностью

(был второй вопрос стека, который я не могу найти, но это был тот же ответ)

Начальная проблема

Моя проблема в том, что у меня есть граф, где узлы имеют емкость, все ребра двунаправлены, и мне нужно найти все пути, которые позволят мне максимизировать поток N элементов через граф.

В основном это лемин с комнатами вместимостью 1 и двунаправленными ребрами бесконечной вместимости.

Представьте себе лабиринт, в котором в туннеле может находиться как можно больше людей, но только один человек на комнату. Они могут перемещаться из одной комнаты в другую за один ход. Как я могу сделать так, чтобы у меня были все возможности пройти от начала до конца лабиринта, не имея двух человек в одной комнате.

Реализация Эдмондс-Карп

Мне удалось реализовать Edmonds-Karp (вероятно, очень плохо), используя матрицу смежности (это 1-мерный массив целых чисел, использующий биты для проверки наличия соединения или нет).

У меня есть 3 функции, функция, которая запускает сам алгоритм (я немного упрощаю код, такой как удаление защиты для malloc, освобождения и т. Д., Чтобы алгоритм выглядел лучше):

  1. Основной алгоритм цикла

Это основной цикл. Я пытаюсь найти путь расширения. Если я этого не сделаю, это означает, что родительский номер (приемника) конечной комнаты будет равен начальному значению (-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. Найти увеличивающий путь

Тогда есть функция, которая находит путь дополнения. Я просто использую 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. Применить добавочный путь

Функция, которая применяет путь дополнения, довольно проста, так как в моем случае емкость равна 1 для всех ребер. Мы просто возвращаемся с конца (раковина), пока не достигнем начала (касание), используя идентификаторы, сохраненные в пути. Для каждой комнаты мы заполняем емкость от родителя к ребенку и освобождаем емкость от ребенка к родителю.

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) это флаг посещения, который я добавляю, когда строю свой путь по найденному пути. Это позволяет мне избегать занимать одну и ту же комнату несколько раз в некоторых ситуациях.

Если у меня будет несколько ребер, в Эдмонде-Карпсе поток будет ограничен ребрами. Это означает, что если у меня есть 4 ребра к узлу, у меня может быть 2 элемента, если у меня есть 2 других ребра для элементов. Эти проверки позволяют избежать этой ситуации.

НО, и это моя главная проблема, делая это, я блокирую некоторые возможные пути через лабиринт.

Следующие фотографии покажут вам проблему. Без моей дополнительной проверки Эдмондс-Карп работает хорошо, но использует ребра, чтобы найти лучший поток:

Эдмондс-Karp

Вот решение, когда я добавляю свой чек, чтобы не использовать одну и ту же комнату дважды:

Модифицированный Эдмондс-Карп

Вот что я хотел бы найти:

Необходимые пути

Есть ли способ изменить мою реализацию Edmonds-Karp, чтобы получить то, что я хочу? Если нет, есть ли другой алгоритм, который я мог бы использовать?

Спасибо всем большое за ваше терпение!

PS: я не могу вставлять картинки, потому что мне не хватает репутации:'(

1 ответ

Решение

Давайте начнем с чего-то простого, предполагая, что у нас есть простой граф с двумя узлами A и B, A, соединенными с B: A <-> B

Для каждого узла добавьте одну пару узлов, SA и EA для A, и SB и EB для B. (S означает начало и E означает конец)

  • Из SA добавьте направленное ребро к узлу EA с емкостью, равной емкости узла A.
  • Примените те же шаги с узлом B,

Теперь у нас есть график выглядит так:

SA -> EA  
SB -> EB

Чтобы представить связь между A и B, мы добавляем направленное ребро из EA -> SB с неограниченной (очень большой) емкостью, аналогично, мы добавляем направленное ребро из EB -> SA

Итак, наш последний график:

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

Мы понимаем, что это преобразование может быть легко применено к более сложному графу, используя аналогичный процесс.

После применения преобразования теперь мы можем использовать стандартный алгоритм максимального потока для решения этой проблемы. Ура!

Другие вопросы по тегам