Ciclo più lungo in un grafico


18

Dato un grafico diretto, emette il ciclo più lungo.

Regole

  • È consentito qualsiasi formato di input ragionevole (ad es. Elenco dei bordi, matrice di connettività).
  • Le etichette non sono importanti, pertanto è possibile imporre restrizioni sulle etichette necessarie e / o desiderate, purché non contengano informazioni aggiuntive non fornite nell'input (ad esempio, non è possibile richiedere che i nodi nei cicli siano etichettato con numeri interi e altri nodi sono etichettati con stringhe alfabetiche).
  • Un ciclo è una sequenza di nodi che sono tutti collegati e nessun nodo viene ripetuto, tranne il nodo che è l'inizio e la fine del ciclo ( [1, 2, 3, 1]è un ciclo, ma [1, 2, 3, 2, 1]non lo è).
  • Se il grafico è aciclico, il ciclo più lungo ha lunghezza 0 e quindi dovrebbe produrre un output vuoto (ad es. Elenco vuoto, nessun output).
  • La ripetizione del primo nodo alla fine dell'elenco dei nodi nel ciclo è facoltativa ( [1, 2, 3, 1]e [1, 2, 3]indica lo stesso ciclo).
  • Se sono presenti più cicli della stessa lunghezza, è possibile che vengano emessi uno o tutti i cicli.
  • I builtin sono consentiti, ma se la tua soluzione ne utilizza uno, sei incoraggiato a includere una soluzione alternativa che non utilizza builtin banalizzanti (ad esempio un builtin che genera tutti i cicli). Tuttavia, la soluzione alternativa non verrà considerata per il tuo punteggio, quindi è del tutto facoltativa.

Casi test

In questi casi di test, l'input viene fornito come un elenco di spigoli (in cui il primo elemento è il nodo di origine e il secondo elemento è il nodo di destinazione) e l'output è un elenco di nodi senza ripetizione del primo / ultimo nodo.

