Самый длинный цикл в графике

18

Учитывая ориентированный граф, выведите самый длинный цикл.

правила

  • Разрешен любой разумный формат ввода (например, список ребер, матрица связности).
  • Метки не важны, поэтому вы можете наложить любые ограничения на метки, которые вам нужны и / или желательны, если они не содержат дополнительную информацию, не указанную во входных данных (например, вы не можете требовать, чтобы узлы в циклах были помечены целыми числами, а другие узлы помечены буквенными строками).
  • Цикл - это последовательность узлов, которые все связаны, и ни один узел не повторяется, кроме узла, который является началом и концом цикла ( [1, 2, 3, 1]это цикл, но [1, 2, 3, 2, 1]это не так).
  • Если график является ациклическим, самый длинный цикл имеет длину 0 и, следовательно, должен давать пустой вывод (например, пустой список, вообще не выводить).
  • Повторять первый узел в конце списка узлов в цикле необязательно ( [1, 2, 3, 1]и [1, 2, 3]обозначать тот же цикл).
  • Если есть несколько циклов одинаковой длины, любой или все из них могут быть выведены.
  • Встроенные функции разрешены, но если ваше решение использует их, рекомендуется включить альтернативное решение, которое не использует тривиализируемые встроенные функции (например, встроенное, которое выводит все циклы). Тем не менее, альтернативное решение не будет учитываться при подсчете очков, поэтому оно не является обязательным.

Тестовые случаи

В этих тестовых случаях входные данные задаются в виде списка ребер (где первый элемент является узлом источника, а второй элемент является узлом назначения), а выходные данные представляют собой список узлов без повторения первого / последнего узла.

[(0, 0), (0, 1)] -> [0]
[(0, 1), (1, 2)] -> []
[(0, 1), (1, 0)] -> [0, 1]
[(0, 1), (1, 2), (1, 3), (2, 4), (4, 5), (5, 1)] -> [1, 2, 4, 5]
[(0, 1), (0, 2), (1, 3), (2, 4), (3, 0), (4, 6), (6, 8), (8, 0)] -> [0, 2, 4, 6, 8]
[(0, 0), (0, 8), (0, 2), (0, 3), (0, 9), (1, 0), (1, 1), (1, 6), (1, 7), (1, 8), (1, 9), (2, 1), (2, 3), (2, 4), (2, 5), (3, 8), (3, 1), (3, 6), (3, 7), (4, 1), (4, 3), (4, 4), (4, 5), (4, 6), (4, 8), (5, 0), (5, 8), (5, 4), (6, 0), (6, 1), (6, 2), (6, 3), (6, 4), (6, 5), (6, 6), (6, 7), (6, 9), (7, 0), (7, 1), (7, 2), (7, 3), (7, 4), (7, 5), (7, 8), (7, 9), (8, 0), (8, 1), (8, 2), (8, 5), (8, 9), (9, 1), (9, 2), (9, 3), (9, 4), (9, 5), (9, 6)] -> [0, 9, 6, 7, 8, 2, 5, 4, 3, 1]
[(0, 0), (0, 2), (0, 4), (0, 5), (0, 7), (0, 9), (0, 11), (1, 2), (1, 4), (1, 5), (1, 8), (1, 9), (1, 10), (2, 0), (2, 1), (2, 3), (2, 4), (2, 5), (2, 6), (3, 0), (3, 1), (3, 5), (3, 6), (3, 7), (3, 8), (3, 9), (3, 11), (4, 1), (4, 3), (4, 7), (4, 8), (4, 9), (4, 10), (4, 11), (5, 0), (5, 4), (5, 6), (5, 7), (5, 8), (5, 11), (6, 0), (6, 8), (6, 10), (6, 3), (6, 9), (7, 8), (7, 9), (7, 2), (7, 4), (7, 5), (8, 8), (8, 9), (8, 2), (8, 4), (8, 7), (9, 0), (9, 1), (9, 2), (9, 3), (9, 6), (9, 10), (9, 11), (10, 8), (10, 3), (10, 5), (10, 6), (11, 2), (11, 4), (11, 5), (11, 9), (11, 10), (11, 11)] -> [0, 11, 10, 6, 9, 3, 8, 7, 5, 4, 1, 2]
Mego
источник
Во всех ваших примерах ваш вывод начинается с узла с наименьшим индексом. Это требование?
Дада
@ Дада Нет, это просто совпадение с тестами. Вывод должен начинаться (и необязательно заканчиваться) первым узлом в цикле.
Мего
Вы должны выбрать формат, с конечной точкой или без, является произвольным и ничего не добавляет к проблеме.
Волшебная урна осьминога
5
@carusocomputing Я не согласен. Последний узел является неявным, если он выключен (поскольку он такой же, как и первый узел). Разрешение выбора повторять или нет первый узел дает больше свободы в игре в гольф.
Мего

