Qualcuno può spiegarmi in termini semplici cos'è un grafo diretto aciclico?


109

Qualcuno può spiegarmi in termini semplici cos'è un grafo diretto aciclico? Ho cercato su Wikipedia ma non mi fa vedere il suo uso nella programmazione.


26
Wikipedia contiene spesso contenuti tecnici travolgenti che richiederebbero ai principianti molto studio per essere compresi. Molti dei siti di aiuto per la matematica sono superiori in questo senso, ma sfortunatamente tendono a non entrare negli argomenti relativi al calcolo.
Jonathon Faust

1
Chiunque utilizzi git utilizza effettivamente DAG senza saperlo, ericsink.com/vcbe/html/directed_acyclic_graphs.html
Qiulang

Risposte:


86

punti con linee che puntano ad altri punti


23
Questa è una delle migliori risposte perché è un modo semplice per descrivere ciò che è un concetto semplice sepolto in una terminologia complessa (se stiamo facendo questa domanda, potremmo non conoscere la teoria dei grafi ... o addirittura aver bisogno di saperlo). La mia variante sarebbe qualcosa come "bar-hopping dove non puoi mai andare due volte allo stesso bar". Sebbene l'esempio dell'albero genealogico di un'altra risposta sia probabilmente concettualmente più semplice, specialmente per quelli di noi che non sono studenti universitari o alcolisti.
Tom Harrison

27
... in una direzione
Mark Robson

3
Questo è un buon esempio di mancata espressione di un concetto intrinsecamente complesso in termini meno che possibili. Ecco perché il quinto postulato di Euclide esiste ancora.
Xaqron

4
Devi includere "dove le linee non formano cicli", altrimenti stai solo descrivendo un grafo diretto, non un grafo aciclico diretto.
Pharap

"i punti con linee puntano ad altri punti, senza loop" sarebbe un miglioramento.
John DeRegnaucourt

172

grafo = struttura costituita da nodi, collegati tra loro con spigoli

diretto = le connessioni tra i nodi (bordi) hanno una direzione: A -> B non è uguale a B -> A

acyclic = "non circolare" = spostandosi da nodo a nodo seguendo i bordi, non incontrerai mai lo stesso nodo per la seconda volta.

Un buon esempio di grafo aciclico diretto è un albero. Si noti, tuttavia, che non tutti i grafi aciclici diretti sono alberi.


Capisco cosa sono i nodi. Quando dici "bordo", intendi una freccia che punta dal nodo A al nodo B?
appshare.co

Migliore spiegazione. Allora cosa c'entra questo con la programmazione? È correlato alla programmazione funzionale?
appshare.co

2
È tipicamente rappresentato da una freccia, ma è solo che c'è una relazione tra A e B. Nel tuo programma questo potrebbe essere un valore reale in una matrice di adiacenza agli indici che rappresentano quei due nodi.
tvanfosson

42
Tutti gli alberi diretti sono DAG, ma non tutti i DAG sono alberi. Il DAG A-> B, A-> C, B-> C non può essere rappresentato come un albero poiché il nodo C ha più di un genitore.
Jason S

2
La direzione dei bordi non è l'unica caratteristica che separa i DAG dagli alberi. Un DAG può avere più di | V | -1 bordi, a differenza di un albero. Ad esempio, A-> B, A-> C, B-> D, C-> D è un DAG ma chiaramente non un albero poiché ha lo stesso numero di bordi e nodi.
Anonym Mus

49

Vedo molte risposte che indicano il significato di DAG (Directed Acyclic Graph) ma nessuna risposta sulle sue applicazioni. Eccone uno molto semplice:

Grafico dei prerequisiti - Durante un corso di ingegneria ogni studente affronta il compito di scegliere materie che seguono requisiti come i prerequisiti. Ora è chiaro che non puoi seguire un corso di Intelligenza Artificiale [B] senza un corso preliminare sugli Algoritmi [A]. Quindi B dipende da A o meglio A ha un margine diretto a B. Quindi per raggiungere il Nodo B devi visitare il Nodo A. Sarà presto chiaro che dopo aver sommato tutti i soggetti con i suoi prerequisiti in un grafico , risulterà essere un grafico aciclico diretto.

