C#のツリーデータ構造


248

C#でツリーまたはグラフのデータ構造を探していましたが、提供されていないようです。C#2.0を使用したデータ構造の詳細な調査では、その理由について少し説明しています。この機能を提供するために一般的に使用される便利なライブラリはありますか?おそらく、記事で提示された問題を解決するための戦略パターンを通じて。

独自のArrayListを実装するのと同じように、独自のツリーを実装するのは少しばかげています。

バランスが取れない一般的なツリーが欲しいだけです。ディレクトリツリーと考えてください。C5は見栄えがしますが、ツリー構造は、ノードの階層を表すよりも検索に適したバランスのとれた赤黒ツリーとして実装されているようです。



プロジェクトにTreeViewを含めて使用できない理由はありますか?実際にユーザーに表示する必要はありません。もちろん、これが選択肢にならない場合、いくつかの形式のプロジェクトがあります。特別な複雑さが必要な場合は、サンプルTreeNodeから継承する新しいクラスをいつでも作成できますか?
単にG.

9
非常に単純なツリーのUIライブラリ全体をインポートすることは悪い考えだと思います。
スティムス

1
やる気を出していただけませんか?実際のハードドライブのスペース要件が問題になることはもうありませんか?不器用?先ほど触れたように、これは既存のユーザーインターフェースを持たない特殊なソフトウェアや何かのソリューションではないことを理解できます。私は怠惰なプログラマーです、もし私が自由のための構造をすべて得ることができれば。そして、既存のライブラリには無料でたくさんのものがあり、それを多くのことのために使用した人々からたくさんのコードを見つけることができます。
単にG.

私は議論していません、あなたの推論を知りたいだけです。
単にG.

回答:


155

私の最善のアドバイスは、標準のツリーデータ構造がないということです。実装できる方法が多すぎて、1つのソリューションですべてのベースをカバーすることは不可能だからです。ソリューションが具体的であるほど、特定の問題に適用できる可能性は低くなります。LinkedListに悩まされることもあります。循環リンクリストが必要な場合はどうすればよいですか。

実装する必要がある基本的な構造はノードのコレクションになります。ここに、開始するためのオプションをいくつか示します。クラスNodeがソリューション全体の基本クラスであると仮定しましょう。

ツリーを下に移動するだけの場合は、Nodeクラスに子のリストが必要です。

ツリーを上に移動する必要がある場合、Nodeクラスには親ノードへのリンクが必要です。

これら2つのポイントのすべての特徴点と実装する必要があるその他のビジネスロジック(子の制限、子の並べ替えなど)を処理するAddChildメソッドを作成します。


5
個人的には、ライブラリーに追加された自己バランス型バイナリツリーの種類を気にしません。これは、単なるadjacenyリストを使用するよりも余分な作業であるためです。
jk。

8
@jk SortedDictionaryとSortedSetは赤/黒の木の上に構築されていると思うので、これらを使用しても機能するはずです。
jonp

複合パターンを見てみましょう;-)まさにあなたが探しているもの
Nicolas Voron

119
delegate void TreeVisitor<T>(T nodeData);

class NTree<T>
{
    private T data;
    private LinkedList<NTree<T>> children;

    public NTree(T data)
    {
         this.data = data;
        children = new LinkedList<NTree<T>>();
    }

    public void AddChild(T data)
    {
        children.AddFirst(new NTree<T>(data));
    }

    public NTree<T> GetChild(int i)
    {
        foreach (NTree<T> n in children)
            if (--i == 0)
                return n;
        return null;
    }

    public void Traverse(NTree<T> node, TreeVisitor<T> visitor)
    {
        visitor(node.data);
        foreach (NTree<T> kid in node.children)
            Traverse(kid, visitor);
    }
}

単純な再帰的な実装... <40行のコード...クラスの外のツリーのルートへの参照を保持するか、別のクラスにラップする必要があります。おそらくTreeNodeに名前を変更しますか?


22
この場合は、とにかくC#で、あなた自身のデリゲートを書い避け、あらかじめ用意された使用することができますAction<T>デリゲートを:public void traverse(NTree<T> node, Action<T> visitor)。アクション<>のシグネチャは次のとおりvoid Action<T>( T obj )です:。0から4つの異なるパラメーターのバージョンもあります。と呼ばれる関数の類似のデリゲートもありFunc<>ます。
Benny Jobigan