[(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]

In tutti i tuoi esempi, l'output inizia con il nodo con l'indice più piccolo. È un requisito?
Dada,

@Dada No, è solo una coincidenza con i casi di test. L'output dovrebbe iniziare (e facoltativamente terminare) con il primo nodo del ciclo.
Mego

Dovresti scegliere un formato, con endpoint o senza è arbitrario e non aggiunge nulla alla sfida.
Magic Octopus Urn

5
@carusocomputing Non sono d'accordo. L'ultimo nodo è implicito se lasciato fuori (poiché è uguale al primo nodo). Consentire la scelta se ripetere o meno il primo nodo consente una maggiore libertà nel golf.
Mego,

Risposte:


4

Mathematica, 80 58 byte

Salvataggio di ben 22 byte grazie a JungHwan Min

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

è il carattere di uso privato a tre byte che U+F3D5rappresenta \[DirectedEdge]. La funzione pura con il primo argomento #dovrebbe essere un elenco di bordi diretti. Trova Allal massimo i cicli di lunghezza Infinityin Graph@#, quindi sostituisce l'elenco vuoto con l'elenco dei loop automatici. I cicli sono rappresentati come elenchi di bordi e ordinati per lunghezza, quindi prendiamo l'ultimo ciclo, quindi da tutti i suoi bordi prendiamo il primo argomento in modo da ottenere un elenco di vertici nel formato di output specificato.

Se solo Mathematica trattasse i loop come un ciclo di lunghezza 1( AcyclicGraphQ @ CycleGraph[1, DirectedEdges -> True]True, sul serio), allora potremmo salvare un altro 26byte:

FindCycle[#,∞,All][[-1,;;,1]]&

1
Non sarà necessario MaximalByperché il risultato di FindCycleè già ordinato per lunghezza (l'ultimo elemento è il più lungo). Inoltre, il primo argomento di FindCyclepuò essere un elenco di \[DirectedEdge](anziché di a Graph). Inoltre, è possibile utilizzare il 2 byte ;;(= 1;;-1) invece del 3 byte Allin Partper salvare un byte. -22 byte (58 byte):(FindCycle[#,∞,All]/.{}->{Cases[#,v_v_]})[[-1,;;,1]]&
JungHwan Min

3

Haskell , 157 154 150 byte

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

Provalo online!

Grazie @Laikoni e @Zgrab per aver salvato un sacco di byte!

Questo è un programma molto inefficiente:

La prima funzione #prende un elenco di percorsi l(un elenco di elenchi di numeri) e cerca di estendere gli elementi lanteponendo ogni possibile fronte (un elenco di lunghezza 2) di ga ciascun elemento di l. Ciò accade solo se l'elemento di lnon è già un ciclo e se il nuovo nodo che verrebbe anteposto non è già contenuto nell'elemento di l. Se è già un ciclo, non anteponiamo nulla ma lo aggiungiamo di nuovo al nuovo elenco di percorsi, se possiamo estenderlo, aggiungiamo il percorso esteso al nuovo elenco, altrimenti non lo aggiungiamo al nuovo elenco .

Ora la funzione htenta ripetutamente di estendere quei percorsi (iniziando con l'elenco dei bordi stessi) fino a raggiungere un punto fisso, cioè non possiamo estendere ulteriormente alcun percorso. A questo punto abbiamo solo cicli nella nostra lista. Quindi si tratta solo di scegliere il ciclo più lungo. Ovviamente i cicli appaiono più volte in questo elenco poiché ogni possibile rotazione ciclica di un ciclo è di nuovo un ciclo.


È possibile rilasciare le parentesi (p:q)<-l.
Laikoni,

E usando <$>invece di mapdovrebbe salvare un altro byte in ((,)=<<length)<$>[]:.
Laikoni,

@Laikoni Grazie mille!
flawr

Hai uno spazio extra dopo l'ultima riga. Inoltre, facendo d@(p:q)<-lsalva alcuni byte.
Zgarb,

Oh, d@(p:q)è davvero bello, grazie per avermelo mostrato!
flawr

2

Pyth, 20 byte

eMefqhMT.>{eMT1s.pMy

Suite di test

Prende un elenco di spigoli, come negli esempi.

Spiegazione:

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.

2

Bash + bsdutils, 129 byte

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 fa tutto il lavoro pesante, ma il suo formato di output è piuttosto unico e non rileva cicli di lunghezza 1. Nota che questo non funziona con GNU tsort.

Verifica

--- 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 byte

5 byte salvati grazie a @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

Snippet di prova


Passare sicuramente a un semplice vecchio mapti fa risparmiare un paio di byte?
Neil,

@Neil Dovrebbe essere .filter().map(), quindi quasi certamente no. L'interruttore mi ha salvato 10 byte (anche se non era completamente golfato come lo è ora)
ETHproductions

Non ti vedo usare il risultato della comprensione, quindi invece di usare a.filter(z=>!e).map(z=>d)puoi usare a.map(z=>e?0:d).
Neil,

Hai ragione, posso combinare tutto per salvare 5 byte. E ho appena capito di non aver bisogno a+a?neanche di
me

Il downvoter potrebbe spiegare cosa c'è che non va? Produce output errati?
ETHproductions

2

Haskell , 109 108 byte

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])]

Una soluzione a forza bruta: genera tutti gli elenchi di spigoli di lunghezze crescenti fino alla lunghezza dell'input, conserva quelli che sono cicli, restituisce l'ultimo. Prende il grafico nel formato [(1,2),(2,3),(2,4),(4,1)]. Provalo online!

Spiegazione

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.

Ci è voluto un po 'di tempo fino a quando ho finalmente capito cosa sta succedendo, la parte per il controllo di percorsi / cicli è davvero intelligente, sono stupito!
flawr

@flawr Grazie! Bene, sembra che isaacg abbia usato essenzialmente lo stesso algoritmo prima di me.
Zgarb,

0

MATLAB, 291 260 byte

Prende una matrice di adiacenza in Acui un bordo (i,j)è indicato da un 1in A(i,j)ed Aè zero in tutte le altre voci. L'output è un elenco di un ciclo più lungo. L'elenco è vuoto se non è presente alcun ciclo e l'elenco include l'inizio e l'endpoint se esiste un ciclo. Utilizza l' 1indicizzazione basata su.

Questa soluzione non utilizza alcuna funzione integrata relativa ai grafici.

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

Sfortunatamente questo non viene eseguito in TryItOnline in quanto utilizza una funzione all'interno di una funzione, che è ricorsiva. Una piccola modifica ti consente di provarlo su octave-online.net .

Nell'ultimo caso di test ho trovato un ciclo più lungo alternativo [0 2 1 4 3 5 7 8 9 11 10 6 0](questa notazione utilizza l'indicizzazione basata su 0)

Spiegazione

L'approccio di base qui è che eseguiamo un BFS da ogni nodo e facciamo attenzione a non visitare nuovamente nessuno dei nodi intermedi tranne il nodo iniziale. Con questa idea possiamo raccogliere tutti i cicli possibili e scegliere facilmente il più lungo.

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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.