C ++ STLが「ツリー」コンテナを提供しないのはなぜですか?


373

C ++ STLが「ツリー」コンテナを提供しないのはなぜですか、代わりに何を使用するのが最善ですか?

オブジェクトの階層をツリーとして保存するのではなく、ツリーをパフォーマンス強化として使用したい...


7
階層の表現を格納するツリーが必要です。
ロディ

20
私は「正しい」答えに反対票を投じた男と一緒にいます。「木は役に立たない」。木の使用が曖昧であるかどうかが重要です。
Joe Soul-bringer

その理由は些細なことだと思います-まだ誰も標準ライブラリに実装していません。それはありませんでした標準ライブラリのようなものだstd::unordered_mapstd::unordered_set最近まで。それまでは、標準ライブラリにはSTLコンテナはまったくありませんでした。
DOC

1
私の考え(ただし、関連する標準を一度も読んだことがないため、これは回答ではなくコメントです)は、STLが特定のデータ構造を気にせず、複雑さに関する仕様とサポートされる操作を気にするということです。したがって、使用される基本的な構造は、仕様を満たす場合、実装やターゲットアーキテクチャによって異なる場合があります。私はかなり確信しているstd::mapし、std::setそこにすべての実装でツリーを使用しますが、彼らはいくつかの非ツリー構造はまた、仕様を満たしている場合場合にはありません。
Mark K Cowan 2017

回答:


182

ツリーを使用する理由は2つあります。

ツリーのような構造を使用して問題をミラーリングしたい
場合:このために、ブーストグラフライブラリがあります。

または、ツリーのようなアクセス特性を持つコンテナが必要です。このために、

基本的に、これら2つのコンテナーの特性は、実際にはツリーを使用して実装する必要があるというものです(ただし、これは実際には要件ではありません)。

この質問も参照してください: Cツリーの実装


64
ツリーが最も一般的であるとしても、ツリーを使用する理由はたくさんあります。最も一般的な!等しいすべて。
Joe Soul-bringer

3
ツリーを必要とする3番目の主な理由は、高速に挿入/削除できる常にソートされたリストのためですが、そのためにstd:multisetがあります。
VoidStar

1
@Durga:ソートされたコンテナとしてマップを使用している場合、深度がどのように関連しているかはわかりません。マップは、log(n)の挿入/削除/ルックアップ(およびソートされた順序で要素を含む)を保証します。これはすべてマップが使用され、(通常は)赤/黒ツリーとして実装されます。赤/黒のツリーは、ツリーのバランスが取れていることを確認します。したがって、ツリーの深さは、ツリー内の要素の数に直接関連しています。
マーティンヨーク

14
2008年と現在の両方で、この回答に同意しません。標準ライブラリはブーストを「持っている」わけではなく、ブーストで何かが利用可能であることは、それを標準に採用しない理由にはなりません(されていません)。さらに、BGLは一般的であり、BGLから独立した特殊なツリークラスに値するほど十分に関与しています。また、STD ::マップとstd ::セットは木を必要とするという事実は、IMO、持つための別の引数は、あるstl::red_black_tree等の最後に、std::mapそしてstd::set木がバランスしている、std::treeかもしれないができません。
einpoklum

1
@einpoklum:「ブーストで何かが利用可能であっても、それを標準に採用しない理由にはならない」- ブーストの目的の1つは、標準に組み込む前に有用なライブラリーの試験場として機能することです。 「絶対に!」と言います。
Martin Bonnerがモニカをサポートする

94

おそらく、boostにツリーコンテナがないのと同じ理由で。このようなコンテナーを実装する方法はたくさんありますが、それを使用するすべての人を満足させる良い方法はありません。

考慮すべきいくつかの問題:

  • ノードの子の数は固定ですか、それとも変数ですか?
  • ノードあたりのオーバーヘッドはどのくらいですか?-つまり、親ポインター、兄弟ポインターなどが必要ですか。
  • 提供するアルゴリズムは?-異なるイテレータ、検索アルゴリズムなど

結局のところ、問題は結局のところ、誰にとっても十分に役立つツリーコンテナが、それを使用するほとんどの人々を満足させるには重すぎるということです。強力なものを探している場合、Boost Graph Libraryは、本質的にツリーライブラリの使用目的のスーパーセットです。

