Quelqu'un peut-il m'expliquer en termes simples ce qu'est un graphique acyclique dirigé? J'ai regardé sur Wikipedia mais ça ne me fait pas vraiment voir son utilisation en programmation.
graphe = structure composée de nœuds reliés entre eux par des arêtes
dirigé = les connexions entre les nœuds (arêtes) ont une direction: A -> B n'est pas la même chose que B -> A
acyclic = "non-circular" = passer d'un nœud à l'autre en suivant les bords, vous ne rencontrerez jamais le même nœud pour la deuxième fois.
Un bon exemple de graphe acyclique dirigé est un arbre. Notez, cependant, que tous les graphiques acycliques dirigés ne sont pas des arbres.
Je vois beaucoup de réponses indiquant la signification de DAG (Directed Acyclic Graph) mais aucune réponse sur ses applications. En voici une très simple -
Graphique des pré-requis - Lors d'un cours d'ingénierie, chaque étudiant est confronté à la tâche de choisir des sujets qui répondent à des exigences telles que les pré-requis. Il est désormais clair que vous ne pouvez pas suivre un cours sur l'intelligence artificielle [B] sans un cours préalable sur les algorithmes [A]. Par conséquent, B dépend de A ou mieux, A a un bord dirigé vers B. Donc, pour atteindre Node B, vous devez visiter Node A. Il il sera bientôt clair qu'après avoir ajouté tous les sujets avec ses pré-requis dans un graphique, il se révélera être un graphique acyclique dirigé.
S'il y avait un cycle, vous ne suivriez jamais un cours: p
Un système logiciel de l'université qui permet aux étudiants de s'inscrire à des cours peut modéliser des sujets en tant que nœuds pour être sûr que l'étudiant a suivi un cours préalable avant de s'inscrire au cours en cours.
Mon professeur a donné cette analogie et cela m'a le mieux aidé à comprendre le DAG plutôt qu'à utiliser un concept compliqué!
Un autre exemple en temps réel -> Exemple en temps réel de la façon dont les DAG peuvent être utilisés dans le système de version
Les exemples d'utilisation d'un graphe acyclique dirigé dans la programmation incluent plus ou moins tout ce qui représente la connectivité et la causalité.
Par exemple, supposons que vous disposez d'un pipeline de calcul configurable au moment de l'exécution. À titre d'exemple, supposons que les calculs A, B, C, D, E, F et G dépendent les uns des autres: A dépend de C, C dépend de E et F, B dépend de D et E et D dépend de F. Cela peut être représenté comme un DAG. Une fois le DAG en mémoire, vous pouvez écrire des algorithmes dans:
entre beaucoup d'autres choses.
En dehors du domaine de la programmation d'applications, tout outil de construction automatisé décent (make, ant, scons, etc.) utilisera des DAG pour garantir un ordre de construction correct des composants d'un programme.
Plusieurs réponses ont donné des exemples d'utilisation de graphiques (par exemple la modélisation de réseau) et vous avez demandé "qu'est-ce que cela a à voir avec la programmation?".
La réponse à cette sous-question est qu'elle n'a pas grand-chose à voir avec la programmation. Cela a à voir avec la résolution de problèmes.
Tout comme les listes chaînées sont des structures de données utilisées pour certaines classes de problèmes, les graphiques sont utiles pour représenter certaines relations. Les listes, arborescences, graphiques et autres structures abstraites liées ont uniquement une connexion à la programmation dans la mesure où vous pouvez les implémenter dans du code. Ils existent à un niveau d'abstraction plus élevé. Il ne s'agit pas de programmation, il s'agit d'appliquer des structures de données pour résoudre des problèmes.
Les graphes acycliques dirigés (DAG) ont les propriétés suivantes qui les distinguent des autres graphes:
Eh bien, je peux penser à une utilisation en ce moment - DAG (connu sous le nom Wait-For-Graphs - plus détails techniques ) sont pratiques pour détecter les blocages car ils illustrent les dépendances entre un ensemble de processus et de ressources (les deux sont des nœuds dans le DAG). Un blocage se produirait lorsqu'un cycle est détecté.
J'espère que ça aide.
à votre santé
Je suppose que vous connaissez déjà la terminologie de base des graphes; sinon vous devriez commencer par l'article sur théorie des graphes .
Directed fait référence au fait que les arêtes (connexions) ont des directions. Dans le diagramme, ces directions sont indiquées par les flèches. Le contraire est un graphe non orienté, dont les bords ne spécifient pas de directions.
Acyclique signifie que, si vous partez de n'importe quel nœud arbitraire X et parcourez tous les bords possibles, vous ne pouvez pas revenir à X sans revenir sur un déjà utilisé Bord.
Plusieurs applications:
Les graphiques, de toutes sortes, sont utilisés en programmation pour modéliser différentes relations réelles. Par exemple, un réseau social est souvent représenté par un graphique (cyclique dans ce cas). De même, les topologies de réseau, les arbres généalogiques, les lignes aériennes, ...
Un DAG est un graphique où tout circule dans la même direction et aucun nœud ne peut se référencer à lui-même.
Pensez aux arbres d'ascendance; ce sont en fait des DAG.
Tous les DAG ont
Les DAG sont différents des arbres. Dans une structure arborescente, il doit y avoir un chemin unique entre tous les deux nœuds. Dans les DAG, un nœud peut avoir deux nœuds parents.
Voici un bon article sur les DAG . J'espère que ça aide.
Du point de vue du code source ou même du code à trois adresses (TAC), vous pouvez visualiser le problème très facilement sur cette page ...
http://cgm.cs.mcgill.ca/~hagha/topic30/topic30.html#Exptree
Si vous allez dans la section de l'arborescence des expressions, puis que vous descendez un peu la page, cela montre le "tri topologique" de l'arborescence et l'algorithme pour évaluer l'expression.
Donc, dans ce cas, vous pouvez utiliser le DAG pour évaluer les expressions, ce qui est pratique car l'évaluation est normalement interprétée et l'utilisation d'un tel évaluateur DAG rendra les interprètes simples plus rapides en principe car il ne pousse pas et ne saute pas dans une pile et également parce qu'il élimine sous-expressions courantes.
L'algorithme de base pour calculer le DAG en égyptien non ancien (c'est-à-dire en anglais) est le suivant:
1) Faites votre objet DAG comme ça
Vous avez besoin d'une liste en direct et cette liste contient tous les nœuds DAG et les sous-expressions DAG en cours. Une sous-expression DAG est un nœud DAG, ou vous pouvez également l'appeler un nœud interne. Ce que je veux dire par live DAG Node est que si vous assignez à une variable X alors elle devient live. Une sous-expression commune qui utilise ensuite X utilise cette instance. Si X est assigné à nouveau alors un NOUVEAU DAG NODE est créé et ajouté à la liste active et l'ancien X est supprimé, de sorte que la sous-expression suivante qui utilise X fera référence à la nouvelle instance et n'entrera donc pas en conflit avec les sous-expressions qui utilisent simplement le même nom de variable.
Une fois que vous avez affecté une variable X, tous les nœuds de sous-expression DAG qui sont actifs au point d'affectation deviennent non actifs, car la nouvelle affectation invalide la signification des sous-expressions en utilisant l'ancienne valeur.
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;
}
Donc, ce que vous faites, c'est parcourir votre arbre dans votre propre code, comme un arbre d'expressions dans le code source par exemple. Appelez les nœuds XNodes existants par exemple.
Donc, pour chaque XNode, vous devez décider comment l'ajouter dans le DAG, et il est possible qu'il soit déjà dans le DAG.
Il s'agit d'un pseudo-code très simple. Non destiné à la compilation.
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;
}
}
C'est donc une façon de voir les choses. Une marche de base de l'arbre et juste en ajoutant et en se référant aux nœuds Dag au fur et à mesure. La racine du dag est ce que DagNode la racine de l'arbre renvoie par exemple.
Il est évident que l'exemple de procédure peut être divisé en parties plus petites ou réalisé en sous-classes avec des fonctions virtuelles.
Quant au tri du Dag, vous parcourez chaque DagNode de gauche à droite. En d'autres termes, suivez le bord gauche des DagNodes, puis le bord droit. Les numéros sont attribués à l'envers. En d'autres termes, lorsque vous atteignez un DagNode sans enfants, affectez-le Node le numéro de tri actuel et incrémentez le numéro de tri, afin que la récursivité se déroule, les numéros sont attribués dans l'ordre croissant.
Cet exemple ne gère que les arbres dont les nœuds ont zéro ou deux enfants. Évidemment, certains arbres ont des nœuds avec plus de deux enfants, donc la logique est toujours la même. Au lieu de calculer à gauche et à droite, calculez de gauche à droite, etc.
// 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;
}
Le nom vous dit l'essentiel de ce que vous devez savoir sur sa définition: c'est un graphique où chaque bord ne coule que dans une direction et une fois que vous descendez un bord, votre chemin ne vous ramènera jamais au sommet que vous venez de quitter.
Je ne peux pas parler de toutes les utilisations (Wikipedia aide là-bas), mais pour moi, les DAG sont extrêmement utiles pour déterminer les dépendances entre les ressources. Mon moteur de jeu, par exemple, représente toutes les ressources chargées (matériaux, textures, shaders, texte en clair, json analysé, etc.) comme un seul DAG. Exemple:
Un matériau est N GL programmes, qui ont chacun besoin de deux shaders, et chaque shader a besoin d'une source de shader en texte brut. En représentant ces ressources comme un DAG, je peux facilement interroger le graphique pour les ressources existantes à éviter charges en double. Supposons que vous souhaitiez que plusieurs matériaux utilisent des vertex shaders avec le même code source. Il est inutile de recharger la source et de recompiler les shaders pour chaque utilisation lorsque vous pouvez simplement établir un nouvel Edge à la ressource existante. De cette façon, vous pouvez utilisez également le graphique pour déterminer si quelque chose dépend d'une ressource, et sinon, supprimez-la et libérez sa mémoire, en fait, cela se produit à peu près automatiquement.
Par extension, les DAG sont utiles pour exprimer des pipelines de traitement de données. La nature acyclique signifie que vous pouvez écrire en toute sécurité du code de traitement contextuel qui peut suivre des pointeurs sur les bords d'un sommet sans jamais retrouver le même sommet. Les langages de programmation visuels comme VVVV , Max MSP ou les interfaces basées sur les nœuds d'Autodesk Maya s'appuient tous sur des DAG.
Si vous savez quels arbres sont en programmation, les DAG en programmation sont similaires mais ils permettent à un nœud d'avoir plus d'un parent. Cela peut être pratique lorsque vous souhaitez permettre à un nœud d'être regroupé sous plus d'un seul parent, sans avoir le problème d'un désordre noué d'un graphique général avec des cycles. Vous pouvez toujours naviguer facilement dans un DAG, mais il existe plusieurs façons de revenir à la racine (car il peut y avoir plusieurs parents). Un seul DAG peut en général avoir plusieurs racines, mais en pratique, il peut être préférable de s'en tenir à une seule racine, comme un arbre. Si vous comprenez l'héritage simple ou multiple dans la POO, vous connaissez l'arbre par rapport au DAG. J'ai déjà répondu à cela ici .