2
このデリゲートをどのように呼び出しますか?
気紛れに

3
トラバースメソッドを静的に変更するか、場合によっては再帰的な性質を隠すためにラップすることをお勧めしますが、トラバースするのは簡単です。デリゲートのシグネチャを持つメソッドを作成します。つまり、intのツリーの場合:void my_visitor_impl(int datum) -必要に応じて静的にします。delgateをインスタンス化します。TreeVisitor <int> my_visitor = my_visitor_impl; ルートノードまたはNTreeクラスを静的にした場合は、NTree <int> .traverse(my_tree、my_visitor)
Aaron Gage

10
addChild()が追加したNTreeを返すようにすると、ツリーにデータを追加するのに適した状態になります。(新しく追加された子== getChild(1)の実装の詳細に依存せずに、これを使用してツリーを構築する巧妙な方法がない場合を除きます)
Rory

1
このステートメント--i == 0は単一のケースでのみ機能すると思いますか?これは本当ですか。これは私を混乱させました
Waseem Ahmad Naeem

57

私の意見では、これはアーロンゲージのと非常によく似ていますが、もう少し慣習的なものです。私の目的のために、でパフォーマンスの問題に遭遇したことはありませんList<T>。必要に応じて、LinkedListに切り替えるのは簡単です。


namespace Overby.Collections
{
    public class TreeNode<T>
    {
        private readonly T _value;
        private readonly List<TreeNode<T>> _children = new List<TreeNode<T>>();

        public TreeNode(T value)
        {
            _value = value;
        }

        public TreeNode<T> this[int i]
        {
            get { return _children[i]; }
        }

        public TreeNode<T> Parent { get; private set; }

        public T Value { get { return _value; } }

        public ReadOnlyCollection<TreeNode<T>> Children
        {
            get { return _children.AsReadOnly(); }
        }

        public TreeNode<T> AddChild(T value)
        {
            var node = new TreeNode<T>(value) {Parent = this};
            _children.Add(node);
            return node;
        }

        public TreeNode<T>[] AddChildren(params T[] values)
        {
            return values.Select(AddChild).ToArray();
        }

        public bool RemoveChild(TreeNode<T> node)
        {
            return _children.Remove(node);
        }

        public void Traverse(Action<T> action)
        {
            action(Value);
            foreach (var child in _children)
                child.Traverse(action);
        }

        public IEnumerable<T> Flatten()
        {
            return new[] {Value}.Concat(_children.SelectMany(x => x.Flatten()));
        }
    }
}

コンストラクタで設定しているときにValueプロパティが公開されるのはなぜですか?それは、コンストラクタを介してすでに設定した後で、操作のために開いたままにしますか?プライベートセットにする必要がありますか?
PositiveGuy

もちろん、それを不変にしてみませんか?編集。
Ronnie Overby 2013年

ありがとう!私は自分で書く必要がないのがとても気に入りました。(それでも、それがネイティブに存在するものではないとは信じられません。私は常に.net、または少なくとも.net 4.0にはすべてがあると思っていました。)
neminem

3
私はこのソリューションが好きでした。挿入する必要があることもわかりました。そのために次のメソッドを追加しました。 public TreeNode<T> InsertChild(TreeNode<T> parent, T value) { var node = new TreeNode<T>(value) { Parent = parent }; parent._children.Add(node); return node; } var five = myTree.AddChild(5); myTree.InsertChild(five, 55);
JabberwockyDecompiler 2015年

48

さらに別のツリー構造:

public class TreeNode<T> : IEnumerable<TreeNode<T>>
{

    public T Data { get; set; }
    public TreeNode<T> Parent { get; set; }
    public ICollection<TreeNode<T>> Children { get; set; }

    public TreeNode(T data)
    {
        this.Data = data;
        this.Children = new LinkedList<TreeNode<T>>();
    }

    public TreeNode<T> AddChild(T child)
    {
        TreeNode<T> childNode = new TreeNode<T>(child) { Parent = this };
        this.Children.Add(childNode);
        return childNode;
    }

    ... // for iterator details see below link
}

使用例:

TreeNode<string> root = new TreeNode<string>("root");
{
    TreeNode<string> node0 = root.AddChild("node0");
    TreeNode<string> node1 = root.AddChild("node1");
    TreeNode<string> node2 = root.AddChild("node2");
    {
        TreeNode<string> node20 = node2.AddChild(null);
        TreeNode<string> node21 = node2.AddChild("node21");
        {
            TreeNode<string> node210 = node21.AddChild("node210");
            TreeNode<string> node211 = node21.AddChild("node211");
        }
    }
    TreeNode<string> node3 = root.AddChild("node3");
    {
        TreeNode<string> node30 = node3.AddChild("node30");
    }
}

ボーナス
:本格的なツリーを参照してください:

  • イテレータ
  • 探す
  • Java / C#

https://github.com/gt4dev/yet-another-tree-structure


コード例で検索をどのように使用しますか?どこnodeから来たの?検索コードを使用するためにツリーを反復処理する必要があるということですか?
BadmintonCat

@GrzegorzDevすべてのIEnumerable<>メンバーを実装していないため、コンパイルされないため、おそらく-1 。
Uwe Keim、2016年

1
@UweKeimよろしくお願いします。次回は実際の使い方でコードを使ってみてください。
szab.kel 2017

私が目にする唯一の問題は、IEnumerable <>を実装しているため、基本的なJsonConvertで正しくシリアル化されないことです
Rakiah

22

一般的に優れたC5 Generic Collection Libraryには、セット、バッグ、辞書など、いくつかの異なるツリーベースのデータ構造があります。ソースコードは、実装の詳細を調べたい場合に利用できます。(具体的なツリー構造は使用していませんが、実稼働コードでC5コレクションを使用して良好な結果が得られています。)


7
状況が変わったのかどうかはわかりませんが、現在、この本はC5サイトからPDFとして無料でダウンロードできます。
オスカー

4
ライブラリを補完する272ページの長いPDFがあるので、ドキュメントの欠如はもはや問題ではありません...コードの品質についてコメントすることはできませんが、ドキュメントの品質から判断して、私はこの今夜掘り下げるのを本当に楽しみにしています!
Florian Doyon、2010年

2
私の理解から、このC5ライブラリにはツリーがまったくなく、ツリーから派生したデータ構造の一部しかありません。
2014

10

http://quickgraph.codeplex.com/を参照してください

QuickGraphは、.Net 2.0以降向けの一般的な有向/無向グラフデータ構造とアルゴリズムを提供します。QuickGraphには、深さ優先検索、呼気優先検索、A *検索、最短経路、k最短経路、最大フロー、最小スパニングツリー、最小共通祖先などのアルゴリズムが付属しています。QuickGraphは、MSAGL、GLEE、Graphvizをサポートしています。グラフのレンダリング、GraphMLへのシリアル化など...


8

独自に作成する場合は、C#2.0のデータ構造の効果的な使用法と、C#でのデータ構造の実装を分析する方法を詳しく説明した6部構成のドキュメントから始めることができます。各記事には、例と、一緒に使用できるサンプルを含むインストーラーがあります。

Scott Mitchellによる「C#2.0を使用したデータ構造の広範な調査」


7

解決策を少し拡張します。

再帰的なジェネリック宣言と派生サブクラスを使用すると、実際のターゲットに集中できます。

非一般的な実装とは異なり、「NodeWorker」で「node」をキャストする必要がないことに注意してください。

これが私の例です:

public class GenericTree<T> where T : GenericTree<T> // recursive constraint  
{
  // no specific data declaration  

  protected List<T> children;

  public GenericTree()
  {
    this.children = new List<T>();
  }

  public virtual void AddChild(T newChild)
  {
    this.children.Add(newChild);
  }

  public void Traverse(Action<int, T> visitor)
  {
    this.traverse(0, visitor);
  }

  protected virtual void traverse(int depth, Action<int, T> visitor)
  {
    visitor(depth, (T)this);
    foreach (T child in this.children)
      child.traverse(depth + 1, visitor);
  }
}

public class GenericTreeNext : GenericTree<GenericTreeNext> // concrete derivation
{
  public string Name {get; set;} // user-data example

  public GenericTreeNext(string name)
  {
    this.Name = name;
  }
}