その他の一般的なツリー実装は次のとおりです。


5
「...全員を満足させる良い方法はありません...」stl :: map、stl :: multimap、およびstl :: setはstlのrb_treeに基づいているため、これらの基本的な型が行うのと同じように多くの場合を満たす必要があります。
Catskul、

44
のノードの子を取得する方法がないことを考えるstd::mapと、これらのツリーコンテナーは呼び出さないでしょう。これらは、通常ツリーとして実装される連想コンテナです。大きな違い。
Mooing Duck 2013

2
Mooing Duckに同意します。std:: mapで幅優先検索をどのように実装しますか?それはものすごく高くなるだろう
マルコA. 14

1
Kasper Peetersのtree.hhを使い始めましたが、GPLv3または他のGPLバージョンのライセンスを確認した後、商用ソフトウェアを汚染しました。商用目的の構造が必要な場合は、@ hplbshのコメントで提供されているtreetreeを確認することをお勧めします。
Jake88 14

3
ツリーに対するさまざまな固有の要件は、ツリーをまったく持たないことではなく、さまざまなタイプのツリーを持つことへの議論です。
アンドレ

50

STLの哲学は、コンテナーの実装方法ではなく、保証に基づいてコンテナーを選択することです。たとえば、コンテナの選択は、高速検索の必要性に基づいている場合があります。コンテナは一方向のリストとして実装することもできます-検索が非常に高速である限り、満足できます。これは、内部に触れていないためです。アクセスにはイテレータまたはメンバー関数を使用しています。コードは、コンテナの実装方法に拘束されるのではなく、コンテナの速度、固定された順序付けが定義されているかどうか、スペースで効率的かどうかなどに依存します。


12
彼はコンテナーの実装について話しているのではなく、実際のツリーコンテナー自体について話していると思います。
Mooing Duck 2013

3
@MooingDuck wilhelmtellが意味することは、C ++標準ライブラリは、基礎となるデータ構造に基づいてコンテナを定義しないということです。インターフェースと漸近的パフォーマンスのような観察可能な特性によってのみコンテナを定義します。考えてみると、ツリーは実際には(私たちが知っているように)コンテナーではありません。彼らはAAまっすぐ前方を持っていないend()と、begin()あなたはすべての要素を反復処理することができたと、など
ヨルダンメロ

7
@JordanMelo:すべての点でナンセンス。オブジェクトを含むものです。begin()とend()と双方向反復子を使用して反復するように設計するのは非常に簡単です。各コンテナーには異なる特性があります。さらにツリーの特性を持たせることができれば便利です。かなり簡単なはずです。
Mooing Duck、2015

したがって、子ノードと親ノードの高速なルックアップを提供するコンテナと、適切なメモリ要件が必要です。
文書

@JordanMelo:その観点から、キュー、スタック、優先度キューなどのアダプターもSTLに属しません(それらにはbegin()and もありませんend())。また、プライオリティキューは通常ヒープであり、少なくとも理論上はツリーです(実際の実装でも)。そのため、基になるデータ構造を使用してツリーをアダプタとして実装した場合でも、STLに含めることができます。
andreee

48

「オブジェクトの階層をツリーとして保存したい」

C ++ 11は登場し去りましたstd::treeが、アイデアは実現しましたが(ここを参照)、まだ提供する必要はありませんでした。多分彼らがこれを追加しなかった理由は、既存のコンテナの上に独自のものを構築することは簡単なことです。例えば...

template< typename T >
struct tree_node
   {
   T t;
   std::vector<tree_node> children;
   };

単純なトラバーサルは再帰を使用します...

template< typename T >
void tree_node<T>::walk_depth_first() const
   {
   cout<<t;
   for ( auto & n: children ) n.walk_depth_first();
   }

あなたは階層を維持したい場合、あなたがそれを操作するSTLアルゴリズム、そして物事が複雑になることがあります。独自のイテレータを作成して互換性を実現することもできますが、アルゴリズムの多くは階層(たとえば、範囲の順序を変更するもの)にはまったく意味がありません。階層内で範囲を定義することさえ、厄介なビジネスになる可能性があります。


