簡體   English   中英

將有向無環圖(DAG)轉換為樹

[英]Converting Directed Acyclic Graph (DAG) to tree

我正在嘗試實現algoritm將Directed Acyclic Graph轉換為Tree(為了好玩,學習,kata,命名它)。 所以我想出了數據結構Node:

DAG到樹

/// <summary>
/// Represeting a node in DAG or Tree
/// </summary>
/// <typeparam name="T">Value of the node</typeparam>
public class Node<T> 
{
    /// <summary>
    /// creats a node with no child nodes
    /// </summary>
    /// <param name="value">Value of the node</param>
    public Node(T value)
    {
        Value = value;
        ChildNodes = new List<Node<T>>();
    }

    /// <summary>
    /// Creates a node with given value and copy the collection of child nodes
    /// </summary>
    /// <param name="value">value of the node</param>
    /// <param name="childNodes">collection of child nodes</param>
    public Node(T value, IEnumerable<Node<T>> childNodes)
    {
        if (childNodes == null)
        {
            throw new ArgumentNullException("childNodes");
        }
        ChildNodes = new List<Node<T>>(childNodes);
        Value = value;
    }

    /// <summary>
    /// Determines if the node has any child node
    /// </summary>
    /// <returns>true if has any</returns>
    public bool HasChildNodes
    {
        get { return this.ChildNodes.Count != 0; }
    }


    /// <summary>
    /// Travearse the Graph recursively
    /// </summary>
    /// <param name="root">root node</param>
    /// <param name="visitor">visitor for each node</param>
    public void Traverse(Node<T> root, Action<Node<T>> visitor)
    {
        if (root == null)
        {
            throw new ArgumentNullException("root");
        }
        if (visitor == null)
        {
            throw new ArgumentNullException("visitor");
        }

        visitor(root); 
        foreach (var node in root.ChildNodes)
        {
            Traverse(node, visitor);
        }
    }

    /// <summary>
    /// Value of the node
    /// </summary>
    public T Value { get; private set; }

    /// <summary>
    /// List of all child nodes
    /// </summary>
    public List<Node<T>> ChildNodes { get; private set; }
}

這很簡單。 方法:

/// <summary>
/// Helper class for Node 
/// </summary>
/// <typeparam name="T">Value of a node</typeparam>
public static class NodeHelper
{
    /// <summary>
    /// Converts Directed Acyclic Graph to Tree data structure using recursion.
    /// </summary>
    /// <param name="root">root of DAG</param>
    /// <param name="seenNodes">keep track of child elements to find multiple connections (f.e. A connects with B and C and B also connects with C)</param>
    /// <returns>root node of the tree</returns>
    public static Node<T> DAG2TreeRec<T>(this Node<T> root, HashSet<Node<T>> seenNodes)
    {
        if (root == null)
        {
            throw new ArgumentNullException("root");
        }
        if (seenNodes == null)
        {
            throw new ArgumentNullException("seenNodes");
        }

        var length = root.ChildNodes.Count;
        for (int i = 0; i < length; ++i)
        {
            var node = root.ChildNodes[i];
            if (seenNodes.Contains(node))
            {
                var nodeClone = new Node<T>(node.Value, node.ChildNodes);
                node = nodeClone;
            }
            else
            {
                seenNodes.Add(node);
            }
            DAG2TreeRec(node, seenNodes);
        }
        return root;
    }
    /// <summary>
    /// Converts Directed Acyclic Graph to Tree data structure using explicite stack.
    /// </summary>
    /// <param name="root">root of DAG</param>
    /// <param name="seenNodes">keep track of child elements to find multiple connections (f.e. A connects with B and C and B also connects with C)</param>
    /// <returns>root node of the tree</returns>
    public static Node<T> DAG2Tree<T>(this Node<T> root, HashSet<Node<T>> seenNodes)
    {
        if (root == null)
        {
            throw new ArgumentNullException("root");
        }
        if (seenNodes == null)
        {
            throw new ArgumentNullException("seenNodes");
        }

        var stack = new Stack<Node<T>>();
        stack.Push(root);

        while (stack.Count > 0) 
        {
            var tempNode = stack.Pop();
            var length = tempNode.ChildNodes.Count;
            for (int i = 0; i < length; ++i)
            {
                var node = tempNode.ChildNodes[i];
                if (seenNodes.Contains(node))
                {
                    var nodeClone = new Node<T>(node.Value, node.ChildNodes);
                    node = nodeClone;
                }
                else
                {
                    seenNodes.Add(node);
                }
               stack.Push(node);
            }
        } 
        return root;
    }
}

