Чи може хтось простим словом пояснити мені, що таке спрямований ациклічний графік?


109

Чи може хтось простим словом пояснити мені, що таке спрямований ациклічний графік? Я подивився у Вікіпедію, але насправді це не змушує мене бачити її використання в програмуванні.


26
У Вікіпедії часто міститься величезний технічний зміст, який потребує розуміння початківців великою кількістю вивчення. Багато сайтів довідки з математики в цьому плані є вищими, але, на жаль, вони, як правило, не потрапляють у теми, що стосуються обчислень.
Джонатан Фауст

1
Хто використовує git, фактично використовує DAG, не знаючи цього, ericsink.com/vcbe/html/directed_acyclic_graphs.html
Qiulang

Відповіді:


86

крапки з лініями, що вказують на інші точки


23
Це одна з найкращих відповідей, тому що це простий спосіб описати, що таке просте поняття, поховане в складній термінології (якщо ми задаємо це питання, ми можемо не знати теорії графів ... або навіть потрібно знати). Мій варіант був би чимось на кшталт "стрибок, де ніколи не можна двічі зайти в один і той же бар". Хоча приклад родинного дерева з іншої відповіді, мабуть, концептуально простіший, особливо для тих із нас, хто не студенти чи алкоголіки.
Том Гаррісон

27
... в одному напрямку
Марк Робсон

3
Це хороший приклад невміння виразити суттєво складне поняття менш, ніж це можливо. Ось чому п’ятий постулат Евкліда все ще існує.
Xaqron

4
Ви повинні включити "там, де рядки не утворюють циклів", інакше ви просто описуєте спрямований графік, а не спрямований ациклічний графік.
Фарап

"крапки з лініями вказують на інші точки, без циклів" - це буде поліпшенням.
Джон Де Реньокур

172

graph = структура, що складається з вузлів, які з'єднані один з одним ребрами

спрямований = з'єднання між вузлами (ребрами) мають напрямок: A -> B - це не те саме, що B -> A

acyclic = "некруглий" = переміщаючись від вузла до вузла, слідуючи за ребрами, ви ніколи не зіткнетесь з тим же вузлом вдруге.

Хороший приклад спрямованого ациклічного графа - дерево. Однак зауважте, що не всі спрямовані ациклічні графіки є деревами.


Я розумію, що таке вузли. Коли ви говорите "край", ви маєте на увазі стрілку, що вказує від Вузла А до Вузла В?
appshare.co

Краще пояснення. Отже, що це стосується програмування? Це пов’язано з функціональним програмуванням?
appshare.co

2
Зазвичай він представлений стрілкою, але насправді просто існує відношення між A і B. У вашій програмі це може бути справжнє значення в матриці суміжності в індексах, що представляють ці два вузли.
tvanfosson

42
Усі спрямовані дерева - це DAG, але не всі DAG - це дерева. DAG A-> B, A-> C, B-> C не може бути представлений у вигляді дерева, оскільки вузол C має більше одного з батьків.
Джейсон S

2
Направленість країв - не єдина особливість, яка відокремлює DAG від дерев. DAG може мати більше | V | -1 країв, на відміну від дерева. Наприклад, A-> B, A-> C, B-> D, C-> D - це DAG, але, очевидно, не дерево, оскільки воно має однакову кількість ребер та вузлів.
Анонім Мус

49

Я бачу багато відповідей, що вказують на значення DAG (Directed Acyclic Graph), але немає відповідей на його застосування. Ось дуже простий -

Попередній графік - під час інженерного курсу перед кожним студентом стоїть завдання вибору предметів, які відповідають таким вимогам, як попередні реквізити. Тепер зрозуміло, що ви не можете взяти заняття зі штучного інтелекту [B] без попереднього курсу з алгоритмів [A]. Отже, B залежить від A, або, краще кажучи, A має ребро, спрямоване на B. Отже, щоб дістатися до Вузла B, вам потрібно відвідати Вузол А. Незабаром стане зрозуміло, що після додавання всіх предметів із його попередніми реквізитами до графіка , це виявиться прямим ациклічним графіком.

Якби був цикл, ви ніколи не закінчили курс: p

Система програмного забезпечення в університеті, яка дозволяє студентам зареєструватися на курси, може моделювати предмети як вузли, щоб бути впевненими, що студент пройшов попередній необхідний курс перед реєстрацією на поточний курс.