2
プロジェクトでtree_nodeの子を並べ替えることができる場合は、std :: vector <>の代わりにstd :: set <>を使用してから、operator <()をtree_nodeオブジェクトに追加すると、大幅に改善されます。 「T」のようなオブジェクトの「検索」パフォーマンス。
Jジョーゲンソン2013年

4
彼らは怠惰で、実際に最初の例を未定義の動作にしたことがわかります。
user541686

2
@Mehrdad:私は最終的にここにあなたのコメントの背後にある詳細を尋ねることに決めまし
nobar

many of the algorithms simply don't make any sense for a hierarchy。解釈の問題。stackoverflowユーザーの構造を想像してみてください。毎年、レピュテーションポイントの高いユーザーが、レピュテーションポイントの低いユーザーをボスにしたいと考えています。したがって、毎年実行するだけでBFSイテレータと適切な比較を提供できますstd::sort(tree.begin(), tree.end())
文書

同じように、上記の例のvectorと置き換えることで、連想ツリーを簡単に構築できます(たとえば、JSONなどの非構造化Key-Valueレコードをモデル化するため)map。JSONのような構造を完全にサポートするvariantには、ノードの定義に使用できます。
nobar

43

RBツリーの実装を探している場合は、stl_tree.hも適切な場合があります。


14
奇妙なことに、これは実際に元の質問に答える唯一の応答です。
Catskul、

12
彼が「平等」を望んでいることを考えると、「バランス」のあるものはすべて間違った答えであると想定するのは安全に思われます。
Mooing Duck 2013

11
「これは、他のライブラリヘッダーに含まれている内部ヘッダーファイルです。直接使用しないでください。」
ダン

3
@Dan:コピーしても、直接使用することにはなりません。
einpoklum

12

std :: mapは赤黒木に基づいています。他のコンテナーを使用して、独自のタイプのツリーを実装することもできます。


13
通常は赤黒木を使用します(そうする必要はありません)。
マーティンヨーク

1
GCCはツリーを使用してマップを実装します。マイクロソフトが使用しているものを確認するために、VCのインクルードディレクトリを確認したい人はいますか?
JJ

//赤黒ツリークラス。STL//連想コンテナ(セット、マルチセット、マップ、マルチマップ)の実装で使用するために設計されています。stl_tree.hファイルからそれを取得しました。
JJ

@JJ少なくともStudio 2010ではordered red-black tree of {key, mapped} values, unique keys、で定義された内部クラスを使用し<xtree>ます。現在、最新バージョンにアクセスできません。
ジャスティン時間-2016年

8

ある意味では、std :: mapはツリーです(バランスの取れたバイナリツリーと同じパフォーマンス特性を持つ必要があります)が、他のツリー機能は公開していません。実際のツリーデータ構造を含めない理由として考えられるのは、おそらくstlにすべてを含めないという問題でした。stlは、独自のアルゴリズムとデータ構造の実装に使用するフレームワークのように見えます。

一般に、必要な基本的なライブラリ機能があり、それがstlにない場合、修正はBOOSTを調べることです。

そうでない場合、ありますたくさんライブラリが 出て そこにあなたの木のニーズに応じて、。


6

すべてのSTLコンテナは、外部的には1つの反復メカニズムを持つ「シーケンス」として表されます。木はこのイディオムに従っていません。


7
ツリーデータ構造は、イテレータを介して、前順、順順、または後順走査を提供できます。実際、これはstd :: mapが行うことです。
Andrew Tomazos 2012

3
はい、いいえ...それはあなたが「木」によって何を意味するかに依存します。std::map内部的にはbtreeとして実装されていますが、外部的にはペアのソートされたシーケンスとして表示されます。誰が前に、誰が後かを普遍的に尋ねることができる要素が与えられた場合。それぞれが他の要素を含む要素を含む一般的なツリー構造は、並べ替えや方向付けを課しません。さまざまな方法でツリー構造をウォークするイテレータを定義できます(sallow | deep first | last ...)が、一度実行すると、std::treeコンテナはbegin関数からそれらの1つを返す必要があります。そして、どちらかを返す明確な理由はありません。
エミリオガラヴァリア2012