並測試:

    static void Main(string[] args)
    {
        // Jitter preheat
        Dag2TreeTest();
        Dag2TreeRecTest();

        Console.WriteLine("Running time ");
        Dag2TreeTest();
        Dag2TreeRecTest();

        Console.ReadKey();
    }

    public static void Dag2TreeTest()
    {
        HashSet<Node<int>> hashSet = new HashSet<Node<int>>();

        Node<int> root = BulidDummyDAG();

        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();
        var treeNode = root.DAG2Tree<int>(hashSet);
        stopwatch.Stop();

        Console.WriteLine(string.Format("Dag 2 Tree = {0}ms",stopwatch.ElapsedMilliseconds));

    }

    private static Node<int> BulidDummyDAG()
    {
        Node<int> node2 = new Node<int>(2);
        Node<int> node4 = new Node<int>(4);
        Node<int> node3 = new Node<int>(3);
        Node<int> node5 = new Node<int>(5);
        Node<int> node6 = new Node<int>(6);
        Node<int> node7 = new Node<int>(7);
        Node<int> node8 = new Node<int>(8);
        Node<int> node9 = new Node<int>(9);
        Node<int> node10 = new Node<int>(10);
        Node<int> root  = new Node<int>(1);

        //making DAG                   
        root.ChildNodes.Add(node2);    
        root.ChildNodes.Add(node3);    
        node3.ChildNodes.Add(node2);   
        node3.ChildNodes.Add(node4);   
        root.ChildNodes.Add(node5);    
        node4.ChildNodes.Add(node6);   
        node4.ChildNodes.Add(node7);
        node5.ChildNodes.Add(node8);
        node2.ChildNodes.Add(node9);
        node9.ChildNodes.Add(node8);
        node9.ChildNodes.Add(node10);

        var length = 10000;
        Node<int> tempRoot = node10; 
        for (int i = 0; i < length; i++)
        {
            var nextChildNode = new Node<int>(11 + i);
            tempRoot.ChildNodes.Add(nextChildNode);
            tempRoot = nextChildNode;
        }

        return root;
    }

    public static void Dag2TreeRecTest()
    {
        HashSet<Node<int>> hashSet = new HashSet<Node<int>>();

        Node<int> root = BulidDummyDAG();

        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();
        var treeNode = root.DAG2TreeRec<int>(hashSet);
        stopwatch.Stop();

        Console.WriteLine(string.Format("Dag 2 Tree Rec = {0}ms",stopwatch.ElapsedMilliseconds));
    }

更重要的是,數據結構需要一些改進:

  • 覆蓋GetHash,toString,Equals,== operator
  • 實現IComparable
  • LinkedList可能是更好的選擇

此外,在轉換之前,需要檢查certian thigs:

  • 多重圖
  • 如果是DAG(周期)
  • DAG的Diamnods
  • DAG中有多個根

總而言之,它縮小為幾個問題: 如何改善轉換? 由於這是一次復發,因此可能會炸毀堆棧。 我可以添加堆棧來記住它。 如果我繼續傳遞風格,我會更有效率嗎?

我覺得在這種情況下不可變的數據結構會更好。 這是對的嗎?

Childs是正確的名字嗎? :)

算法:

  • 如您所見,某些節點在輸出中出現兩次。 如果節點2有子節點,則整個子樹將出現兩次。 如果您希望每個節點只出現一次,請替換

     if (hashSet.Contains(node)) { var nodeClone = new Node<T>(node.Value, node.Childs); node = nodeClone; } 

     if (hashSet.Contains(node)) { // node already seen -> do nothing } 
  • 我不會太擔心堆棧的大小或遞歸的性能。 但是,您可以使用廣度優先搜索替換深度優先搜索 ,這將導致節點更接近先前訪問的根,從而產生更“自然”的樹(在您的圖片中,您已經按照BFS順序編號了節點) )。

      var seenNodes = new HashSet<Node>(); var q = new Queue<Node>(); q.Enqueue(root); seenNodes.Add(root); while (q.Count > 0) { var node = q.Dequeue(); foreach (var child in node.Childs) { if (!seenNodes.Contains(child )) { seenNodes.Add(child); q.Enqueue(child); } } 

    該算法處理鑽石和周期。

  • 多根

    只需聲明一個包含所有頂點的類Graph

     class Graph { public List<Node> Nodes { get; private set; } public Graph() { Nodes = new List<Node>(); } } 

碼:

  • hashSet可以命名為seenNodes

  • 代替

     var length = root.Childs.Count; for (int i = 0; i < length; ++i) { var node = root.Childs[i]; 

     foreach (var child in root.Childs) 
  • 在Traverse,訪客是非常不必要的。 你可能寧願有一個方法可以產生樹的所有節點(以與遍歷相同的順序),並且用戶可以對節點做任何事情:

     foreach(var node in root.TraverseRecursive()) { Console.WriteLine(node.Value); } 
  • 如果重寫GetHashCode和Equals,該算法將無法再區分具有相同值的兩個不同節點,這可能不是您想要的。

  • 我沒有看到為什么LinkedList比List更好的原因,除了List在添加節點時所做的重新分配(容量2,4,8,16,...)。

  1. 你最好在CodeReview中發布
  2. 孩子是錯的=>孩子
  3. 你不必使用HashSet,你可以很容易地使用List>,因為這里只檢查引用就足夠了。 (因此不需要GetHashCode,Equals和運算符覆蓋)

  4. 更簡單的方法是序列化您的類,然后使用XmlSerializer將其再次反序列化為第二個對象。 序列化和反序列化時,引用2次的1個對象將成為具有不同引用的2個對象。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM