2011-06-18 41 views
11

我試圖實現算法以將有向無環圖轉換爲樹(有趣,學習,kata,將其命名)。於是我拿出數據結構的節點:將有向非循環圖(DAG)轉換爲樹

DAG to Tree

/// <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,平等相待,==操作符
  • 實現了IComparable
  • LinkedList可能是更好的選擇

另外,在轉換之前有需要進行檢查certian thigs:

  • 多重圖
  • 如果它是DAG(週期)
  • Diamnods在DAG中的DAG
  • 多根

總而言之,它縮小到幾個問題: 如何改進轉換?由於這是一個反覆,有可能炸燬堆棧。我可以添加堆棧來記憶它。如果我做延續傳球的風格,我會更有效率嗎?

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

Childs是正確的名字嗎? :)

+0

在回答你的問題'孩子是正確的名字嗎?','孩子'是一個更好的名字,甚至是'ChildNodes'。 –

+0

'Children'更好:) – pbalaga

+0

100%確定Children節點在Tree中。圖形(各種)還有childern節點?圖論中的 –

回答

7

算法:

  • 當你觀察到,一些節點輸出出現兩次。如果節點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 
    } 
    
  • 我不會太擔心堆棧或遞歸的性能的大小。但是,您可以用Breadth-first-search替換深度優先搜索,這會導致更靠近先前訪問的根的節點,從而生成更「自然」的樹(在圖片中,您已按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); 
        } 
    } 
    

    算法處理鑽石和週期。

  • 多根

    只是聲明一個類圖將包含所有頂點

    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) 
    
  • 特拉弗斯,遊客完全沒有必要。你可以相當有其產生樹的所有節點(在相同的序遍歷一樣)的方法,它是由用戶做任何與節點:

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

  • 除了重新分配(容量爲2,4,8,16,...)列表在添加節點時做什麼之外,我沒有看到LinkedList會比List更好的原因。

+0

1:我完全不明白「什麼都不做」。 1 - > 2和3 - > 2之間仍然存在關聯。Net在遞歸解決方案方面做得很好,但我已經實現了另一個帶有明確堆棧的版本(udpated post - 廣度優先搜索絕對更好)。 3:class Graph應該檢查是否不添加另一個根「4:同意5:因爲我正在改變集合,所以我不能這樣做6:是的7:是的8:我都不是 –

+0

@ 5,這個for循環是一個對抖動進行優化要快一些,還有一些DAG發生器可以很好地測試。 –

1
  1. 可要張貼在CodeReview
  2. 蔡爾茲是錯誤=>兒童
  3. 你不必使用HashSet的,你可以很容易地使用列表>,因爲只有在檢查引用這裏夠了。 (所以沒有GetHashCode,Equals和運算符必須被覆蓋)

  4. 更簡單的方法是序列化你的類,然後用XmlSerializer反序列化成第二個對象。 而序列化和反序列化時,引用2次的1個對象將成爲具有不同引用的2個對象。

+0

+1對於XmlSerializer中序列化,然後解序列化爲第二個對象的想法。 –