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.
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.
Câu trả lời:
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 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
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 để:
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.
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 đề.
Đồ 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:
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.
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:
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ó
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.
Đồ 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, ...
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;
}
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 .
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.
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ệ.