Ai đó có thể giải thích một cách đơn giản cho tôi một biểu đồ xoay chiều có hướng là gì không?


109

Ai đó có thể giải thích một cách đơn giản cho tôi một biểu đồ xoay chiều có hướng là gì không? Tôi đã xem trên Wikipedia nhưng nó không thực sự khiến tôi thấy công dụng của nó trong lập trình.


26
Wikipedia thường chứa nhiều nội dung kỹ thuật mà người mới bắt đầu sẽ phải nghiên cứu rất nhiều mới có thể hiểu được. Nhiều trang web trợ giúp toán học vượt trội hơn về mặt này, nhưng rất tiếc, chúng có xu hướng không đi sâu vào các chủ đề liên quan đến máy tính.
Jonathon Faust

1
Bất cứ ai sử dụng git thực sự sử dụng DAG mà không biết nó, ericsink.com/vcbe/html/diirect_acyclic_graphs.html
Qiulang

Câu trả lời:


86

dấu chấm với đường chỉ đến các dấu chấm khác


23
Đây là một trong những câu trả lời tốt nhất vì nó là một cách đơn giản để mô tả khái niệm đơn giản bị chôn vùi trong thuật ngữ phức tạp (nếu chúng ta hỏi câu hỏi này, chúng ta có thể không biết lý thuyết đồ thị ... hoặc thậm chí cần biết). Biến thể của tôi sẽ là một cái gì đó giống như "nhảy thanh nơi bạn không bao giờ có thể đi đến cùng một thanh hai lần". Mặc dù ví dụ về cây gia đình từ một câu trả lời khác có thể đơn giản hơn về mặt khái niệm, đặc biệt là đối với những người trong chúng ta không phải là sinh viên đại học hoặc người nghiện rượu.
Tom Harrison

27
... theo một hướng
Đánh dấu Robson

3
Đây là một ví dụ điển hình về việc không thể diễn đạt một khái niệm vốn dĩ phức tạp bằng những thuật ngữ ít hơn có thể. Đó là lý do tại sao định đề thứ năm của Euclid vẫn tồn tại.
Xaqron 19/07/18

4
Bạn phải bao gồm "nơi các đường không tạo thành chu trình", nếu không bạn chỉ đang mô tả một đồ thị có hướng, không phải đồ thị xoay có hướng.
Pharap

"dấu chấm có đường trỏ đến các dấu chấm khác, không có vòng lặp" sẽ là một cải tiến.
John DeRegnaucourt

172

graph = cấu trúc bao gồm các nút, được kết nối với nhau bằng các cạnh

có hướng = các liên kết giữa các nút (cạnh) có hướng: A -> B không giống với B -> A

acyclic = "non-circle" = di chuyển từ nút này sang nút khác bằng cách đi theo các cạnh, bạn sẽ không bao giờ gặp phải cùng một nút lần thứ hai.

Một ví dụ điển hình về đồ thị xoay chiều có hướng là một cái cây. Tuy nhiên, lưu ý rằng không phải tất cả các đồ thị xoay chiều có hướng đều là cây.


Tôi hiểu các nút là gì. Khi bạn nói "edge", có phải ý bạn là một mũi tên chỉ từ Nút A đến Nút B?
appshare.co

Giải thích tốt hơn. Vậy điều này có liên quan gì đến lập trình? Nó có liên quan đến lập trình chức năng không?
appshare.co

2
Nó thường được biểu diễn bằng một mũi tên, nhưng thực sự chỉ là có mối quan hệ giữa A và B. Trong chương trình của bạn, giá trị này có thể là giá trị thực trong ma trận kề tại các chỉ số đại diện cho hai nút đó.
tvanfosson

42
Tất cả các cây được định hướng đều là DAG, nhưng không phải tất cả các DAG đều là cây. DAG A-> B, A-> C, B-> C không thể được biểu diễn dưới dạng cây vì nút C có nhiều hơn một nút cha.
Jason S

2
Chỉ thị của các cạnh không phải là tính năng duy nhất tách DAGs khỏi cây. Một DAG có thể có nhiều hơn | V | -1 cạnh, không giống như một cây. Ví dụ, A-> B, A-> C, B-> D, C-> D là một DAG nhưng rõ ràng không phải là một cây vì nó có cùng số cạnh và nút.
Anonymous Mus,

