web-dev-qa-db-ja.com

C ++でのSTLセットの基本的なデータ構造は何ですか?

セットがC++でどのように実装されているか知りたいのですが。 STL提供のコンテナーを使用せずに独自のセットコンテナーを実装する場合、このタスクを実行するにはどの方法が最適ですか?

STLセットがバイナリ検索ツリーの抽象的なデータ構造に基づいていることを理解しています。では、基礎となるデータ構造は何ですか?配列?

また、insert()はセットでどのように機能しますか?セットは、要素がすでにその中に存在するかどうかをどのようにチェックしますか?

私はウィキペディアで、セットを実装する別の方法はハッシュテーブルを使用することだと読んだ。これはどのように機能しますか?

46
zebraman

最初にNode構造体を定義することにより、バイナリ検索ツリーを実装できます。

struct Node
{
  void *nodeData;
  Node *leftChild;
  Node *rightChild;
}

次に、別のNode *rootNode;を使用してツリーのルートを定義できます

Binary Search Tree のWikipediaエントリには、挿入メソッドを実装する方法のかなり良い例があるので、これもチェックすることをお勧めします。

重複に関しては、通常、セットでは許可されないため、仕様に応じて、その入力を破棄したり、例外をスローしたりすることができます。

13
Raul Agrait

KTCが言ったように、どのようにstd::set実装はさまざまです。C++標準では、抽象データ型を指定するだけです。言い換えると、標準はコンテナの実装方法を指定せず、サポートするために必要な操作のみを指定します。ただし、STLのほとんどの実装では、私の知る限り、 red-black trees またはその他の種類のバランスのとれたバイナリ検索ツリーを使用します(たとえば、GNU libstdc ++は、赤黒ツリーを使用します)。 。

理論的には、セットをハッシュテーブルとして実装して、より速い漸近的なパフォーマンス(ルックアップと挿入の償却済みO(キー長)とO(log n)を比較)を得ることができますが、ユーザーが必要なタイプのハッシュ関数を指定する必要があります保存する(ハッシュテーブルの Wikipediaのエントリ を参照してください)。バイナリ検索ツリーの実装に関しては、配列を使用したくないでしょう-Raulが述べたように、ある種のNodeデータ構造が必要でしょう。

23
Toli

_g++_へのステップデバッグ__ 6.4 stdlibc ++ソース

Ubuntuの16.04のデフォルトの_g++-6_パッケージまたは ソースからのGCC 6.4ビルド では、追加の設定なしでC++ライブラリにステップインできることをご存知ですか?

そうすることで、この実装では赤黒木が使用されていると簡単に結論付けられます。

_std::set_を順番にたどることができるため、これは理にかなっています。これは、ハッシュマップが使用されている場合には効率的ではありません。

main.cpp

_#include <cassert>
#include <set>

int main() {
    std::set<int> s;
    s.insert(1);
    s.insert(2);
    assert(s.find(1) != s.end());
    assert(s.find(2) != s.end());
    assert(s.find(3) == s3.end());
}
_

コンパイルとデバッグ:

_g++ -g -std=c++11 -O0 -o main.out main.cpp
gdb -ex 'start' -q --args main.out
_

ここで、s.insert(1)にステップインすると、すぐに_/usr/include/c++/6/bits/stl_set.h_に到達します。

_487 #if __cplusplus >= 201103L
488       std::pair<iterator, bool>
489       insert(value_type&& __x)
490       {
491     std::pair<typename _Rep_type::iterator, bool> __p =
492       _M_t._M_insert_unique(std::move(__x));
493     return std::pair<iterator, bool>(__p.first, __p.second);
494       }
495 #endif
_

これは明らかに__M_t._M_insert_unique_に転送するだけです。

したがって、vimでソースファイルを開き、__M_t_の定義を見つけます。

_      typedef _Rb_tree<key_type, value_type, _Identity<value_type>,
           key_compare, _Key_alloc_type> _Rep_type;
       _Rep_type _M_t;  // Red-black tree representing set.
_

したがって、__M_t_は__Rep_type_タイプであり、__Rep_type_は__Rb_tree_です。