Se ci fosse un ciclo, non completeresti mai un corso: p

Un sistema software nell'università che consente agli studenti di registrarsi ai corsi può modellare le materie come nodi per essere sicuri che lo studente abbia seguito un corso pre-requisito prima di registrarsi al corso corrente.

Il mio professore ha dato questa analogia e mi ha aiutato meglio a capire DAG piuttosto che usare un concetto complicato!

Un altro esempio in tempo reale -> Esempio in tempo reale di come i DAG possono essere utilizzati nel sistema di versione


4
Questa dovrebbe essere la risposta con il punteggio più alto. Semplice analogia e non usa la definizione del libro di testo che l'OP non è in grado di comprendere facilmente.
Kimathie

25

Esempi di usi di un grafo aciclico diretto nella programmazione includono più o meno tutto ciò che rappresenta connettività e causalità.

Si supponga, ad esempio, di avere una pipeline di calcolo configurabile in fase di esecuzione. Come esempio di ciò, supponiamo che i calcoli A, B, C, D, E, F e G dipendano l'uno dall'altro: A dipende da C, C dipende da E ed F, B dipende da D ed E e D dipende da F. Questo può essere rappresentato come un DAG. Una volta che hai il DAG in memoria, puoi scrivere algoritmi per:

  • assicurarsi che i calcoli siano valutati nell'ordine corretto ( ordinamento topologico )
  • se i calcoli possono essere eseguiti in parallelo ma ogni calcolo ha un tempo di esecuzione massimo, puoi calcolare il tempo di esecuzione massimo dell'intero set

tra molte altre cose.

Al di fuori del regno della programmazione delle applicazioni, qualsiasi strumento di compilazione automatizzato decente (make, ant, scons, ecc.) Utilizzerà i DAG per garantire il corretto ordine di compilazione dei componenti di un programma.


+1 per la menzione della causalità. Ciò emerge molto quando è necessario rappresentare un sistema complesso in cui l'output di un processo è l'input per uno o più altri processi.
Alex Feinman

14

Diverse risposte hanno fornito esempi di utilizzo dei grafici (es. Modellazione di rete) e tu hai chiesto "cosa c'entra questo con la programmazione?".

La risposta a questa domanda secondaria è che non ha molto a che fare con la programmazione. Ha a che fare con la risoluzione dei problemi.

Proprio come le liste concatenate sono strutture di dati utilizzate per determinate classi di problemi, i grafici sono utili per rappresentare determinate relazioni. Elenchi, alberi, grafici e altre strutture astratte collegati hanno solo una connessione alla programmazione in quanto è possibile implementarli nel codice. Esistono a un livello più alto di astrazione. Non si tratta di programmazione, si tratta di applicare strutture dati nella soluzione dei problemi.


Può essere implementato in programmazione. Sì, mi piace, poiché i grafici esistono nel mondo reale indipendentemente dai computer!
appshare.co

13

I grafici aciclici diretti (DAG) hanno le seguenti proprietà che li distinguono dagli altri grafici:

  1. I loro bordi mostrano la direzione.
  2. Non hanno cicli.

Bene, posso pensare a un utilizzo in questo momento: DAG (noto come Wait-For-Graphs - più dettagli tecnici ) è utile per rilevare i deadlock poiché illustrano le dipendenze tra un insieme di processi e risorse (entrambi sono nodi nel DAG) . Il deadlock si verifica quando viene rilevato un ciclo.


1
Andriyev, +1 per l'esempio di deadlock. Questo è infatti utilizzato dal motore InnoDB di MySQL, e lo chiamano "wait-for-graph", come in "quella riga deve attendere che il blocco su quella riga venga rilasciato"
Roland Bouman