static void Main(string[] args)  
{  
  GenericTreeNext tree = new GenericTreeNext("Main-Harry");  
  tree.AddChild(new GenericTreeNext("Main-Sub-Willy"));  
  GenericTreeNext inter = new GenericTreeNext("Main-Inter-Willy");  
  inter.AddChild(new GenericTreeNext("Inter-Sub-Tom"));  
  inter.AddChild(new GenericTreeNext("Inter-Sub-Magda"));  
  tree.AddChild(inter);  
  tree.AddChild(new GenericTreeNext("Main-Sub-Chantal"));  
  tree.Traverse(NodeWorker);  
}  

static void NodeWorker(int depth, GenericTreeNext node)  
{                                // a little one-line string-concatenation (n-times)
  Console.WriteLine("{0}{1}: {2}", String.Join("   ", new string[depth + 1]), depth, node.Name);  
}  

深さとは何ですか?どこからどのようにしてそれを得るのですか?
PositiveGuy

@ WeDoTDD.comが彼のクラスを見ると、Traverseはそれを0としてルートノードから開始するように宣言し、メソッドtraverseを使用して、各反復でそのintに追加しています。
Edward

特定のノードのツリー全体をどのように検索しますか?
mattpm、

6

これが私のものです:

class Program
{
    static void Main(string[] args)
    {
        var tree = new Tree<string>()
            .Begin("Fastfood")
                .Begin("Pizza")
                    .Add("Margherita")
                    .Add("Marinara")
                .End()
                .Begin("Burger")
                    .Add("Cheese burger")
                    .Add("Chili burger")
                    .Add("Rice burger")
                .End()
            .End();

        tree.Nodes.ForEach(p => PrintNode(p, 0));
        Console.ReadKey();
    }

    static void PrintNode<T>(TreeNode<T> node, int level)
    {
        Console.WriteLine("{0}{1}", new string(' ', level * 3), node.Value);
        level++;
        node.Children.ForEach(p => PrintNode(p, level));
    }
}

public class Tree<T>
{
    private Stack<TreeNode<T>> m_Stack = new Stack<TreeNode<T>>();

    public List<TreeNode<T>> Nodes { get; } = new List<TreeNode<T>>();

    public Tree<T> Begin(T val)
    {
        if (m_Stack.Count == 0)
        {
            var node = new TreeNode<T>(val, null);
            Nodes.Add(node);
            m_Stack.Push(node);
        }
        else
        {
            var node = m_Stack.Peek().Add(val);
            m_Stack.Push(node);
        }

        return this;
    }

    public Tree<T> Add(T val)
    {
        m_Stack.Peek().Add(val);
        return this;
    }

    public Tree<T> End()
    {
        m_Stack.Pop();
        return this;
    }
}

public class TreeNode<T>
{
    public T Value { get; }
    public TreeNode<T> Parent { get; }
    public List<TreeNode<T>> Children { get; }

    public TreeNode(T val, TreeNode<T> parent)
    {
        Value = val;
        Parent = parent;
        Children = new List<TreeNode<T>>();
    }

    public TreeNode<T> Add(T val)
    {
        var node = new TreeNode<T>(val, this);
        Children.Add(node);
        return node;
    }
}

出力:

Fastfood
   Pizza
      Margherita
      Marinara
   Burger
      Cheese burger
      Chili burger
      Rice burger

4

この簡単なサンプルを試してください。

public class TreeNode<TValue>
{
    #region Properties
    public TValue Value { get; set; }
    public List<TreeNode<TValue>> Children { get; private set; }
    public bool HasChild { get { return Children.Any(); } }
    #endregion
    #region Constructor
    public TreeNode()
    {
        this.Children = new List<TreeNode<TValue>>();
    }
    public TreeNode(TValue value)
        : this()
    {
        this.Value = value;
    }
    #endregion
    #region Methods
    public void AddChild(TreeNode<TValue> treeNode)
    {
        Children.Add(treeNode);
    }
    public void AddChild(TValue value)
    {
        var treeNode = new TreeNode<TValue>(value);
        AddChild(treeNode);
    }
    #endregion
}

2

他の人に役立つNodeクラスを作成します。クラスには次のようなプロパティがあります。

  • 子供達
  • 祖先
  • 子孫
  • きょうだい
  • ノードのレベル
  • ルート
  • 等。

