12

有向非巡回グラフをツリーに変換するアルゴリズムを実装しようとしています(楽しみ、学習、カタ、名前を付けてください)。だから私はデータ構造ノードを思い付きました:

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、== 演算子のオーバーライド
  • IComparable の実装
  • LinkedList はおそらくより良い選択です

また、変換の前に確認する必要がある特定の事柄があります。

  • マルチグラフ
  • DAG(Cycles)なら
  • DAG のダイアモンド
  • DAG の複数のルート

全体として、いくつかの質問に絞り込まれ ます。コンバージョンを改善するにはどうすればよいですか? これは再帰であるため、スタックを爆破することができます。スタックを追加して記憶することができます。継続渡しスタイルを行うと、より効率的になりますか?

この場合、不変のデータ構造の方が良いと思います。それが正しいか?

チャイルズは正しい名前ですか?:)

4

2 に答える 2

7

アルゴリズム:

  • ご覧のとおり、一部のノードは出力に2回表示されます。ノード2に子がある場合、サブツリー全体が2回表示されます。各ノードを1回だけ表示する場合は、

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

    アルゴリズムはダイヤモンドとサイクルを処理します。

  • 重根

    すべての頂点を含むクラスグラフを宣言するだけです

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

コード:

  • hashSetseenNodesという名前にすることができます。

  • それ以外の

    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つの異なるノードを区別できなくなります。これはおそらくあなたが望むものではありません。

  • ノードを追加するときにListが行う再割り当て(容量2、4、8、16、...)を除いて、LinkedListがListよりも優れている理由はわかりません。

于 2011-06-26T21:14:30.777 に答える
3
  1. CodeReviewに投稿したほうがいい
  2. チャイルズが間違っている => 子供
  3. ここでは参照のみをチェックするだけで十分なので、HashSet を使用する必要はありません。List> を簡単に使用できます。(したがって、GetHashCode、Equals、および演算子のオーバーライドは必要ありません)

  4. より簡単な方法は、クラスをシリアライズしてから、XmlSerializer を使用して 2 番目のオブジェクトに再度デシリアライズすることです。Serialized と Deserialized の間、2 回参照される 1 つのオブジェクトは異なる参照を持つ 2 つのオブジェクトになります。

于 2011-06-18T17:39:56.473 に答える