sì, hai ragione con il nome - Wait For Graph. Alcuni lo hanno mancato. Aggiornata la risposta. :)
Arnkrishn

Come fanno a sapere che c'è una dipendenza? È controllando per vedere se due nodi hanno un antenato comune?
appshare.co

Questo link - cis.temple.edu/~ingargio/cis307/readings/deadlock.html contiene ulteriori dettagli tecnici.
Arnkrishn

11

Presumo che tu conosca già la terminologia di base dei grafi; altrimenti dovresti iniziare dall'articolo sulla teoria dei grafi .

Diretto si riferisce al fatto che i bordi (collegamenti) hanno direzioni. Nel diagramma, queste direzioni sono indicate dalle frecce. L'opposto è un grafico non orientato, i cui bordi non specificano direzioni.

Aciclico significa che, se parti da un nodo X arbitrario e percorri tutti gli archi possibili, non puoi tornare a X senza tornare su un arco già utilizzato.

Diverse applicazioni:

  • Fogli di calcolo; questo è spiegato nell'articolo DAG .
  • Controllo di revisione : se dai uno sguardo al diagramma in quella pagina, vedrai che l'evoluzione del codice controllato da revisione è diretta (va "giù", in questo diagramma) e aciclica (non torna mai "su") .
  • Albero genealogico: è diretto (sei figlio dei tuoi genitori, non il contrario) e aciclico (i tuoi antenati non possono mai essere tuoi discendenti).

5

Un DAG è un grafico in cui tutto scorre nella stessa direzione e nessun nodo può fare riferimento a se stesso.

Pensa agli alberi di discendenza; sono in realtà DAG.

Tutti i DAG hanno

  • Nodi (luoghi in cui memorizzare i dati)
  • Bordi orientati (quel punto nella stessa direzione)
  • Un nodo ancestrale (un nodo senza genitori)
  • Foglie (nodi che non hanno figli)

I DAG sono diversi dagli alberi. In una struttura ad albero, deve esserci un percorso univoco tra ogni due nodi. Nei DAG, un nodo può avere due nodi padre.

Ecco un buon articolo sui DAG . Spero che aiuti.


4

I grafici, di tutti i tipi, vengono utilizzati nella programmazione per modellare varie relazioni del mondo reale. Ad esempio, un social network è spesso rappresentato da un grafico (ciclico in questo caso). Allo stesso modo, topologie di rete, alberi genealogici, rotte aeree, ...


2

Dal punto di vista del codice sorgente o anche del codice a tre indirizzi (TAC) puoi visualizzare il problema molto facilmente in questa pagina ...

http://cgm.cs.mcgill.ca/~hagha/topic30/topic30.html#Exptree

Se vai alla sezione dell'albero delle espressioni, e poi scorri un po 'verso il basso, mostra l' "ordinamento topologico" dell'albero e l'algoritmo su come valutare l'espressione.

Quindi in quel caso puoi usare il DAG per valutare le espressioni, il che è utile poiché la valutazione è normalmente interpretata e l'utilizzo di un analizzatore di DAG di questo tipo renderà semplici interpreti più veloci in linea di principio perché non sta spingendo e saltando in una pila e anche perché sta eliminando sottoespressioni comuni.

L'algoritmo di base per calcolare il DAG in egiziano non antico (cioè inglese) è questo:

1) Crea il tuo oggetto DAG in questo modo

È necessario un elenco live e questo elenco contiene tutti i nodi DAG attivi e le sottoespressioni DAG correnti. Una sottoespressione DAG è un nodo DAG oppure puoi anche chiamarla nodo interno. Quello che intendo per nodo DAG live è che se si assegna a una variabile X, diventa live. Una sottoespressione comune che quindi utilizza X utilizza quell'istanza. Se X viene assegnato di nuovo, viene creato un NUOVO NODO DAG che viene aggiunto all'elenco attivo e la vecchia X viene rimossa in modo che la successiva sottoespressione che utilizza X si riferisca alla nuova istanza e quindi non entrerà in conflitto con le sottoespressioni che usa semplicemente lo stesso nome di variabile.