Мій професор дав цю аналогію, і це найкраще допомогло мені зрозуміти DAG, а не використовувати якусь складну концепцію!

Ще один приклад у реальному часі -> Приклад у реальному часі, як DAG можна використовувати у системі версій


4
Це має бути найбільш високо оціненою відповіддю. Проста аналогія та не використовує визначення підручника, яке ОП не може легко зрозуміти.
kimathie

25

Приклади використання спрямованого ациклічного графіка в програмуванні включають більш-менш все, що представляє зв’язок і причинність.

Наприклад, припустимо, у вас є конвеєрний конвеєр, який можна налаштувати під час виконання. Як приклад цього, припустимо, обчислення A, B, C, D, E, F і G залежать один від одного: A залежить від C, C залежить від E і F, B залежить від D і E, а D залежить від F. Це можна представити як DAG. Коли у вас є DAG в пам'яті, ви можете записати алгоритми для:

  • переконайтесь, що обчислення оцінені у правильному порядку ( топологічне сортування )
  • якщо обчислення можна робити паралельно, але кожен обчислення має максимальний час виконання, ви можете обчислити максимальний час виконання всього набору

серед багатьох інших речей.

Поза сферою прикладного програмування будь-який гідний автоматизований інструмент збирання (make, ant, scons тощо) використовує DAG, щоб забезпечити належний порядок побудови компонентів програми.


+1 для згадки про причинності. Це виникає багато, коли потрібно представляти складні системи, де вихід одного процесу є входом для одного або декількох інших процесів.
Алекс Фейнман

14

У кількох відповідях наведено приклади використання графіків (наприклад, мережеве моделювання), і ви запитали "що це стосується програмування?".

Відповідь на це підпитання полягає в тому, що він не має нічого спільного з програмуванням. Це стосується вирішення проблем.

Так само, як пов'язані списки - це структури даних, що використовуються для певних класів проблем, графіки корисні для представлення певних взаємозв'язків. Пов'язані списки, дерева, графіки та інші абстрактні структури мають підключення до програмування лише тим, що ви можете реалізувати їх у коді. Вони існують на більш високому рівні абстракції. Справа не в програмуванні, а в застосуванні структур даних для вирішення проблем.


Може реалізуватися в програмуванні. Так, мені це подобається, оскільки графіки існують у реальному світі незалежно від комп'ютерів!
appshare.co

13

Направлені ациклічні графіки (DAG) мають такі властивості, що відрізняють їх від інших графіків:

  1. Їх краї показують напрямок.
  2. У них немає циклів.

Ну, я зараз думаю про одне використання - DAG (відомий як Wait-For-Graphs - більш технічні деталі ) є зручним у виявленні тупикових ситуацій, оскільки вони ілюструють залежності між набором процесів та ресурсів (обидва - це вузли в DAG) . Тупик трапиться, коли буде виявлено цикл.


1
Андрієв, +1 для прикладу тупика. Це насправді використовується двигуном InnoDB MySQL, і вони називають його "чеканням графіка", як в ", цей рядок повинен чекати, коли замок на цьому рядку буде випущений"
Roland Bouman

так, ви мертві прямо з назвою - Зачекайте на графік. Дехто як це пропустив. Оновлено відповідь. :)
Arnkrishn

Як вони знають, що існує залежність? Це перевіряючи, чи є два вузли спільним предком?
appshare.co

Це посилання - cis.temple.edu/~ingargio/cis307/readings/deadlock.html містить більше технічних деталей.
Арнкрішн

11

Я припускаю, що ви вже знаєте базову термінологію графа; інакше слід почати зі статті про теорію графів .