4
std :: mapは通常、Bツリーではなく、バランスのとれた二分探索木で表されます。std :: unordered_setにも同じ引数を適用できます。自然な順序はありませんが、開始反復子と終了反復子が存在します。beginとendの要件は、すべての要素を決定論的な順序で繰り返すことであり、自然な要素が存在する必要があるということではありません。preorderは、ツリーの完全に有効な反復順序です。
Andrew Tomazos 2012

4
あなたの答えの意味は、それが「シーケンス」インターフェースを持っていないので、stl n-treeデータ構造がないということです。これは単に正しくありません。
Andrew Tomazos 2012

3
@EmiloGaravaglia:によって証明されるようにstd::unordered_set、これはそのメンバーを反復する「ユニークな方法」を持っていません(実際、反復順序は疑似ランダムで実装が定義されています)がまだstlコンテナーです-これはあなたの主張を反証します。順序が定義されていなくても、コンテナ内の各要素を反復処理することは、依然として便利な操作です。
Andrew Tomazos 2012

4

STLは「すべて」のライブラリではないためです。本質的に、物を造るために必要な最小限の構造が含まれています。


13
バイナリツリーは非常に基本的な機能であり、std :: map、std :: multimap、stl :: setなどのタイプであるため、実際には他のコンテナよりも基本的です。これらの型はそれらに基づいているため、基になる型が公開されることを期待します。
Catskul、

2
OPがバイナリツリーを要求しているとは思わない、彼は階層を格納するためにツリーを要求している。
Mooing Duck 2013

それだけでなく、ツリーの「コンテナー」をSTLに追加すると、たとえばツリーナビゲーター(一般化イテレーター)など、多くの新しい概念が追加されます。
alfC 2016

5
「物を構築するための最小構造」は非常に主観的な発言です。生のC ++の概念で物事を構築できるため、真の最小値はSTLがまったくないだろうと思います。
DOC


3

IMO、脱落。しかし、ツリー構造をSTLに含めないことには十分な理由があると思います。ツリーの維持には多くのロジックがあり、メンバー関数TreeNodeとしてベースオブジェクトに書き込むのが最適です。ときTreeNodeSTLヘッダーに包まれている、それだけでメシエなります。

例えば:

template <typename T>
struct TreeNode
{
  T* DATA ; // data of type T to be stored at this TreeNode

  vector< TreeNode<T>* > children ;

  // insertion logic for if an insert is asked of me.
  // may append to children, or may pass off to one of the child nodes
  void insert( T* newData ) ;

} ;

template <typename T>
struct Tree
{
  TreeNode<T>* root;

  // TREE LEVEL functions
  void clear() { delete root ; root=0; }

  void insert( T* data ) { if(root)root->insert(data); } 
} ;

7
これは、所有している多くの生のポインタであり、その多くはポインタである必要はまったくありません。
Mooing Duck 2013

この回答を撤回することをお勧めします。TreeNodeクラスは、ツリー実装の一部です。
einpoklum 2016

3

STLツリーがない理由はいくつかあると思います。主にツリーは、コンテナ(リスト、ベクトル、セット)のように、非常に異なる細かい構造を持つ再帰的なデータ構造の形式であり、正しい選択を難しくしています。また、STLを使用して基本的な形式で構築することも非常に簡単です。

有限ルートツリーは、値またはペイロードを持つコンテナ、たとえばクラスAのインスタンス、およびルート(サブ)ツリーのおそらく空のコレクションと考えることができます。サブツリーのコレクションが空のツリーは、葉と見なされます。

template<class A>
struct unordered_tree : std::set<unordered_tree>, A
{};

template<class A>
struct b_tree : std::vector<b_tree>, A
{};

template<class A>
struct planar_tree : std::list<planar_tree>, A
{};

イテレータの設計などについて少し考える必要があります。また、ツリー間での定義と効率化を可能にする製品と連産品の操作-元のSTLを適切に記述する必要があるため、空のセット、ベクター、またはリストコンテナーはデフォルトの場合、ペイロードは本当に空です。

木は多くの数学的構造で重要な役割を果たします(ブッチャー、グロスマン、ラーセンの古典的な論文を参照してください。それらを結合できる例と、それらを列挙するためにどのように使用するかについては、コネとクリーマーの論文も参照してください)。彼らの役割が単に他の特定の業務を促進することであると考えるのは正しくありません。むしろ、データ構造としての基本的な役割のため、これらのタスクを容易にします。

