O(logn)
、受け取りませんO(1)
が、値は常にソートされます。C ++ 11から(と思います)、ハッシュ関数を使用して実装されているunordered_map
とがありunordered_set
、それらは並べ替えられていませんが、ほとんどのクエリと操作はO(1)
(平均)で可能です
O(logn)
、受け取りませんO(1)
が、値は常にソートされます。C ++ 11から(と思います)、ハッシュ関数を使用して実装されているunordered_map
とがありunordered_set
、それらは並べ替えられていませんが、ほとんどのクエリと操作はO(1)
(平均)で可能です
回答:
おそらく最も一般的な2つの自己平衡ツリーアルゴリズムは、Red-BlackツリーとAVLツリーです。挿入/更新後にツリーのバランスを取るには、両方のアルゴリズムで回転の概念を使用して、ツリーのノードを回転させて再バランスを実行します。
どちらのアルゴリズムでも挿入/削除操作はO(log n)ですが、Red-Blackツリーの場合、リバランス回転はO(1)操作ですが、AVLではこれはO(log n)操作です。 Red-Blackツリーは、リバランス段階のこの側面でより効率的であり、それがより一般的に使用される考えられる理由の1つです。
赤黒ツリーは、JavaやMicrosoft .NET Frameworkの提供物を含むほとんどのコレクションライブラリで使用されます。
std::map
実装のリストを作成し、開発者を追跡して、彼らが決定を下すのに使用した基準を尋ねることです。これは推測にとどまります。
それは本当に使用方法に依存します。AVLツリーには通常、リバランスのローテーションが多く含まれています。したがって、アプリケーションに挿入操作と削除操作があまり多くないが、検索に重きが置かれている場合は、おそらくAVLツリーが適切な選択です。
std::map
ノードの挿入/削除の速度と検索の間で妥当なトレードオフが得られるため、Red-Blackツリーを使用します。
AVLツリーの最大の高さは1.44logn、RBツリーの最大は2lognです。AVLに要素を挿入すると、ツリーのある時点でリバランスが行われる可能性があります。リバランスにより挿入が完了します。新しい葉の挿入後、その葉の祖先の更新は、ルートまで、または2つのサブツリーの深さが等しい点まで実行する必要があります。k個のノードを更新する必要がある確率は1/3 ^ kです。リバランスはO(1)です。要素を削除すると、複数の再調整が必要になる場合があります(ツリーの深さの半分まで)。
RBツリーは、バイナリ検索ツリーとして表される4次のBツリーです。Bツリーの4つのノードは、同等のBSTの2つのレベルになります。最悪の場合、ツリーのすべてのノードは2ノードで、リーフまでの3ノードのチェーンが1つだけあります。その葉は根から2lognの距離にあります。
ルートから挿入ポイントに移動して、4ノードを2ノードに変更して、挿入によって葉が飽和しないようにする必要があります。挿入から戻って、これらのすべてのノードを分析して、それらが4ノードを正しく表すことを確認する必要があります。これは、ツリーを下っていくときにも実行できます。グローバルコストは同じになります。無料ランチはありません!ツリーからの要素の削除は同じ順序です。
これらのすべてのツリーでは、ノードが高さ、重量、色などの情報を運ぶ必要があります。このような追加情報が含まれないのはSplayツリーだけです。しかし、ほとんどの人はその構造の乱雑さのため、スプレーの木を恐れています!
最後に、ツリーはノードで重み情報を運ぶこともでき、重みのバランスをとることができます。さまざまなスキームを適用できます。サブツリーに他のサブツリーの要素数の3倍を超える数が含まれている場合は、バランスを再調整する必要があります。リバランスは、1回転または2回転のいずれかで再度実行されます。これは、2.4lognの最悪のケースを意味します。3の代わりに2倍でより良い比率を得ることができますが、それは、サブツリーの1%弱をあちこちで不均衡にしておくことを意味する場合があります。トリッキー!
どのタイプの木が最適ですか?確かにAVL。これらはコーディングが最も簡単で、最悪の高さはlognに最も近い。1000000要素のツリーの場合、AVLは高さの最大で29、RB 40、および比率に応じて重みのバランスが36または50になります。
他にも多くの変数があります。ランダム性、追加、削除、検索の比率などです。
以前の回答はツリーの代替案のみに対応しており、赤黒はおそらく歴史的な理由のために残っています。
なぜハッシュテーブルではないのですか?
タイプでは、<
演算子(比較)をツリーのキーとして使用するだけで済みます。ただし、ハッシュテーブルでは、各キータイプにhash
関数が定義されている必要があります。型の要件を最小限に抑えることは、一般的なプログラミングにとって非常に重要であり、さまざまな型やアルゴリズムで使用できるようになります。
優れたハッシュテーブルを設計するには、使用するコンテキストの詳細な知識が必要です。オープンアドレッシングまたはリンクチェーンを使用する必要がありますか?サイズを変更する前に、どのレベルの負荷を受け入れる必要がありますか?衝突を回避する高価なハッシュを使用するべきですか、それともラフで高速なハッシュを使用するべきですか?
STLはアプリケーションに最適な選択肢を予測できないため、デフォルトはより柔軟である必要があります。木は「ただ動く」だけで、うまくスケーリングします。
(C ++ 11には、とのハッシュテーブルを追加しましたunordered_map
。あなたが見ることができるからドキュメント、それはこれらのオプションの多くのconfigureにポリシーを設定する必要があります。)
他の木はどうですか?
赤黒木は高速検索を提供し、BSTとは異なり、自己バランスをとります。別のユーザーは、自己バランスAVLツリーに比べてその利点を指摘しました。
アレクサンダーステパノフ(STLの作成者)はstd::map
、最新のメモリキャッシュに対してより友好的であるため、もう一度書いた場合、Red-Blackツリーの代わりにB *ツリーを使用すると述べました。
それ以降の最大の変更点の1つは、キャッシュの増加です。キャッシュミスは非常にコストがかかるため、参照の局所性は現在、はるかに重要です。参照の局所性が低いノードベースのデータ構造は、あまり意味がありません。今日STLを設計していたとしたら、別のコンテナーのセットができます。たとえば、メモリ内のB *ツリーは、連想コンテナを実装するための赤黒ツリーよりもはるかに優れた選択肢です。- アレクサンドル・ステパノフ
マップは常に木を使用する必要がありますか?
別の可能なマップ実装は、ソートされたベクトル(挿入ソート)とバイナリ検索です。これは、頻繁には変更されないが頻繁に照会されるコンテナに適しています。私はしばしばこれをCでqsort
行いbsearch
、組み込まれています。
マップを使用する必要さえありますか?
キャッシュに関する考慮事項は、学校で教えられた状況(リストの中央から要素を削除するなど)であっても、それを使用することstd::list
やそれをstd::deque
超えることはほとんど意味がないことを意味しますstd:vector
。同じ理由で、forループを使用してリストを線形検索する方が、いくつかのルックアップ用のマップを作成するよりも効率的でクリーンです。
もちろん、読み取り可能なコンテナを選択することは、通常、パフォーマンスよりも重要です。
2017-06-14の更新:コメントを書き込んだ後、webbertigerが回答を編集します。私はその答えが私の目にははるかに良くなっていることを指摘しておきます。しかし、私は追加情報として私の答えを保持しました...
最初の答えは間違っている(訂正:両方ではない)と思い、3番目の答えは誤った肯定を持っているためです。はっきりさせないといけないと思います...
最も人気のある2つのツリーは、AVLとRed Black(RB)です。主な違いは使用率です。
主な違いはカラーリングによるものです。カラーリングを使用すると、比較的高コストのリバランスアクションをスキップまたは短縮できる場合があるため、RBツリーではAVLよりもリバランスアクションが少なくなります。色付けのため、RBツリーはより高いレベルのノードも持っています。これは、黒いノード(最大2倍のレベルの可能性がある)の間の赤いノードを受け入れることができるため、検索(読み取り)が少し効率的になります...しかし、それは定数(2x)、O(log n)のままです。
ツリーの変更によるパフォーマンスヒット(重要)とツリーのコンサルテーションによるパフォーマンスヒット(ほとんど重要ではない)を考えると、一般的なケースではAVLよりもRBを優先するのが自然です。