49

Tôi thấy rất nhiều câu trả lời chỉ ra ý nghĩa của DAG (Đồ thị vòng quay có hướng) nhưng không có câu trả lời nào về các ứng dụng của nó. Đây là một cái rất đơn giản -

Biểu đồ điều kiện tiên quyết - Trong một khóa học kỹ thuật, mỗi sinh viên phải đối mặt với nhiệm vụ chọn các môn học tuân theo các yêu cầu như điều kiện tiên quyết. Bây giờ rõ ràng là bạn không thể tham gia một lớp học về Trí tuệ nhân tạo [B] mà không có một khóa học cần thiết trước về Thuật toán [A]. Do đó B phụ thuộc vào A hay nói cách tốt hơn là A có một cạnh hướng tới B. Vì vậy, để đến được Nút B, bạn phải truy cập vào Nút A. Sẽ sớm rõ ràng rằng sau khi thêm tất cả các đối tượng với điều kiện tiên quyết của nó vào một biểu đồ , nó sẽ trở thành một Đồ thị Acyclic được Hướng dẫn.

Nếu có chu kỳ thì bạn sẽ không bao giờ hoàn thành một khóa học: p

Một hệ thống phần mềm trong trường đại học cho phép sinh viên đăng ký các khóa học có thể mô hình hóa các môn học dưới dạng các nút để chắc chắn rằng sinh viên đã tham gia khóa học tiên quyết trước khi đăng ký khóa học hiện tại.

Giáo sư của tôi đã đưa ra phép loại suy này và nó đã giúp tôi hiểu rõ nhất về DAG hơn là sử dụng một số khái niệm phức tạp!

Một ví dụ thời gian thực khác -> Ví dụ thời gian thực về cách DAG có thể được sử dụng trong hệ thống phiên bản


4
Đây phải là câu trả lời được xếp hạng cao nhất. Phép loại suy đơn giản và không sử dụng định nghĩa sách văn bản mà OP không thể dễ dàng hiểu được.
kimathie

25

Ví dụ sử dụng biểu đồ xoay chiều có hướng trong lập trình bao gồm nhiều hơn hoặc ít hơn bất cứ điều gì đại diện cho kết nối và quan hệ nhân quả.

Ví dụ: giả sử bạn có một đường dẫn tính toán có thể định cấu hình trong thời gian chạy. Ví dụ về điều này, giả sử các phép tính A, B, C, D, E, F và G phụ thuộc vào nhau: A phụ thuộc vào C, C phụ thuộc vào E và F, B phụ thuộc vào D và E, và D phụ thuộc vào F. Điều này có thể được biểu diễn dưới dạng DAG. Khi bạn có DAG trong bộ nhớ, bạn có thể viết các thuật toán để:

  • đảm bảo rằng các phép tính được đánh giá theo đúng thứ tự ( sắp xếp topo )
  • nếu các phép tính có thể được thực hiện song song nhưng mỗi phép tính có thời gian thực hiện tối đa, bạn có thể tính thời gian thực hiện tối đa của toàn bộ tập hợp

trong số nhiều thứ khác.

Bên ngoài lĩnh vực lập trình ứng dụng, bất kỳ công cụ xây dựng tự động phù hợp nào (make, ant, scons, v.v.) sẽ sử dụng DAG để đảm bảo thứ tự xây dựng phù hợp của các thành phần của chương trình.


+1 cho đề cập đến quan hệ nhân quả. Điều này xuất hiện rất nhiều khi bạn cần biểu diễn một hệ thống phức tạp trong đó đầu ra của một quy trình là đầu vào cho một hoặc nhiều quy trình khác.
Alex Feinman

14

Một số câu trả lời đã đưa ra các ví dụ về việc sử dụng đồ thị (ví dụ như mô hình mạng) và bạn đã hỏi "điều này có liên quan gì đến lập trình?".

Câu trả lời cho câu hỏi phụ đó là nó không liên quan gì nhiều đến lập trình. Nó liên quan đến giải quyết vấn đề.