IdとParentIdを持つアイテムのフラットリストをツリーに変換する可能性もあります。ノードは子と親の両方への参照を保持するため、ノードの反復が非常に高速になります。


2

それが言及されていないので、今リリースされた.netコードベース、特に赤黒SortedSetツリーを実装するのコードに注目してもらいたいのです。

https://github.com/Microsoft/referencesource/blob/master/System/compmod/system/collections/generic/sortedset.cs

ただし、これはバランスの取れたツリー構造です。したがって、私の答えは、.netコアライブラリの唯一のネイティブツリー構造であると私が信じているものへの参照です。


2

@Berezhが共有したコードを完成させました。

  public class TreeNode<T> : IEnumerable<TreeNode<T>>
    {

        public T Data { get; set; }
        public TreeNode<T> Parent { get; set; }
        public ICollection<TreeNode<T>> Children { get; set; }

        public TreeNode(T data)
        {
            this.Data = data;
            this.Children = new LinkedList<TreeNode<T>>();
        }

        public TreeNode<T> AddChild(T child)
        {
            TreeNode<T> childNode = new TreeNode<T>(child) { Parent = this };
            this.Children.Add(childNode);
            return childNode;
        }

        public IEnumerator<TreeNode<T>> GetEnumerator()
        {
            throw new NotImplementedException();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return (IEnumerator)GetEnumerator();
        }
    }
    public class TreeNodeEnum<T> : IEnumerator<TreeNode<T>>
    {

        int position = -1;
        public List<TreeNode<T>> Nodes { get; set; }

        public TreeNode<T> Current
        {
            get
            {
                try
                {
                    return Nodes[position];
                }
                catch (IndexOutOfRangeException)
                {
                    throw new InvalidOperationException();
                }
            }
        }


        object IEnumerator.Current
        {
            get
            {
                return Current;
            }
        }


        public TreeNodeEnum(List<TreeNode<T>> nodes)
        {
            Nodes = nodes;
        }

        public void Dispose()
        {
        }

        public bool MoveNext()
        {
            position++;
            return (position < Nodes.Count);
        }

        public void Reset()
        {
            position = -1;
        }
    }

良いデザイン。ただし、ノードがその子ノードのシーケンスであるかどうかはわかりません。次のことを検討します。ノードは0個以上の子ノードを「持っている」ため、ノードは一連の子ノードから派生していませんが、子ノードの集約(構成?)です
Harald Coppoolse

2

これが木です

public class Tree<T> : List<Tree<T>>
{
    public  T Data { get; private set; }

    public Tree(T data)
    {
        this.Data = data;
    }

    public Tree<T> Add(T data)
    {
        var node = new Tree<T>(data);
        this.Add(node);
        return node;
    }
}

イニシャライザを使用することもできます。

    var tree = new Tree<string>("root")
    {
        new Tree<string>("sample")
        {
            "console1"
        }
    };

1

ほとんどのツリーは、処理しているデータによって形成されます。

あなたが持っていると言うperson誰かの内容を含んでいるクラスを parents使用すると、むしろあなたの「ドメインクラス」の一環として、ツリー構造を持っている、またはあなたの人のオブジェクトへのリンクが含まれて別のツリークラスを使用することになり、?すべてのgrandchildrenを取得するなどの簡単な操作について考えてみてpersonください。このコードはperson クラス内にあるのpersonでしょうか、それともクラスのユーザーが別のツリークラスについて知っている必要があるのでしょうか。

もう1つの例は、コンパイラーの解析ツリーです...

これらの例の両方が示すのは、ツリーの概念がドメインの一部であることですデータの、別個の汎用ツリーを使用すると、作成されるオブジェクトの数が少なくとも2倍になり、APIを再度プログラムすることが困難になることです。

私たちが望むのは、すべてのツリーに対してそれらを再実装する必要なく、同時に標準ツリークラスを使用する必要がない標準のツリー操作を再利用する方法です。BoostはC ++でこの種の問題を解決しようとしましたが、.NETへの影響が適応されるのはまだわかりません。


@Puchacz、申し訳ありませんが、C ++のデータが15年切れてしまいました。Boostとテンプレートを見てください。いくつかの弱い研究を経て、それらを理解できます。パワーは学習コストが高い!!
Ian Ringrose 2016年

1