Una volta assegnato a una variabile X, casualmente tutti i nodi delle sottoespressioni DAG che sono attivi nel punto di assegnazione diventano non attivi, poiché la nuova assegnazione invalida il significato delle sottoespressioni utilizzando il vecchio valore.

class Dag {
  TList LiveList;
  DagNode Root;
}

// In your DagNode you need a way to refer to the original things that
// the DAG is computed from. In this case I just assume an integer index
// into the list of variables and also an integer index for the opertor for
// Nodes that refer to operators. Obviously you can create sub-classes for
// different kinds of Dag Nodes.
class DagNode {
  int Variable;
  int Operator;// You can also use a class
  DagNode Left;
  DagNode Right;
  DagNodeList Parents;
}

Quindi quello che fai è attraversare il tuo albero nel tuo codice, come ad esempio un albero di espressioni nel codice sorgente. Chiama i nodi esistenti XNodes per esempio.

Quindi per ogni XNode è necessario decidere come aggiungerlo al DAG e c'è la possibilità che sia già nel DAG.

Questo è uno pseudo codice molto semplice. Non destinato alla compilazione.

DagNode XNode::GetDagNode(Dag dag) {
  if (XNode.IsAssignment) {
    // The assignment is a special case. A common sub expression is not
    // formed by the assignment since it creates a new value.

    // Evaluate the right hand side like normal
    XNode.RightXNode.GetDagNode();  


    // And now take the variable being assigned to out of the current live list
    dag.RemoveDagNodeForVariable(XNode.VariableBeingAssigned);

    // Also remove all DAG sub expressions using the variable - since the new value
    // makes them redundant
    dag.RemoveDagExpressionsUsingVariable(XNode.VariableBeingAssigned);

    // Then make a new variable in the live list in the dag, so that references to
    // the variable later on will see the new dag node instead.
    dag.AddDagNodeForVariable(XNode.VariableBeingAssigned);

  }
  else if (XNode.IsVariable) {
    // A variable node has no child nodes, so you can just proces it directly
    DagNode n = dag.GetDagNodeForVariable(XNode.Variable));
    if (n) XNode.DagNode = n;
    else {
      XNode.DagNode = dag.CreateDagNodeForVariable(XNode.Variable);
    }
    return XNode.DagNode;
  }
  else if (XNode.IsOperator) {
    DagNode leftDagNode = XNode.LeftXNode.GetDagNode(dag);
    DagNode rightDagNode = XNode.RightXNode.GetDagNode(dag);


    // Here you can observe how supplying the operator id and both operands that it
    // looks in the Dags live list to check if this expression is already there. If
    // it is then it returns it and that is how a common sub-expression is formed.
    // This is called an internal node.
    XNode.DagNode = 
      dag.GetOrCreateDagNodeForOperator(XNode.Operator,leftDagNode,RightDagNode) );

    return XNode.DagNode;
  }
}

Quindi questo è un modo di vederlo. Una passeggiata di base dell'albero e solo l'aggiunta e il riferimento ai nodi Dag mentre procede. La radice del dag è qualunque cosa DagNode restituita dalla radice dell'albero, ad esempio.

Ovviamente la procedura di esempio può essere suddivisa in parti più piccole o realizzata come sottoclassi con funzioni virtuali.

Per quanto riguarda l'ordinamento del Dag, si passa attraverso ciascun DagNode da sinistra a destra. In altre parole, seguire il bordo sinistro dei DagNodes e quindi il bordo del lato destro. I numeri vengono assegnati in ordine inverso. In altre parole, quando raggiungi un DagNode senza figli, assegna a quel Nodo il numero di ordinamento corrente e incrementa il numero di ordinamento, in modo che la ricorsione si svolga i numeri vengono assegnati in ordine crescente.