Giống như danh sách liên kết là cấu trúc dữ liệu được sử dụng cho một số lớp vấn đề nhất định, đồ thị rất hữu ích để biểu diễn các mối quan hệ nhất định. Danh sách được liên kết, cây, đồ thị và các cấu trúc trừu tượng khác chỉ có mối liên hệ với lập trình mà bạn có thể triển khai chúng trong mã. Chúng tồn tại ở mức trừu tượng cao hơn. Nó không phải là về lập trình, nó là về việc áp dụng cấu trúc dữ liệu trong giải pháp của các vấn đề.


Có thể được thực hiện trong lập trình. Vâng, tôi thích điều đó, vì đồ thị tồn tại trong thế giới thực độc lập với máy tính!
appshare.co

13

Đồ thị vòng có hướng (DAG) có các thuộc tính sau để phân biệt chúng với các đồ thị khác:

  1. Các cạnh của chúng hiển thị hướng.
  2. Chúng không có chu kỳ.

Chà, tôi có thể nghĩ đến một cách sử dụng ngay bây giờ - DAG (được gọi là Đồ thị Chờ đợi - chi tiết kỹ thuật hơn ) rất tiện lợi trong việc phát hiện các bế tắc vì chúng minh họa sự phụ thuộc giữa một tập hợp các quy trình và tài nguyên (cả hai đều là các nút trong DAG) . Bế tắc sẽ xảy ra khi một chu kỳ được phát hiện.


1
Andriyev, +1 cho ví dụ về bế tắc. Điều này trên thực tế được sử dụng bởi công cụ InnoDB của MySQL và họ gọi nó là "đồ thị chờ đợi", như trong "hàng đó phải đợi khóa trên hàng đó được giải phóng"
Roland Bouman 17/02/10

vâng, bạn đã chết đúng với cái tên - Wait For Graph. Một số làm thế nào đã bỏ lỡ điều đó. Đã cập nhật câu trả lời. :)
Arnkrishn

Làm thế nào để họ biết có một sự phụ thuộc? Đó là bằng cách kiểm tra xem liệu hai nút có một tổ tiên chung không?
appshare.co

Liên kết này - cis.temple.edu/~ingargio/cis307/readings/deadlock.html có thêm chi tiết kỹ thuật.
Arnkrishn

11

Tôi giả sử bạn đã biết thuật ngữ đồ thị cơ bản; nếu không bạn nên bắt đầu từ bài viết về lý thuyết đồ thị .

Directed đề cập đến thực tế là các cạnh (kết nối) có hướng. Trong sơ đồ, các hướng này được hiển thị bằng các mũi tên. Ngược lại là một biểu đồ vô hướng, có các cạnh không chỉ định hướng.

Acyclic có nghĩa là, nếu bạn bắt đầu từ bất kỳ nút X tùy ý nào và đi qua tất cả các cạnh có thể có, bạn không thể quay lại X mà không quay lại cạnh đã được sử dụng.

Một số ứng dụng:

  • Bảng tính; điều này được giải thích trong bài báo DAG .
  • Kiểm soát sửa đổi : nếu bạn xem sơ đồ trong trang đó, bạn sẽ thấy rằng sự phát triển của mã được kiểm soát sửa đổi là hướng (nó đi xuống "xuống", trong sơ đồ này) và theo chu kỳ (nó không bao giờ quay trở lại "lên") .
  • Cây gia đình: nó trực tiếp (bạn là con của bố mẹ bạn, không phải ngược lại) và dòng chảy (tổ tiên của bạn không bao giờ có thể là con cháu của bạn).

5

DAG là một biểu đồ trong đó mọi thứ chảy theo cùng một hướng và không có nút nào có thể tham chiếu trở lại chính nó.

Hãy nghĩ về cây tổ tiên; chúng thực sự là DAG.

Tất cả các DAG đều có

  • Nút (nơi lưu trữ dữ liệu)
  • Các cạnh có hướng (điểm đó theo cùng một hướng)
  • Một nút tổ tiên (một nút không có cha mẹ)
  • Lá (nút không có con)

DAGs khác với cây cối. Trong cấu trúc dạng cây, phải có một đường dẫn duy nhất giữa hai nút. Trong DAG, một nút có thể có hai nút cha.