上記のNTreeクラスを使用した完全なソリューションと例を追加し、「AddChild」メソッドも追加しました...

    public class NTree<T>
    {
        public T data;
        public LinkedList<NTree<T>> children;

        public NTree(T data)
        {
            this.data = data;
            children = new LinkedList<NTree<T>>();
        }

        public void AddChild(T data)
        {
            var node = new NTree<T>(data) { Parent = this };
            children.AddFirst(node);
        }

        public NTree<T> Parent { get; private set; }

        public NTree<T> GetChild(int i)
        {
            foreach (NTree<T> n in children)
                if (--i == 0)
                    return n;
            return null;
        }

        public void Traverse(NTree<T> node, TreeVisitor<T> visitor, string t, ref NTree<T> r)
        {
            visitor(node.data, node, t, ref r);
            foreach (NTree<T> kid in node.children)
                Traverse(kid, visitor, t, ref r);
        }
    }
    public static void DelegateMethod(KeyValuePair<string, string> data, NTree<KeyValuePair<string, string>> node, string t, ref NTree<KeyValuePair<string, string>> r)
    {
        string a = string.Empty;
        if (node.data.Key == t)
        {
            r = node;
            return;
        }
    }

を使用して

 NTree<KeyValuePair<string, string>> ret = null;
 tree.Traverse(tree, DelegateMethod, node["categoryId"].InnerText, ref ret);

トラバースは静的メソッドである必要がありますか?それ自体にそれ自体を渡すインスタンスメソッドとして非常にぎこちないようです
Sinaesthetic '30年

0

ここにBSTの私の実装があります

class BST
{
    public class Node
    {
        public Node Left { get; set; }
        public object Data { get; set; }
        public Node Right { get; set; }

        public Node()
        {
            Data = null;
        }

        public Node(int Data)
        {
            this.Data = (object)Data;
        }

        public void Insert(int Data)
        {
            if (this.Data == null)
            {
                this.Data = (object)Data;
                return;
            }
            if (Data > (int)this.Data)
            {
                if (this.Right == null)
                {
                    this.Right = new Node(Data);
                }
                else
                {
                    this.Right.Insert(Data);
                }
            }
            if (Data <= (int)this.Data)
            {
                if (this.Left == null)
                {
                    this.Left = new Node(Data);
                }
                else
                {
                    this.Left.Insert(Data);
                }
            }
        }

        public void TraverseInOrder()
        {
            if(this.Left != null)
                this.Left.TraverseInOrder();
            Console.Write("{0} ", this.Data);
            if (this.Right != null)
                this.Right.TraverseInOrder();
        }
    }

    public Node Root { get; set; }
    public BST()
    {
        Root = new Node();
    }
}


-4

使用するメモリが少ないルート化されたツリーデータ構造の実装が必要な場合は、次のようにNodeクラスを記述できます(C ++実装)。

class Node {
       Node* parent;
       int item; // depending on your needs

       Node* firstChild; //pointer to left most child of node
       Node* nextSibling; //pointer to the sibling to the right
}

12
特にC#の質問にC ++コードを投稿することは、最高のアイデアではありません、ジェイク。特にポインタを含むもの。ポインタがC#で容赦なく追い詰められていることをご存知でしょうか?:p
ThunderGr 2013年

2
公平ではない@ThunderGr。C#での回答の方が良かったのですが、それらのC ++ポインターはC#スピーカーが参照として理解できます(安全性は低くなります)。David Boike、Aaron Gage、Ronnie Overby、Grzegorz Dev、Berezh、およびErik Nagelが基本的に同じデータ構造をサジェストした後、わずかな違いが式にあるだけで、ジェイクはリンクされたリストを分解して、1種類のノードのみを持つより単純な構造を生成し、兄弟のナビゲーション可能性。建設的な答えに反対票を投じて、C ++への嫌悪感を表明しないでください。
migle

3
@migle回答に反対票を投じなかった(反対票も投じなかった)。そして、私はC ++を嫌いではありません。なぜ、どのようにして彼の回答を改善するかについてジェイクに何も提案することなく、回答が反対票を投じられたことがわかりました。「良くなる」ということではありません。質問はC#のみにタグ付けされています。タグ以外の言語で回答を投稿することはお勧めできません。反対票を投じる人もいます。
ThunderGr 2014年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.