Questo esempio gestisce solo alberi con nodi che hanno zero o due figli. Ovviamente alcuni alberi hanno nodi con più di due figli, quindi la logica è sempre la stessa. Invece di calcolare sinistra e destra, calcola da sinistra a destra ecc ...

// Most basic DAG topological ordering example.
void DagNode::OrderDAG(int* counter) {
  if (this->AlreadyCounted) return;

  // Count from left to right
  for x = 0 to this->Children.Count-1
    this->Children[x].OrderDag(counter)

  // And finally number the DAG Node here after all
  // the children have been numbered
  this->DAGOrder = *counter;

  // Increment the counter so the caller gets a higher number
  *counter = *counter + 1;

  // Mark as processed so will count again
  this->AlreadyCounted = TRUE;
}

1

Se sai quali alberi sono nella programmazione, i DAG nella programmazione sono simili ma consentono a un nodo di avere più di un genitore. Questo può essere utile quando vuoi lasciare che un nodo sia raggruppato sotto più di un solo genitore, ma non hai il problema di un pasticcio annodato di un grafico generale con cicli. È comunque possibile navigare facilmente in un DAG, ma esistono diversi modi per tornare alla radice (perché può esserci più di un genitore). Un singolo gruppo di disponibilità del database potrebbe in generale avere più radici, ma in pratica potrebbe essere meglio restare con una sola radice, come un albero. Se comprendi l'ereditarietà singola e multipla in OOP, allora conosci tree vs. DAG. Ho già risposto qui .


1

Il nome ti dice la maggior parte di ciò che devi sapere sulla sua definizione: è un grafico in cui ogni bordo scorre solo in una direzione e una volta che scivoli lungo un bordo il tuo percorso non ti riporterà mai al vertice che hai appena lasciato.

Non posso parlare di tutti gli usi (Wikipedia aiuta lì), ma per me i DAG sono estremamente utili quando si determinano le dipendenze tra le risorse. Il mio motore di gioco, ad esempio, rappresenta tutte le risorse caricate (materiali, trame, shader, testo in chiaro, json analizzato, ecc.) Come un singolo DAG. Esempio:

Un materiale è costituito da programmi N GL, ciascuno dei quali necessita di due shader e ogni shader necessita di una sorgente shader in testo normale. Rappresentando queste risorse come DAG, posso facilmente interrogare il grafico per le risorse esistenti per evitare caricamenti duplicati. Supponi di voler utilizzare più materiali per utilizzare gli shader di vertice con lo stesso codice sorgente. È uno spreco ricaricare la sorgente e ricompilare gli shader per ogni utilizzo quando puoi semplicemente stabilire un nuovo vantaggio per la risorsa esistente. In questo modo puoi anche usare il grafico per determinare se qualcosa dipende da una risorsa e, in caso contrario, eliminarlo e liberarne la memoria, infatti questo avviene praticamente automaticamente.

Per estensione, i DAG sono utili per esprimere pipeline di elaborazione dati. La natura aciclica significa che puoi scrivere in sicurezza codice di elaborazione contestuale che può seguire i puntatori lungo i bordi da un vertice senza mai incontrare nuovamente lo stesso vertice. I linguaggi di programmazione visiva come VVVV , Max MSP o le interfacce basate sui nodi di Autodesk Maya si basano tutti sui DAG.


-5

Un grafo diretto aciclico è utile quando si vuole rappresentare ... un grafo diretto aciclico! L'esempio canonico è un albero genealogico o genealogia.


Ah, anche questo ha senso. Ma ancora, cosa c'entra questo con la programmazione?
appshare.co

1
Cosa ha a che fare una struttura dati con la programmazione?
Jonathan Feinberg

Ok ho capito. È solo che non hai menzionato la "struttura dei dati" nella tua risposta
appshare.co

5
Tautology! = Spiegazione
Eva
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.