Đây là một bài báo hay về DAGs . Tôi hy vọng rằng sẽ giúp.


4

Đồ thị, thuộc tất cả các loại, được sử dụng trong lập trình để mô hình hóa các mối quan hệ khác nhau trong thế giới thực. Ví dụ, một mạng xã hội thường được biểu diễn bằng một đồ thị (trong trường hợp này là theo chu kỳ). Tương tự như vậy, cấu trúc liên kết mạng, cây họ, tuyến hàng không, ...


2

Từ góc độ mã nguồn hoặc thậm chí ba địa chỉ (TAC), bạn có thể hình dung vấn đề thực sự dễ dàng tại trang này ...

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

Nếu bạn đi đến phần cây biểu thức và sau đó trang xuống một chút, nó sẽ hiển thị "sắp xếp cấu trúc liên kết" của cây và thuật toán về cách đánh giá biểu thức.

Vì vậy, trong trường hợp đó, bạn có thể sử dụng DAG để đánh giá các biểu thức, điều này rất tiện lợi vì đánh giá thường được diễn giải và việc sử dụng bộ đánh giá DAG như vậy sẽ làm cho các trình thông dịch đơn giản nhanh hơn trong chính vì nó không đẩy và xuất hiện vào một ngăn xếp và cũng vì nó đang loại bỏ biểu thức phụ thông thường.

Thuật toán cơ bản để tính toán DAG trong tiếng Ai Cập cổ đại (tức là tiếng Anh) là:

1) Làm cho đối tượng DAG của bạn như vậy

Bạn cần một danh sách trực tiếp và danh sách này chứa tất cả các nút DAG trực tiếp hiện tại và các biểu thức con DAG. Biểu thức con DAG là một Nút DAG, hoặc bạn cũng có thể gọi nó là một nút bên trong. Ý tôi muốn nói về Node DAG trực tiếp là nếu bạn gán cho một biến X thì nó sẽ hoạt động. Một biểu thức con phổ biến sau đó sử dụng X sử dụng trường hợp đó. Nếu X được gán lại thì một DAG NODE MỚI được tạo và thêm vào danh sách trực tiếp và X cũ bị xóa vì vậy biểu thức con tiếp theo sử dụng X sẽ tham chiếu đến phiên bản mới và do đó sẽ không xung đột với các biểu thức phụ chỉ sử dụng cùng một tên biến.

Khi bạn gán cho một biến X, thì ngẫu nhiên tất cả các nút biểu thức con DAG đang tồn tại tại điểm gán trở thành không tồn tại, vì phép gán mới làm mất hiệu lực ý nghĩa của các biểu thức phụ sử dụng giá trị cũ.

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;
}

Vì vậy, những gì bạn làm là đi qua cây của bạn trong mã của riêng bạn, chẳng hạn như một cây biểu thức trong mã nguồn chẳng hạn. Gọi các nút hiện có là XNodes chẳng hạn.

Vì vậy, đối với mỗi XNode, bạn cần quyết định cách thêm nó vào DAG và có khả năng nó đã có trong DAG.

Đây là mã giả rất đơn giản. Không dùng để biên dịch.

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;
  }
}

Vì vậy, đó là một cách để xem xét nó. Một bước đi cơ bản của cây và chỉ cần thêm vào và tham chiếu đến các nút Dag khi nó hoạt động. Gốc của dag là bất kỳ DagNode nào mà gốc của cây trả về chẳng hạn.

Rõ ràng là thủ tục ví dụ có thể được chia thành các phần nhỏ hơn hoặc được tạo thành các lớp con với các hàm ảo.

Đối với việc phân loại Dag, bạn đi qua từng DagNode từ trái sang phải. Nói cách khác, theo cạnh bên trái của DagNodes, và sau đó là cạnh bên phải. Các con số được gán ngược lại. Nói cách khác, khi bạn đến một DagNode không có con nào, hãy gán cho Node đó số sắp xếp hiện tại và tăng số sắp xếp, để đệ quy giải nén các số được gán theo thứ tự tăng dần.