Ответы:

4

Mathematica, 80 58 байт

Спасло колоссальные 22 байта благодаря JungHwan Min

(FindCycle[#,∞,All]/.{}->{Cases[#,v_v_]})[[-1,;;,1]]&

это три байта частного использование символов , U+F3D5представляющие \[DirectedEdge]. Чистая функция с первым аргументом #должна быть списком направленных ребер. Находит Allциклы длины максимум InfinityвGraph@# , затем заменяют пустой список со списком самооценок петель. Циклы представлены в виде списков ребер и отсортированы по длине, поэтому мы берем последний такой цикл, затем из всех его ребер мы берем первый аргумент, чтобы получить список вершин в указанном выходном формате.

Если только Mathematica рассматривает циклы как цикл длины 1( AcyclicGraphQ @ CycleGraph[1, DirectedEdges -> True]дает True, серьезно), то мы могли бы сохранить другие 26байты:

FindCycle[#,∞,All][[-1,;;,1]]&
ngenisis
источник
1
Вам не нужно, MaximalByпотому что результат FindCycleуже отсортирован по длине (последний элемент самый длинный). Кроме того, первый аргумент FindCycleможет быть списком \[DirectedEdge](вместо Graph). Кроме того , вы можете использовать 2-байты ;;(= 1;;-1) вместо 3- х байт Allв , Partчтобы сохранить байты. -22 байта (58 байтов):(FindCycle[#,∞,All]/.{}->{Cases[#,v_v_]})[[-1,;;,1]]&
JungHwan Мин
3

Haskell , 157 154 150 байт

import Data.List
g#l=nub[last$(e:d):[d|p==last q||e`elem`init d]|d@(p:q)<-l,[e,f]<-g,p==f]
h g=snd$maximum$((,)=<<length)<$>[]:until((==)=<<(g#))(g#)g

Попробуйте онлайн!

Спасибо @Laikoni и @Zgrab за сохранение нескольких байтов!

Это очень неэффективная программа:

Первая функция #берет список путей l(список списков чисел) и пытается расширить элементы l, добавляя каждое возможное ребро (список длиной 2) gкаждого элемента l. Это происходит только в том случае, если элемент lне является циклом и если новый добавляемый узел еще не содержится в элементе l. Если это уже цикл, мы ничего не добавляем, а добавляем его снова в новый список путей, если мы можем его расширить, мы добавляем расширенный путь в новый список, в противном случае мы не добавляем его в новый список. ,

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

flawr
источник
Вы можете оставить скобки в (p:q)<-l.
Лайкони
И использование <$>вместо mapдолжно сохранить другой байт в ((,)=<<length)<$>[]:.
Лайкони
@Laikoni Спасибо большое!
flawr
У вас есть дополнительное место после последней строки. Кроме того, выполнение d@(p:q)<-lэкономит несколько байтов.
Згарб
О, d@(p:q)это действительно мило, спасибо, что показали мне!
flawr
2

Pyth, 20 байт

eMefqhMT.>{eMT1s.pMy

Тестирование

Принимает список ребер, как в примерах.

Объяснение:

eMefqhMT.>{eMT1s.pMy
eMefqhMT.>{eMT1s.pMyQ    Variable introduction
                   yQ    Take all subsets of the input, ordered by length
                .pM      Reorder the subsets in all possible ways
               s         Flatten
                         (This should be a built in, I'm going to make it one.)
   f                     Filter on (This tests that we've found a cycle)
    qhMT                 The list of first elements of edges equals
           eMT           The last elements
         .>   1          Rotated right by 1
        {                Deduplicated (ensures no repeats, which would not be a
                         simple cycle)
  e                      Take the last element, which will be the longest one.
eM                       Take the last element of each edge, output.
isaacg
источник
2

Bash + bsdutils, 129 байт

sed 's/^\(.*\) \1$/x \1 \1 x/'|sort|(tsort -l>&-)|&tr c\\n '
 '|sed 's/x //g'|awk 'm<NF{m=NF;gsub(/[^0-9 ] ?/,"");print}'|tail -1

tsort выполняет всю тяжелую работу, но его выходной формат довольно уникален, и он не определяет циклы длины 1. Обратите внимание, что это не работает с GNU tsort.

верификация

--- t1 ---
0
--- t2 ---
--- t3 ---
0 1
--- t4 ---
1 2 4 5
--- t5 ---
0 2 4 6 8
--- t6 ---
0 2 1 6 3 7 4 8 9 5
--- t7 ---
0 11 10 3 1 2 4 7 5 8 9 6
Деннис
источник
2

JavaScript (ES6), 173 163 156 145 139 байт

Сохранено 5 байт благодаря @Neil

f=(a,m,b=[])=>a.map(z=>!([x,y]=z,m&&x-m.slice(-1))&&b.length in(c=(n=m||[x],q=n.indexOf(y))?~q?b:f(a.filter(q=>q!=z),[...n,y]):n)?b=c:0)&&b

Тестовый фрагмент

ETHproductions
источник
Конечно, переход на простой старый mapэкономит вам пару байтов?
Нил
@Neil Это должно быть .filter().map(), так что почти наверняка нет. Переключатель сэкономил мне 10 байт (хотя он не был таким же удачным, как сейчас)
ETHproductions
Я не вижу, чтобы вы использовали результат понимания, поэтому вместо использования a.filter(z=>!e).map(z=>d)вы можете использовать a.map(z=>e?0:d).
Нил
Вы правы, я могу объединить все, чтобы сэкономить 5 байтов. И я только что понял, что мне тоже не нужно a+a?:-)
ETHproductions
Может ли downvoter объяснить, в чем дело? Это дает неправильные результаты?
ETHproductions
2

Haskell , 109 108 байт

import Data.List
f g=last$[]:[b|n<-[1..length g],e:c<-mapM(\_->g)[1..n],b<-[snd<$>e:c],b==nub(fst<$>c++[e])]

Решение для грубой силы: генерируйте все списки ребер с увеличивающейся длиной до длины ввода, сохраняйте циклы и возвращайте последний. Берет график в формате [(1,2),(2,3),(2,4),(4,1)]. Попробуйте онлайн!

объяснение

f g=                    -- Define function f on input g as
  last$                 -- the last element of the following list
  []:                   -- (or [], if the list is empty):
  [b|                   --  lists of vertices b where
   n<-[1..length g],    --  n is between 1 and length of input,
   e:c<-                --  list of edges with head e and tail c is drawn from
    mapM(\_->g)[1..n],  --  all possible ways of choosing n edges from g,
   b<-[snd<$>e:c],      --  b is the list of second elements in e:c,
   b==                  --  and b equals
    nub(fst<$>c++[e])]  --  the de-duplicated list of first elements
                        --  in the cyclic shift of e:c.
Zgarb
источник
Прошло некоторое время, пока я наконец не понял, что происходит, часть проверки путей / циклов действительно умная, я поражен!
flawr
@flawr Спасибо! Ну, похоже, что isaacg использовал по сути тот же алгоритм до меня.
Згарб
0

MATLAB, 291 260 байт

Принимает матрицу приличия, в Aкоторой ребро (i,j)обозначено как 1in A(i,j), и Aравно нулю во всех других записях. Вывод представляет собой список самого длинного цикла. Список пуст, если цикла вообще нет, и список содержит начальную и конечную точки, если цикл существует. Он использует 1индексацию на основе.

Это решение не использует никаких встроенных функций, связанных с графиками.

function c=f(A);N=size(A,1);E=eye(N);c=[];for j=1:N;l=g(j);if numel(l)>numel(c);c=l;end;end;function p=g(p)if ~any(find(p(2:end)==p(1)))e=E(p(end),:)Q=find(e*A)k=[];for q=Q;if ~ismember(q,p(2:end))n=g([p,q]);if numel(n)>numel(k);k=n;end;end;end;p=k;end;end;end

К сожалению, это не выполняется в TryItOnline, поскольку использует функцию внутри функции, которая является рекурсивной. Небольшая модификация позволяет вам попробовать это на octave-online.net .

Для самого последнего контрольного примера я нашел альтернативный самый длинный цикл [0 2 1 4 3 5 7 8 9 11 10 6 0](в этой записи используется индексирование на основе 0)

объяснение

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

function c=f(A);
N=size(A,1);
E=eye(N);
c=[]; % current longest cycle
for j=1:N;                                      % iterate over all nodes
    l=getLongestCycle(j);                       % search the longest cycle through the current node
    if numel(l)>numel(c);                       % if we find a longer cycle, update our current longest cycle
        c=l;
    end;

end;

    function p=getLongestCycle(p);              % get longest cycle from p(1) using recursion
        if ~any(find(p(2:end)==p(1)));          % if we just found a cycle, return the cycle do nothing else, OTHERWISE:
            e=E(p(end),:);                      % from the last node, compute all outgoing edges
            Q=find(e*A);                        
            k=[];                               
            for q=Q;                            % iterate over all outogoin edges
                if ~ismember(q,p(2:end));       % if we haven't already visited this edge,
                    n=getLongestCycle([p,q]);   % recursively search from the end node of this edge
                    if numel(n)>numel(k);       % if this results in a longer cycle, update our current longest cycle
                        k=n;
                    end;
                end;
            end;
            p=k;
        end;
    end; 
end
flawr
источник