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.
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.
Risposte:
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.
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
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:
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.
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.
I grafici aciclici diretti (DAG) hanno le seguenti proprietà che li distinguono dagli altri grafici:
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.
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:
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
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.
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, ...
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;
}
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 .
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.
Un grafo diretto aciclico è utile quando si vuole rappresentare ... un grafo diretto aciclico! L'esempio canonico è un albero genealogico o genealogia.