Ví dụ này chỉ xử lý các cây có nút không có hoặc hai nút con. Rõ ràng là một số cây có các nút với nhiều hơn hai con nên logic vẫn giống nhau. Thay vì tính toán trái và phải, hãy tính toán từ trái sang phải, v.v.

// 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

Nếu bạn biết cây gì trong lập trình, thì DAG trong lập trình cũng tương tự nhưng chúng cho phép một nút có nhiều hơn một nút cha. Điều này có thể hữu ích khi bạn muốn để một nút được nhóm lại dưới nhiều hơn một nút cha duy nhất, nhưng không gặp vấn đề về sự lộn xộn thắt nút của một đồ thị tổng quát với các chu trình. Bạn vẫn có thể điều hướng một DAG dễ dàng, nhưng có nhiều cách để quay lại thư mục gốc (vì có thể có nhiều hơn một trang gốc). Nhìn chung, một DAG đơn lẻ có thể có nhiều rễ nhưng trong thực tế, có thể tốt hơn nếu chỉ gắn bó với một gốc, giống như một cái cây. Nếu bạn hiểu đơn thừa kế so với đa kế thừa trong OOP, thì bạn biết cây so với DAG. Tôi đã trả lời điều này ở đây .


1

Cái tên cho bạn biết hầu hết những gì bạn cần biết về định nghĩa của nó: Đó là một biểu đồ mà mọi cạnh chỉ chảy theo một hướng và một khi bạn bò xuống một cạnh, con đường của bạn sẽ không bao giờ đưa bạn trở lại đỉnh mà bạn vừa rời đi.

Tôi không thể nói tất cả các mục đích sử dụng (Wikipedia giúp ở đó), nhưng đối với tôi DAG cực kỳ hữu ích khi xác định sự phụ thuộc giữa các tài nguyên. Ví dụ: công cụ trò chơi của tôi đại diện cho tất cả các tài nguyên được tải (vật liệu, kết cấu, bộ đổ bóng, bản rõ, json được phân tích cú pháp, v.v.) dưới dạng một DAG duy nhất. Thí dụ:

Vật liệu là các chương trình N GL, mỗi chương trình cần hai trình đổ bóng và mỗi trình đổ bóng cần một nguồn tô bóng bản rõ. Bằng cách biểu diễn các tài nguyên này dưới dạng DAG, tôi có thể dễ dàng truy vấn biểu đồ cho các tài nguyên hiện có để tránh tải trùng lặp. Giả sử bạn muốn một số tài liệu sử dụng bộ đổ bóng đỉnh với cùng một mã nguồn. Thật lãng phí khi tải lại nguồn và biên dịch lại các trình tạo bóng cho mỗi lần sử dụng khi bạn chỉ có thể thiết lập một cạnh mới cho tài nguyên hiện có. Bằng cách này, bạn cũng có thể sử dụng biểu đồ để xác định xem có điều gì phụ thuộc vào tài nguyên hay không, và nếu không, hãy xóa tài nguyên đó đi và giải phóng bộ nhớ của nó, trên thực tế điều này diễn ra khá tự động.

Nói cách khác, DAG rất hữu ích để thể hiện các đường ống xử lý dữ liệu. Bản chất xoay vòng có nghĩa là bạn có thể viết mã xử lý theo ngữ cảnh một cách an toàn có thể đi theo con trỏ xuống các cạnh từ một đỉnh mà không bao giờ ghi lại cùng một đỉnh. Các ngôn ngữ lập trình trực quan như VVVV , Max MSP hoặc giao diện dựa trên nút của Autodesk Maya đều dựa trên DAG.


-5

Một đồ thị xoay chiều có hướng rất hữu ích khi bạn muốn biểu diễn ... một đồ thị xoay chiều có hướng! Ví dụ kinh điển là một cây gia đình hoặc phả hệ.


Ah, điều đó cũng có lý. Nhưng vẫn còn, điều này có liên quan gì đến lập trình?
appshare.co

1
Cấu trúc dữ liệu liên quan gì đến lập trình?
Jonathan Feinberg

Vâng tôi hiểu. Nó chỉ rằng bạn đã không đề cập đến "cấu trúc dữ liệu" trong câu trả lời của bạn
appshare.co

5
! Lặp lại không cần giải thích =
Eva
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.