ただし、木の他に「共木」もあります。上記のツリーには、ルートを削除するとすべてを削除するというプロパティがあります。

ツリー上のイテレータについて考えてみましょう。おそらくイテレータの単純なスタックとして、ノードとその親、ルートまで実現されます。

template<class TREE>
struct node_iterator : std::stack<TREE::iterator>{
operator*() {return *back();}
...};

ただし、必要な数だけ作成できます。集合的にそれらは「ツリー」を形成しますが、すべての矢印がルートに向かう方向に流れる場合、この共ツリーはイテレータを介して自明なイテレータとルートに向かって反復できます。ただし、すべてのインスタンスを追跡することを除いて、他のイテレータを認識したり、前後に移動したりすることはできません。

ツリーは信じられないほど有用であり、それらは多くの構造を持っているので、これは決定的に正しいアプローチを得ることを深刻な課題にします。私の見解では、これがSTLに実装されていない理由です。さらに、過去に、人々が宗教的になり、独自のタイプのインスタンスを含むコンテナのタイプのアイデアに挑戦するのを見たことがあります-しかし、彼らはそれに直面しなければなりません-それはツリータイプが表すものです-それはを含むノードです(小さい)木の空のコレクション。現在の言語では、デフォルトのコンストラクターcontainer<B>がヒープ(またはその他の場所)にスペースを割り当てないため、問題なくそれを許可していますB

これが、良い形で、標準への道を見つけたなら、私は喜んでいるでしょう。


0

ここで答えを読んでいる一般的な名前付きの理由は、ツリーを反復できないか、ツリーが他のSTLコンテナーへの同様のインターフェースを想定しておらず、そのようなツリー構造でSTLアルゴリズムを使用できないことです。

それを念頭に置いて、STLのようなインターフェイスを提供し、既存のSTLアルゴリズムで可能な限り使用できる独自のツリーデータ構造を設計しようとしました。

私の考えは、ツリーは既存のSTLコンテナーに基づく必要があり、コンテナーを非表示にしてはならず、STLアルゴリズムで使用できるようにするためです。

ツリーが提供しなければならないもう1つの重要な機能は、走査反復子です。

これが私が思いついたものです:https//github.com/igagis/utki/blob/master/src/utki/tree.hpp

そしてここにテストがあります:https : //github.com/igagis/utki/blob/master/tests/tree/tests.cpp


-9

すべてのSTLコンテナーはイテレーターで使用できます。イテレータやツリーを作成することはできません。これは、ツリーを '' 1つ正しい ''方法で実行できないためです。


3
しかし、BFSまたはDFSが正しい方法であると言えます。またはそれらの両方をサポートします。またはあなたが想像できる他のどんなものでも。それが何であるかをユーザーに伝えます。
tomas789 2013年

2
std :: mapにはツリー反復子があります。
Jai

1
ツリーは、すべてのノードを1つの「極端」から別の順序でトラバースする独自のカスタムイテレータタイプを定義できます(つまり、パス0および1のバイナリツリーの場合、「すべて0」から「すべて」に至るイテレータを提供できます。 1「その反対を行う逆イテレータ3の深さと開始ノードとツリーのためにs、例えば、それはノードを反復可能性としてs000s00s001s0s010s01s011ss100s10s101s1s110s11s111(」左端) 『右端」に』;それはまた(奥行きトラバーサルパターンを使用することができss0s1s00s01s10s11
ジャスティン・タイム-モニカを復活させる

など)、または他のパターン。ただし、各ノードが1回だけ渡されるようにすべてのノードを反復処理する限り。
ジャスティンタイム-モニカ

1
@doc、非常に良い点。std::unordered_set任意の方法(内部的にハッシュ関数によって与えられる)以外の要素を反復するより良い方法がわからないので、私はシーケンスを「作った」と思います。私はそれがツリーの反対のケースだと思います:反復の指定unordered_setが不十分であり、理論的にはおそらく「ランダムに」以外に反復を定義する「方法」はありません。木の場合、多くの「良い」(ランダムではない)方法があります。しかし、再び、あなたのポイントは有効です。
alfC 2016
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.