Спрямований означає, що краї (з'єднання) мають напрямки. На схемі ці напрямки показані стрілками. Протилежне - це непрямий графік, у краях якого не вказано напрямків.

Ациклічний означає, що якщо ви почнете з будь-якого довільного вузла X і пройдете через усі можливі краї, ви не можете повернутися до X, не повернувшись до вже використаного краю.

Кілька застосувань:

  • Електронні таблиці; це пояснено у статті DAG .
  • Контроль редагування: якщо ви подивитесь на діаграму на цій сторінці, ви побачите, що еволюція коду, керованого редакцією, спрямована (на цій діаграмі йде «вниз») і ациклічна (вона ніколи не повертається «вгору») .
  • Родинне дерево: воно спрямоване (ви дитина ваших батьків, а не навпаки) та ациклічне (ваші предки ніколи не можуть бути вашим нащадком).

5

DAG - це графік, де все тече в одному напрямку і жоден вузол не може посилатися на себе.

Подумайте про предкові дерева; вони насправді DAG.

Усі DAG є

  • Вузли (місця для зберігання даних)
  • Направлені краї (ця точка в тому ж напрямку)
  • Родовий вузол (вузол без батьків)
  • Листя (вузли, у яких немає дітей)

DAG відрізняються від дерев. У структурі, що нагадує дерево, повинен бути унікальний шлях між кожними двома вузлами. У DAG, вузол може мати два батьківські вузли.

Ось хороша стаття про DAGs . Я сподіваюся, що це допомагає.


4

Графіки різного роду використовуються в програмуванні для моделювання різних різних реальних відносин. Наприклад, соціальна мережа часто представлена ​​графіком (в даному випадку циклічним). Так само мережеві топології, генеалогічні дерева, маршрути авіакомпаній, ...


2

З точки зору вихідного коду або навіть трьох адрес (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;
}

1

Якщо ви знаєте, які дерева є в програмуванні, то DAG в програмуванні схожі, але вони дозволяють вузлу мати більше одного з батьків. Це може бути зручно, коли ви хочете, щоб вузол збився під більш ніж одиноким батьком, але все ж не виникне проблема вузлавого безладу загального графіка з циклами. Ви все одно можете легко переміщуватися по DAG, але є кілька способів повернутися до кореня (тому що може бути більше одного з батьків). Один DAG може взагалі мати декілька коренів, але на практиці може бути краще просто дотримуватися одного кореня, як дерево. Якщо ви розумієте одинакове відносно багаторазового успадкування в OOP, то ви знаєте дерево проти DAG. Я вже тут відповів на це .


1

Назва говорить вам про те, що вам потрібно знати про її визначення: це графік, де кожен край тече лише в одному напрямку, і як тільки ви повзаєте по краю, ваш шлях ніколи не поверне вас до вершини, яку ви тільки що залишили.

Я не можу говорити про всі види використання (Wikipedia допомагає там), але для мене DAG є надзвичайно корисними при визначенні залежностей між ресурсами. Наприклад, мій ігровий движок представляє всі завантажені ресурси (матеріали, текстури, шейдери, непростий текст, проаналізований json тощо) як єдину DAG. Приклад:

Матеріалом є N GL-програми, для кожного потрібні два шейдери, і кожен шейдер потребує джерела шейдерного простого тексту. Представляючи ці ресурси як DAG, я можу легко запитати графік щодо наявних ресурсів, щоб уникнути повторюваних навантажень. Скажімо, ви хочете, щоб у кількох матеріалах були використані вершинні шейдери з тим самим вихідним кодом. Марно перезавантажувати джерело та перекомпілювати шейдери для кожного використання, коли ви зможете просто встановити новий край існуючого ресурсу. Таким чином, ви також можете використовувати графік, щоб визначити, чи взагалі щось залежить від ресурсу, а якщо ні, видаліть його та звільнить його пам'ять, адже це відбувається майже автоматично.

За розширенням DAG є корисними для вираження конвеєрів обробки даних. Ациклічна природа означає, що ви можете сміливо писати контекстний код обробки, який може слідувати покажчиками вниз по краях від вершини, ніколи не переглядаючи ту саму вершину. Візуальні мови програмування, такі як VVVV , Max MSP або інтерфейси на основі вузла Autodesk Maya, покладаються на DAG.


-5

Спрямований ациклічний графік корисний, коли ви хочете зобразити ... спрямований ациклічний графік! Канонічний приклад - це генеалогічне дерево або генеалогія.


Ах, і в цьому є сенс. Але все ж, що це стосується програмування?
appshare.co

1
Що стосується будь-якої структури даних з програмуванням?
Джонатан Фейнберг

Добре, я розумію. Це лише те, що ви не згадали про "структуру даних" у своїй відповіді
appshare.co

5
Тавтологія! = Пояснення
Єва
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.