OK、これで私には十分な証拠となりました。 __Rb_tree_が黒赤ツリーであると思わない場合は、少し先に進んでアルゴリズムを読んでください。

_unordered_set_はハッシュテーブルを使用します

同じ手順ですが、コードのsetを_unordered_set_に置き換えます。

_std::unordered_set_を順番にたどることができないため、これは理にかなっています。ハッシュマップの方が償却挿入時間が複雑になるため、標準ライブラリは赤黒木ではなくハッシュマップを選択しました。

insertにステップインすると、_/usr/include/c++/6/bits/unordered_set.h_につながります。

_415       std::pair<iterator, bool>
416       insert(value_type&& __x)
417       { return _M_h.insert(std::move(__x)); }
_

したがって、vimでソースファイルを開き、__M_h_を検索します。

_      typedef __uset_hashtable<_Value, _Hash, _Pred, _Alloc>  _Hashtable;
      _Hashtable _M_h;
_

だからハッシュテーブルです。

_std::map_および_std::unordered_map_

_std::set_と_std:unordered_set_の類似: C++のstd :: map内にはどのデータ構造がありますか?

パフォーマンス特性

タイミングをとることによって、使用されるデータ構造を推測することもできます。

enter image description here

グラフ生成手順とヒープとBSTの分析、および ヒープとバイナリ検索ツリー(BST)

私たちは明確に見ていきます:

  • _std::set_、対数挿入時間
  • _std::unordered_set_、より複雑なパターンのハッシュマップパターン:

    • ズームされていないプロットでは、線形に増加するスパイクから巨大な配列に2倍になるバッキング動的配列がはっきりと見えます
    • ズームされたプロットでは、時間は基本的に一定で250nsに向かっていることがわかります。したがって、非常に小さいマップサイズを除いて、_std::map_よりもはるかに高速です。

      いくつかのストリップがはっきりと見え、アレイが2倍になるたびに、その傾きは小さくなります。

      これは、各ビンでリンクリストのウォークが平均的に直線的に増加しているためだと思います。次に、配列が2倍になると、ビンの数が増えるため、歩く時間が短くなります。

STLセットがバイナリ検索ツリーの抽象的なデータ構造に基づいていることを理解しています。では、基礎となるデータ構造は何ですか?配列?

他の人が指摘したように、それは異なります。セットは通常、ツリー(赤黒ツリー、バランスツリーなど)として実装されますが、他の実装が存在する場合もあります。

また、セットに対してinsert()はどのように機能しますか?

それはあなたのセットの基礎となる実装に依存します。バイナリツリーとして実装されている場合、 Wikipedia には、insert()関数のサンプル再帰実装があります。ぜひチェックしてみてください。

セットは、要素がすでに存在するかどうかをどのようにチェックしますか?

ツリーとして実装されている場合は、ツリーを走査して各要素をチェックします。ただし、セットでは重複する要素を保存できません。要素の重複を許可するセットが必要な場合は、マルチセットが必要です。

私はウィキペディアで、セットを実装する別の方法はハッシュテーブルを使用することだと読んだ。これはどのように機能しますか?

ハッシュテーブルを使用してセットが実装されているhash_setを参照している可能性があります。要素を格納する場所を知るために、ハッシュ関数を提供する必要があります。この実装は、要素をすばやく検索できるようにする場合に最適です。ただし、要素を特定の順序で格納することが重要な場合は、ツリーの実装がより適切です。プレオーダー、インオーダー、ポストオーダーでトラバースできるためです。

8
jasonline

特定のコンテナーがC++でどのように実装されるかは、完全に実装依存です。必要なのは、さまざまなメソッドの複雑さの要件、イテレータの要件など、標準で設定された要件を満たす結果を得るためだけです。

7
KTC

cppreferenceは言う

セットは通常赤黒木として実装されます。

チェックしたところ、libc++libstdc++はどちらもstd::setに赤黒木を使用しています。

std::unordered_setlibc++のハッシュテーブルを使用して実装されており、libstdc++も同じだと思いますが、チェックしませんでした。

編集:どうやら私の言葉は十分ではありません。

  • libc++12
  • libstdc++1
1
Timmmm