Чи може хтось простим словом пояснити мені, що таке спрямований ациклічний графік? Я подивився у Вікіпедію, але насправді це не змушує мене бачити її використання в програмуванні.
Чи може хтось простим словом пояснити мені, що таке спрямований ациклічний графік? Я подивився у Вікіпедію, але насправді це не змушує мене бачити її використання в програмуванні.
Відповіді:
graph = структура, що складається з вузлів, які з'єднані один з одним ребрами
спрямований = з'єднання між вузлами (ребрами) мають напрямок: A -> B - це не те саме, що B -> A
acyclic = "некруглий" = переміщаючись від вузла до вузла, слідуючи за ребрами, ви ніколи не зіткнетесь з тим же вузлом вдруге.
Хороший приклад спрямованого ациклічного графа - дерево. Однак зауважте, що не всі спрямовані ациклічні графіки є деревами.
Я бачу багато відповідей, що вказують на значення DAG (Directed Acyclic Graph), але немає відповідей на його застосування. Ось дуже простий -
Попередній графік - під час інженерного курсу перед кожним студентом стоїть завдання вибору предметів, які відповідають таким вимогам, як попередні реквізити. Тепер зрозуміло, що ви не можете взяти заняття зі штучного інтелекту [B] без попереднього курсу з алгоритмів [A]. Отже, B залежить від A, або, краще кажучи, A має ребро, спрямоване на B. Отже, щоб дістатися до Вузла B, вам потрібно відвідати Вузол А. Незабаром стане зрозуміло, що після додавання всіх предметів із його попередніми реквізитами до графіка , це виявиться прямим ациклічним графіком.
Якби був цикл, ви ніколи не закінчили курс: p
Система програмного забезпечення в університеті, яка дозволяє студентам зареєструватися на курси, може моделювати предмети як вузли, щоб бути впевненими, що студент пройшов попередній необхідний курс перед реєстрацією на поточний курс.
Мій професор дав цю аналогію, і це найкраще допомогло мені зрозуміти DAG, а не використовувати якусь складну концепцію!
Ще один приклад у реальному часі -> Приклад у реальному часі, як DAG можна використовувати у системі версій
Приклади використання спрямованого ациклічного графіка в програмуванні включають більш-менш все, що представляє зв’язок і причинність.
Наприклад, припустимо, у вас є конвеєрний конвеєр, який можна налаштувати під час виконання. Як приклад цього, припустимо, обчислення A, B, C, D, E, F і G залежать один від одного: A залежить від C, C залежить від E і F, B залежить від D і E, а D залежить від F. Це можна представити як DAG. Коли у вас є DAG в пам'яті, ви можете записати алгоритми для:
серед багатьох інших речей.
Поза сферою прикладного програмування будь-який гідний автоматизований інструмент збирання (make, ant, scons тощо) використовує DAG, щоб забезпечити належний порядок побудови компонентів програми.
У кількох відповідях наведено приклади використання графіків (наприклад, мережеве моделювання), і ви запитали "що це стосується програмування?".
Відповідь на це підпитання полягає в тому, що він не має нічого спільного з програмуванням. Це стосується вирішення проблем.
Так само, як пов'язані списки - це структури даних, що використовуються для певних класів проблем, графіки корисні для представлення певних взаємозв'язків. Пов'язані списки, дерева, графіки та інші абстрактні структури мають підключення до програмування лише тим, що ви можете реалізувати їх у коді. Вони існують на більш високому рівні абстракції. Справа не в програмуванні, а в застосуванні структур даних для вирішення проблем.
Направлені ациклічні графіки (DAG) мають такі властивості, що відрізняють їх від інших графіків:
Ну, я зараз думаю про одне використання - DAG (відомий як Wait-For-Graphs - більш технічні деталі ) є зручним у виявленні тупикових ситуацій, оскільки вони ілюструють залежності між набором процесів та ресурсів (обидва - це вузли в DAG) . Тупик трапиться, коли буде виявлено цикл.
Я припускаю, що ви вже знаєте базову термінологію графа; інакше слід почати зі статті про теорію графів .
Спрямований означає, що краї (з'єднання) мають напрямки. На схемі ці напрямки показані стрілками. Протилежне - це непрямий графік, у краях якого не вказано напрямків.
Ациклічний означає, що якщо ви почнете з будь-якого довільного вузла X і пройдете через усі можливі краї, ви не можете повернутися до X, не повернувшись до вже використаного краю.
Кілька застосувань:
DAG - це графік, де все тече в одному напрямку і жоден вузол не може посилатися на себе.
Подумайте про предкові дерева; вони насправді DAG.
Усі DAG є
DAG відрізняються від дерев. У структурі, що нагадує дерево, повинен бути унікальний шлях між кожними двома вузлами. У DAG, вузол може мати два батьківські вузли.
Ось хороша стаття про DAGs . Я сподіваюся, що це допомагає.
Графіки різного роду використовуються в програмуванні для моделювання різних різних реальних відносин. Наприклад, соціальна мережа часто представлена графіком (в даному випадку циклічним). Так само мережеві топології, генеалогічні дерева, маршрути авіакомпаній, ...
З точки зору вихідного коду або навіть трьох адрес (TAC) коду ви можете легко уявити проблему на цій сторінці ...
http://cgm.cs.mcgill.ca/~hagha/topic30/topic30.html#Exptree
Якщо ви перейдете до розділу дерева виразів, а потім трохи опустіться на сторінку, він показує "топологічне сортування" дерева та алгоритм того, як оцінити вираз.
Тож у такому випадку ви можете використовувати DAG для оцінки виразів, що зручно, оскільки оцінювання зазвичай інтерпретується, а використання такого оцінювача DAG зробить прості інкрепретатори швидшими в принципі, оскільки це не натискання та вискакування до стека, а також тому, що це усуває поширені під вирази.
Основний алгоритм для обчислення DAG у не давніх єгипетських (тобто англійській мові) такий:
1) Зробіть свій об’єкт DAG таким чином
Вам потрібен живий список, і цей список містить усі поточні вузли DAG в реальному часі та підвирази DAG. Підвираз DAG - це вузол DAG, або його можна також назвати внутрішнім вузлом. Що я маю на увазі під живим DAG-вузлом, це те, що якщо ви присвоюєте змінну X, вона стає реальною. Звичайний підвираз, який потім використовує X, використовує цей примірник. Якщо X присвоєно знову, то НОВИЙ DAG NODE створюється та додається до списку живих даних, а старий X видаляється, тому наступний підвираз, який використовує X, буде посилатися на новий екземпляр і, таким чином, не буде конфліктувати з суб-виразами, які просто використовувати те саме ім’я змінної.
Після того, як ви призначите змінну X, спільно між собою всі вузли суб-вирази DAG, які перебувають у прямому ефірі в точці присвоєння, стають неприйнятими, оскільки нове призначення приводить в недійсність значення суб-виразів, використовуючи старе значення.
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;
}
Отже, ви робите це прогулянка по дереву у власному коді, наприклад, дерево виразів у вихідному коді, наприклад. Викличте, наприклад, існуючі вузли XNodes.
Отже, для кожного XNode потрібно вирішити, як додати його до DAG, і є можливість, що він вже є в DAG.
Це дуже простий псевдо-код. Не призначений для складання.
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;
}
}
Тож це один із способів дивитися на це. Основна прогулянка по дереву та просто додавання та посилання на вузли Дага, як це відбувається. Корінь дага є будь-яким DagNode, наприклад, корінь дерева повертається.
Очевидно, що приклад процедура може бути розбита на менші частини або складена як підкласи з віртуальними функціями.
Що стосується сортування Dag, то ви проходите через кожну DagNode зліва направо. Іншими словами дотримуйтесь лівого краю DagNodes, а потім краю правого боку. Числа призначаються в зворотному порядку. Іншими словами, коли ви дістаєтесь до DagNode без дітей, призначте цьому Node поточний номер сортування та збільшить сортувальний номер, таким чином, коли рекурсія розкручує номери, присвоюються в порядку збільшення.
Цей приклад обробляє лише дерева з вузлами, які мають нуль або двох дітей. Очевидно, що на деяких деревах є вузли з більш ніж двома дітьми, тому логіка все одно. Замість обчислень ліворуч і праворуч обчислюйте зліва направо тощо.
// 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;
}
Якщо ви знаєте, які дерева є в програмуванні, то DAG в програмуванні схожі, але вони дозволяють вузлу мати більше одного з батьків. Це може бути зручно, коли ви хочете, щоб вузол збився під більш ніж одиноким батьком, але все ж не виникне проблема вузлавого безладу загального графіка з циклами. Ви все одно можете легко переміщуватися по DAG, але є кілька способів повернутися до кореня (тому що може бути більше одного з батьків). Один DAG може взагалі мати декілька коренів, але на практиці може бути краще просто дотримуватися одного кореня, як дерево. Якщо ви розумієте одинакове відносно багаторазового успадкування в OOP, то ви знаєте дерево проти DAG. Я вже тут відповів на це .
Назва говорить вам про те, що вам потрібно знати про її визначення: це графік, де кожен край тече лише в одному напрямку, і як тільки ви повзаєте по краю, ваш шлях ніколи не поверне вас до вершини, яку ви тільки що залишили.
Я не можу говорити про всі види використання (Wikipedia допомагає там), але для мене DAG є надзвичайно корисними при визначенні залежностей між ресурсами. Наприклад, мій ігровий движок представляє всі завантажені ресурси (матеріали, текстури, шейдери, непростий текст, проаналізований json тощо) як єдину DAG. Приклад:
Матеріалом є N GL-програми, для кожного потрібні два шейдери, і кожен шейдер потребує джерела шейдерного простого тексту. Представляючи ці ресурси як DAG, я можу легко запитати графік щодо наявних ресурсів, щоб уникнути повторюваних навантажень. Скажімо, ви хочете, щоб у кількох матеріалах були використані вершинні шейдери з тим самим вихідним кодом. Марно перезавантажувати джерело та перекомпілювати шейдери для кожного використання, коли ви зможете просто встановити новий край існуючого ресурсу. Таким чином, ви також можете використовувати графік, щоб визначити, чи взагалі щось залежить від ресурсу, а якщо ні, видаліть його та звільнить його пам'ять, адже це відбувається майже автоматично.
За розширенням DAG є корисними для вираження конвеєрів обробки даних. Ациклічна природа означає, що ви можете сміливо писати контекстний код обробки, який може слідувати покажчиками вниз по краях від вершини, ніколи не переглядаючи ту саму вершину. Візуальні мови програмування, такі як VVVV , Max MSP або інтерфейси на основі вузла Autodesk Maya, покладаються на DAG.
Спрямований ациклічний графік корисний, коли ви хочете зобразити ... спрямований ациклічний графік! Канонічний приклад - це генеалогічне дерево або генеалогія.