Задать вопрос
Ответы пользователя по тегу Графы
  • Как найти или показать существование цикла в ориентированном графе быстрее, чем за O(IVI+IEI)?

    wataru
    @wataru Куратор тега Математика
    Разработчик на С++, экс-олимпиадник.
    Никак.

    Это можно доказать. Вам так или иначе придется посмотреть на все ребра. Допустим, есть алгоритм, который всегда может проверить цикл, смотря не все ребра. Рассморим какой-то граф без циклов. Алгоритм там какие-то ребра посмотрел и сказал, что циклов нет. А мы возьмем и скормим этому алгоритму почти такой же граф, только одно из ребер, которые он вообще не трогал, сделаем обратным какому-то другому ребру в графе, сделав таким образом цикл. Но алгоритм посмотрит на те же самые ребра, увидет все то же самое и сделает точно такой же вывод, что циклов нет, и ошибется. Все потому что мы допустили алгоритм, который всегда смотрит не все ребра. Значит таких алгоритмов нет и там всегда будет хотя бы O(|E|).
    Ответ написан
    2 комментария
  • Возможно ли обнаружить сортировкой Кана изолированные узлы, сортировать сразу рёбра и не использовать степень?

    wataru
    @wataru Куратор тега Математика
    Разработчик на С++, экс-олимпиадник.
    Зачем в алгоритме сортировки Кана вначале требуется степень или просчёт всех соединений, вроде и без неё всё работает?


    Без нее работает медленно. Смотрите алгоритм. В алгоритме надо брать вершину в которую не входит ни одно ребро, а также удалять вершины. Кончено, можно наивно брать все вершины, потом брать все ребра, смотреть, не удалено ли ребро и не ведет ли оно в эту вершину. Но это будет O(VE) для поиска одной вершины, а суммарно алгоритм будет O(V^2E). Когда как реализация с подсчетом степеней и очередью будет O(V+E).

    Есть ли возможность сортировать не узлы, а рёбра сразу?

    Не совсем, потому что порядок вершин - это основа. Можно во время работы алгоритма вместо вершины выдавать все исходящие из нее ребра, будет у вас сортировка ребер. Но суть алгоритма остается той же самой.

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


    Можно. Изолированные узлы - это те в которые ни одно ребро не входит. В алгоритме Кана это те, которые можно вывести до удаления любой вершины.
    Ответ написан
  • Как записать DAG для прохода по ациклическому ориентированному графу, используя C#?

    wataru
    @wataru Куратор тега Математика
    Разработчик на С++, экс-олимпиадник.
    Раз вам надо обойти врешины в топлолгическом порядке, вам нужна топологическая сортировка. Если у вас только одна корневая вершина, то можно и проще - тут сработает любой обход: в ширину или в глубину из корневой вершины. Если корневых вершин несколько, то надо будет использовать какой-либо алгоритм топологической сортировки. Гуглите его. Есть алгоритм основанный на двух обходах в глубину, или нечто основанное на обходе в ширину.

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

    Но все перечисленные тут алгоритмы являются примерно одинаково эффективными. Без бенчмарков нельзя сказать какой из них лучше, и на разных формах графов одни могут быть лучше других и наоборот.
    Ответ написан
    33 комментария
  • Как решать задачу с графом?

    wataru
    @wataru
    Разработчик на С++, экс-олимпиадник.
    Все становится сложно из-за того, что можно второй раз проходить по комнате и при этом не тратить время на гоблина и не получая награды. Мне кажется, можно задачу коммивояжора свести к этой задаче, так что она NP-полная и тут нет быстрого алгоритма. Только перебор или что-то экспоненциальное. Соответственно, оно все работает лишь не небольших гарафах.

    Можно применять тот же прием, что и в задаче коммивояжора - расслоите граф на 2^n слоев. Пуст у вас n вершин в изначальном графе. В новом графе каждая вершина соответсвует подмножеству комнат и одной комнате. Фактически, вершина обозначает, какие комнаты вы обошли и где сейчас находитесь.

    Ребра есть:
    Из вершины ({}, 0) во все вершины ({v},v) ценой goblinTime[v]: это мы можем зайти в лабиринт в любую комнату и должны там гоблина победить.
    Из вершины (S, v) в вершину (S,u) ценой path[v,u], если u в S - это мы идем в ранее посещенную комнату.
    Из вершины (S, v) в вершину (S+{u},u) ценой path[v,u]+goblitTime[u], если u не в S - это мы идем в ранее не посещенную комнату и бъем там гоблина.

    В этом графе на n2^n вершин вам надо найти все кратчайшие пути из начальной ({},0) во все вершины (например, дейкстрой). Потом переберите все вершины в графе (S, v) и, если вершина соответствует комнате с выходом и вы до нее дошли за время не превосходящее время обрушения, то берите сумму количества золота в комнатах в множестве S в качесвте варианта ответа. Из всех них берите максимум.
    Ответ написан
    Комментировать
  • В чем суть ассиметричного графа?

    wataru
    @wataru
    Разработчик на С++, экс-олимпиадник.
    Суть в том, что в графе нет одинаковых вершин или групп вершин. Их нельзя как-то переставить, что граф останется выглядеть точно так же. Вот пример граф из 4 вершин в виде квадрата. Его крутить можно как угодно и отражать - он выглядит точно так же - квадрат он и есть квадрат. А вот треугольник из одной вершины которого торчит путь длины 1, а из другой вершины - путь длины 2 - он несимметричный. Каждая вершина по своему уникальна. Одна вершина на треугольнике имеет степень 2. Одна имеет степень 3 и до ближайшей вершины-листа путь длины 1, Одна имеет степень 3 и до ближайшей вершины листа путь длины 2. Есть только одна вершина степени 2 не на цикле. Оставшиеся 2 вершины имеют степени 1, но от одной до цикла путь длины 1, а от другой - 2. Вы эти вершины как-то поменяйте местами и у вас граф будет выглядеть по другому.

    Не обязательно все вершины должны иметь разную степень. Но их нельзя поменять местами, потому что они связаны с другими вершинам по разному.
    Ответ написан
    2 комментария
  • Как найти количество помеченных связных графов?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Формула включения-исключения. Берете все графы, где хотя бы одна вершина соединена со всеми (их n*2^(n-2)*(n-1)), вычитаете все графы, где хотя бы две вершины соеденины со всеми (2 раза, ведь они 2 раза подсчитались), прибавляете графы, где хотя бы 3 вершины соеденины со всеми (3 раза)... И т.д.

    Графов, где хотя бы k вершин имеют степень n-1 - C(n, k)*2^{(n-k-1)(n-k)/2}: тут можно выбрать k вершин и для каждого ребра из оставшихся n-k вершин есть 2 варианта - оно или есть, или нет.

    Это будет за O(n log n) решение, если пердподсчитать все факториалы и обратные к им по модулю в задаче. Ну, или O(n^2), если считать сочетания через треугольник паскаля.

    Ражеванное объяснение:

    Сложно подсчитать количество графов где ровно 1 вершина такая полная. Но легко подсчитать те, где k или более таких. Мы их такие зафиксируем и нарисуем от них все ребра. А все оставшиеся ребра в графе могут быть любыми. Во-первых, можно выбрать эти k вершин - поэтому у нас есть множитель C[n,k]. Оставшиеся, незафиксированные ребра идут между любыми двумя оставшимся n-k вершин. Их (n-k)(n-k-1)/2. И каждое может быть или проведено или нет.

    Поэтому всего таких графов, с не менее k вершин: F(k)=C[n,k]*2^{(n-k)(n-k-1)/2}.

    Теперь, как подсчитать графы ровно с 1 вершиной? Можно взять F(1). Но мы насчитали много лишнего, Графы с 2мя такими вершинами мы в F(1) подсчитали 2 раза. Поэтому вычтем 2F(2). Теперь графы ровно с 3 вершинами мы подсчитали 3 раза в F(1) и 3 раза в каждом F(2). Поэтому пока мы их насчитали 3-2*3 = -3 раза. Поэтому прибавим 3F(3). И далее, получится, что графы ровно с 4-мя вершинами мы подсчитали 4 раза (4-2*6+3*4). И т.д.
    Ответ написан
    6 комментариев
  • Как построить граф по его граням?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Как-то странно вы его релизовали. В самом алгоритме надо вершины и раскладывать на плоскости. Когда первый цикл укладываете - можно просто по большой окружности. Потом ребра или напрямую, если они внутри грани, или по дуге какой-то окружности вокруг.
    Ответ написан
  • В чем проблема в коде работы с графом?

    wataru
    @wataru Куратор тега C++
    Разработчик на С++, экс-олимпиадник.
    Ошибка в том, что у вас граф криво задан. У вас там ровно 6 списков. А размер графа - 7. Поэтому в MakeStep вы обращаетесь к graph[6], а это undefined behavior. А там непонятно, что происходит. Может вы таблицу портите каким-то значениями и никогда положительного значения не получаете. Скорее всего корраптите память через обращение к мусорным adj в std::array (который на стеке лежит) и получаете мусор в каких-то локальных переменных.
    Ответ написан
  • Как найти компоненты связности в графе в распределенной памяти?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Простой подход, если у вас есть выделенный лидер и по графу распространяется одна волна в каждый момент времени.

    Лидер опрашивает остальные сервера по очереди (включая себя), и справшивает их, есть ли у них еще непокрашенные вершины. Если есть, инструктирует их запустить волну. Когда волна закончится, справшивает опять. Когда сервер говорит, что больше непокрашенных вершин нет - лидер переходит к следующему серверу.

    Когда сервер запускает волну - он обходит все вершины у себя, если встречает призрачное ребро, то посылает сообщение другому серверу запустить обход у себя с присоеденненной вершины.
    Чтобы отследить конец волны, перед запуском обхода в другом сервере, каждый сервер отслывает лидеру сообщение, что вот тот сервер теперь будет работать. Когда волна в каком-то сервере заканчивается, он присылает лидеру сообщение, что все закончилось. Лидер поддерживает счетчик - сколько еще серверов работает. Когда счетчик достигает 0, надо еще подождать немного (скажем, пол секунды), на случай реордеринга сообщений. Если в течении этой пол секунды никаких сообщений не пришло, можно запускать новую волну.

    Более сложный вариант - когда у вас сразу запускаются все волны. Опять же, без лидера совсем сложно будет.
    Тут, наверно стоит сначала в каждом отдельном параграфе найти все компоненты связности отдельно. Это будут промежуточные варианты.

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

    Когда сервер получает сообщение о номере по призначному ребру, если этот номер меньше текущего у компоненты, она перекрашивается. И сообщение рассылается по всем остальным призрачным ребрам, торчащим из этой компоненты. Рано или поздно, все вершины одной компоненты по всем серверам будут помечены минимальным номером.

    Чтобы определить, что все закончилось, можно разбить процесс на фазы. В первой фазе все рассылают свои номера. Во второй фазе только те, у кого номера обновились. И т.д. Фаза на сервере начинается после сообщения от лидера. Каждый сервер отправляет на лидер сообщение, когда он закончил рассылать свои номера и были ли там вообще обновления. Сообщения надо делать по tcp с подтверждением. Когда лидер заметит, что никто никаких сообщений больше не рассылал в предыдущей фазе - все сошлось.
    Ответ написан
    1 комментарий
  • Почему поиск в ширину работает?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Код вообще странно реализует поиск в ширину. Я уверен, что на каких-то тестах оно работать не будет. Зависит от порядка вершин в usersMap. Т.е. если вы их все переименуете, вы можете получить другой ответ.

    Вообще тут не нужен обход в ширину, тут, кажется, достаточно цикла по всем вершинам.

    А так ваш алгоритм зачем-то имеет внешний цикл по User user и если он натыкается на какую-то еще не посещенную вершину, то дальше уже перебирает все вершины в очереди и добавляет их соседей в очередь в цикле, фактически выполняя обход в ширину. Правда там в очереди будет лежать какая-то случайная вершина из цикла по user. Так что это будет обход вширину сразу из двух вершин.

    На какой-то следующей итерации цикла он опять запустит обход в ширину от какой-то еще не посещенной вершины, если в графе несколько компонент связности. В итоге первый обход в ширину может затронуть одну или 2 компоненты связности, а все следующие обходы обойдут по одной компоненте.
    Ответ написан
    Комментировать
  • Как найти минимальное число прохождений по автомату до конечного состояния с N вероятностью?

    wataru
    @wataru Куратор тега Математика
    Разработчик на С++, экс-олимпиадник.
    Есть простой трюк сильно упростить задачу: Измените переходы из "конечной" вершины - она теперь с вероятностью 100% будет вести только в саму себя. Таким образом, процесс хотя бы раз достигший вершины, навсегда в ней останется.

    А вопрос из задачи (если его понимать как: минимальное количество шагов, чтобы хотя бы раз посетить конечную вершину с вероятностью >99%) становится эквивалентен: минимальное количество шагов, чтобы быть в конечной вершине с вероятностью не меньше 99%.

    А тут уже надо найти минимальное число k, такое что соответствующее значение в матрице A^k было бы > 0.99. A тут - это матрица переходов.

    Можно или в тупую умножать матрицу саму на себя, пока значение в строке начальной вершины и столбце конечной вершины не станет достаточно большим. Это будет O(N^3*ответ). Или можно делать хитро: бинарным поиском перебирайте степень, а внутри логарифмическим возведением в степень считайте A^k. Это будет O(N^3*log(ответ)^2).
    Ответ написан
    Комментировать
  • Поиск кратчайшего пути в ориентированном графе с цветными вершинами и дугами?

    wataru
    @wataru
    Разработчик на С++, экс-олимпиадник.
    Тут надо сделать из графа на N вершин граф на N^2 вершин. Граф-то совсем маленький. Каждая вершина нового графа соответствует паре вершин изначалного графа (x, y) и обозначает, что фишки стоят в вершинах x и y соответственно. Переходы в новом графе делаются из переходов в изначальном: из (x, y) можно перейти в (x, z), если в изначальном графе есть дуга y->z и ее цвет совпадает с вершиной x. Аналогично из (x,y) можно перейти в (z,y), если есть дуга x->z и ее цвет совпадает с y.

    В этом графе уже можно любым известным вам способом искать путь. Только тут конечных вершин будет сразу много: это все пары (x,y), где x или у - конечная вершина в графе. Можно или добавить новую конечную вершину и соединить с ней все вот эти вот, или прямо в алгоритме исправить условие останова.

    Можно граф не хранить явно. Если у вас BFS, то храние в очереди сразу пару чисел. Внутри перебирайте переходы из первой а потом из второй вершины и делайте их только если условие на совпадение цвета выполняется. Ну и, все массивы хранящие расстояние и т.п. в алгоритме будут уже двумерные.
    Ответ написан
    Комментировать
  • Как найти такую вершину заданного графа, которая принадлежит каждому пути между двумя выделенными?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Все такие вершины являются точками сочленения. Ведь, удалив такую вершину из графа больше не будет пути между двумя вершинами, а значит граф развалился на новые компоненты связности. Есть алгоритм на основе Dfs, который их ищет. Это все работает за O(V+E). Предложенный вами алгоритм с удалением по одной вершине вполне себе работает, но будет медленнее: O(V(V+E)). Для небольших графов может и подойдет.

    Потом надо найти путь между заданными вершинами и вывести в ответ точки сочленения. Лучше ищите путь тем же DFSом, что и ищите точки сочленения. Так путь будет идти по дереву DFS. Надо найти в нем LCA двух искомых точек (Пик пути, после которого он пойдет вниз в другую ветку).

    Важно, выводить надо не все точки сочленения, а только те, у которых из следующей вершины в пути, она же будет ребенком в дереве DFS, нельзя вернутся назад выше текущей точки сочленения. Т.е. точка назначена алгоритмом точкой сочленения из-за ребенка именно в сторону искомой вершины.
    Ответ написан
    Комментировать
  • Как в целом пишут представление (структуры данных) деревьев в коде? и в чём суть Деревьев?

    wataru
    @wataru
    Разработчик на С++, экс-олимпиадник.
    1) Можно представлять дерево всеми способами, как и граф. Но в этом случае о деревьях обычно и не говорят. Ну граф, ну без цикла может даже быть. Ну и что? Пользы никакой не несет. Вот если же дерево представлять подвешанным - с одним корнем, с детьми и родителем у каждой вершины, то уже есть куча полезных применений. В основном при реализации всяких хитрых структур данных.

    2) Дерево представляется в виде массива. Но не любое. А только полу-полное двоичное дерево (у каждой вершины до 2 детей. Если ребенок один, то левый. Все уровни, кроме последнего заполненны целиком, последний заполнен слева-направо).

    3) Дерево нельзя представить в виде списка в общем случае. Потому что список - линеен, а дерево нет. Можно детей в каждой вершине хранить списком. Ну или вырожденное дерево, где у каждой вершины есть только один ребенек.

    4) Нет.

    5) Нет.
    Ответ написан
  • Чем отличаются двунаправленные графы от графа с дугами в обе стороны?

    wataru
    @wataru
    Разработчик на С++, экс-олимпиадник.
    Ориентированный граф, у которого все ребра идут парами туда и обратно, полностью соответсвует неориентированному графу. Но если хотя бы где-то нет обратной дуги (как на правой картинке), то это уже отличие.

    Почему вообще придумали использовать ориентированне графы? Потому что они полезны. Например, отношение "любовь" между людьми можно описать ориентированным графом, а вот неориентированным - нет (ибо есть безответная любовь).

    Иногда имеет смысл рассматривать неориентированный граф как оринетированный с парными дугами, если в процессе работы симетрия как-то нарушается. Например в алгоритмах поиска максимального потока на графе так и происходит. Поэтому он не работает с неориентированными графами - в них надо каждое ребро разбить на два туда и обратно.
    Ответ написан
    9 комментариев
  • Что за ошибка во время вывода?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Блин, вы что, случайно код пишите, пока он не заработает? Можете вниматиельно прочитать свой код и объяснить себе, зачем там каждая строчка нужна? Прогнать его в дебаггере или в голове - строчка за строчкой, записывая значения переменных в тетрадке?

    У вас опять куча мелких ошибок в коде.

    На вскидку:
    f надо уменьшать там, где у вас закомментированно, после ввода. А то вы после основного цикла обращаетесь по индексу не уменьшенного f и выходите за границу массива.

    Массив p надо переписывать только при удачной релаксации, вы же в основном цикле, после вызова функции еще зачем-то всегда переписываете предка. Из-за этого у вас там полный бред в массиве ссылок к предку получается и цикл вывода, скорее всего, виснет в бесконечном цикле из ссылок.
    Ответ написан
  • Как исправить алгоритм Дейкстры?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Раз уж я в комментариях все разобрал, скопирую в ответ: куча мелких ошибок в коде, вроде:
    - циклы с 1 вместо 0
    - не инициализированные массивы
    - опечатки
    Ответ написан
  • А как предков вывести?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Опять у вас обход вширину написан через одно место.

    В очередь вы должны класть только идентификатор вершины/состояния. В той задаче это были 2 координаты клетки поля. Тут - это четырехзначное число.

    Найденное расстояние, путь к предку, пометка о посещении - все это не кладется в очередь, а лежит отдельно от него в массиве. В некоторых задачах, когда граф огромен (и неявно задан - вроде бесконечного шахматного поля), то нужно завести ассоциативный массив (мап), который хранил бы расстояние/предка/пометку только для посещенных вершин. Тут же меньше 10000 вершин всегда, поэтому тупо заведите массив на 10000 элементов и не мучайтесь.

    Еще раз, в очереди у вас только число, а в массиве рядом пометки о песещении и путь к предку (расстояние конкретно в этой задаче считать и выводить не нужно).

    Взяли из очереди 4-х значное число, произвели над ним 4 возможные операции, получили 4 новых числа. В массиве посмотрели что новое число не было обработано, тогда кладем его в очередь, помечаем обработанным и запоминаем, что для нового числа предок - текущее.

    В конце надо циклом while от конечной вершины по сохраненным предкам идти, пока не упретесь в начало. Потом список обойденных чисел надо развернуть. Или хитрость - проводите обратные операции начиная с конечного состояния, пока не найдете начальное. Операции развернуть просто - вычитайте из первой цифры и прибавляйте к последней. Сдвиги симметричны и остаются как есть. Тогда не надо разворачивать ответ после восстановления циклом while.
    Ответ написан
    Комментировать
  • Как правильно пропарсить лабиринт в граф?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Самое простое - это сделать вершиной каждую клетку и гнать в этом графе поиск в ширину. Можно даже не строить граф явно - а просто при обходе смотреть на четырех соседей и проверять, а не стена ли там, а были ли мы в клетке (x+dx[k], y+dy[k]), и класть координаты в очередь, если надо.

    Вы же хотите считать только перекрестки и повороты врешинами. В худшем случае, если поворотов в лабиринте много - это будет даже медленнее обхода в ширину. Потому что он работает за линию, а дейкстра - за O(n log n) в лучшем случае. Плюс там константа больше у этого n log n. Правда, это можно решить если вместо дейкстры использовать обобщенный 0-1-BFS (BFS с ограниченными ребрами - там где много очередей используется). Но реализация будет сильно посложнее простого BFS.

    Но если так хотите - то делается это так: Сначала пройдитесь по всем клеткам и занумеруйте те, которые не являются коридором (один сосед, больше двух соседей, или соседи не вдоль одной прямой). Можно в отдельной матрице номера проставить.
    Потом пройдитесь по каждому столбцу сверху вниз и по каждой строчке слева направо и храните последнюю встреченную вершину. Если встретили стену - забывайте про эту увиденную раньше врешину. Если встретили просто проход - пропускайте его. Если встретили поворот(вершину) - то создайте ребро между ней и последней увиденной (если она еще в памяти, ведь стена могла ее удалить). Ну и перепишите последнюю увиденную вершину.

    Что-то типа такого:
    int last_node = 0;
    for (i = x_start, j = y_start; i <n && j < m; i+= dx, j+=dy) {
       if (is_wall[i][j]) {
        last_node = -1;
      }  
      if (node_number[i][j] <- 0) continue;
      if (last_node > 0) {
        // добавить ребро между вершинами.
        AddEdge(last_node, node_number[i][j]);
      }
      last_node = node_number[i][j];
    }


    Тут я просто добавляю ребра в строке или столбце между двумя соседними вершинами.
    Ответ написан
    Комментировать
  • Как имитировать разрыв вершин графа?

    wataru
    @wataru Куратор тега C++
    Разработчик на С++, экс-олимпиадник.
    Надо или удалять из графа вершину, или помечать ее удаленной и в вашем алгоритме поиска пути просто пропускать такие помеченные вершины во всех циклах по вершинам.
    Ответ написан
    Комментировать