From Trees to DAGs: Practical Examples Using SharpGraphLib
This tutorial shows practical examples of using SharpGraphLib (a C# graph library) to model trees and directed acyclic graphs (DAGs). It covers creating graphs, traversals, common algorithms, and small real-world examples with compact, copy-paste-ready code snippets.
Prerequisites
- .NET 6+ or compatible SDK
- SharpGraphLib referenced via NuGet (assumed package id: SharpGraphLib). Install:
bash
dotnet add package SharpGraphLib
- Basic C# knowledge
1. Modeling a Tree
A tree is a directed graph where each node (except the root) has exactly one parent and there are no cycles. Use trees for hierarchical data: file systems, organization charts, parse trees.
Code: Define nodes and build a simple tree
csharp
using SharpGraphLib; using System; class TreeExample { static void Main() { var graph = new DirectedGraph<string>(); // Create nodes graph.AddVertex(“Root”); graph.AddVertex(“A”); graph.AddVertex(“B”); graph.AddVertex(“A1”); graph.AddVertex(“A2”); graph.AddVertex(“B1”); // Add edges (parent -> child) graph.AddEdge(“Root”, “A”); graph.AddEdge(“Root”, “B”); graph.AddEdge(“A”, “A1”); graph.AddEdge(“A”, “A2”); graph.AddEdge(“B”, “B1”); Console.WriteLine(“Pre-order traversal:”); foreach (var v in GraphTraversal.PreOrder(graph, “Root”)) Console.WriteLine(v); } }
Traversal: Pre-order and Post-order
Use depth-first search to produce pre-order/post-order sequences. Example utility (if SharpGraphLib lacks helpers):
csharp
public static class GraphTraversal { public static IEnumerable<T> PreOrder<T>(DirectedGraph<T> g, T root) { var visited = new HashSet<T>(); var stack = new Stack<T>(); stack.Push(root); while (stack.Count > 0) { var node = stack.Pop(); if (!visited.Add(node)) continue; yield return node; // push children in reverse to visit in insertion order foreach (var child in g.GetOutgoing(node).Reverse()) stack.Push(child); } } }
2. Converting a Tree to a DAG (sharing subtrees)
When multiple parents reference identical substructures, convert the tree to a DAG by reusing vertices. Example: expression trees with common subexpressions.
Code: Build expression tree with shared nodes
csharp
var dag = new DirectedGraph<string>(); dag.AddVertex(“x”); dag.AddVertex(“y”); dag.AddVertex(“add”); // represents (x + y) dag.AddVertex(“mul”); // represents (add * y) // edges for add: add -> x, add -> y dag.AddEdge(“add”, “x”); dag.AddEdge(“add”, “y”); // reuse ‘add’ as input to mul dag.AddEdge(“mul”, “add”); dag.AddEdge(“mul”, “y”);
3. Topological Sort on DAGs
Topological sorting orders vertices so every directed edge u->v has u before v. Useful for build systems, task scheduling, dependency resolution.
Code: Kahn’s algorithm (implementation)
csharp
public static IList<T> TopologicalSort<T>(DirectedGraph<T> g) { var inDegree = new Dictionary<T,int>(); foreach (var v in g.Vertices) inDegree[v] = 0; foreach (var u in g.Vertices) foreach (var v in g.GetOutgoing(u)) inDegree[v]++; var q = new Queue<T>(inDegree.Where(kv => kv.Value==0).Select(kv => kv.Key)); var result = new List<T>(); while (q.Count > 0) { var n = q.Dequeue(); result.Add(n); foreach (var m in g.GetOutgoing(n)) { inDegree[m]–; if (inDegree[m]==0) q.Enqueue(m); } } if (result.Count != inDegree.Count) throw new InvalidOperationException(“Graph has at least one cycle.”); return result; }
4. Detecting Cycles (ensure DAG)
Use DFS color marking or Kahn’s algorithm failure to detect cycles.
Code: DFS cycle detection
csharp
public static bool HasCycle<T>(DirectedGraph<T> g) { var color = g.Vertices.ToDictionary(v => v, v => 0); // 0=white,1=gray,2=black bool Dfs(T u) { color[u] = 1; foreach (var v in g.GetOutgoing(u)) { if (color[v] == 1) return true; if (color[v] == 0 && Dfs(v)) return true; } color[u] = 2; return false; } foreach (var v in g.Vertices) if (color[v] == 0 && Dfs(v)) return true; return false; }
5. Practical Examples
Example A — Build System Dependency
- Vertices: projects/files
- Edges: A -> B if A depends on B
- Use TopologicalSort to determine build order. Short snippet: use TopologicalSort(dag)
Example B — Task Scheduling with Shared Resources
- Create DAG of tasks; common subtask nodes reused to avoid duplicate work. Run topological order and execute in sequence, parallelizing nodes with zero in-degree.
Example C — Expression DAG for Memoized Evaluation
- Reuse identical subexpressions as single vertices; evaluate in topological order to compute outputs once.
6. Performance Tips
- Use adjacency lists and HashSet for vertices for O(1) lookups.
- Cache out-degrees/in-degrees if running many queries.
- For large graphs, prefer iterative algorithms (Kahn) to avoid recursion depth issues.
7. Summary
- Trees are simple directed graphs without cycles and with unique parent per node.
- DAGs allow shared substructures; use topological sort for ordering.
- Common operations: traversal, cycle detection, topological sort, and reuse of nodes to convert trees into DAGs.
If you want, I can convert these snippets into a runnable sample project (Program.cs + csproj).
Leave a Reply