web-dev-qa-db-ja.com

なぜPythonはハッシュテーブルを使用してdictを実装しますが、Red-Black Treeを実装しないのですか?

なぜPythonはハッシュテーブルを使用してdictを実装しますが、Red-Black Treeを実装しないのですか?

鍵は何ですか?パフォーマンス?

11
longdeqidao

これは、Python固有ではない一般的な回答です。

アルゴリズムの複雑さの比較

_       | Hash Table  |   Red-Black Tree    |
-------+-------------+---------------------+
Space  | O(n) : O(n) | O(n)     : O(n)     |
Insert | O(1) : O(n) | O(log n) : O(log n) |
Fetch  | O(1) : O(n) | O(log n) : O(log n) |
Delete | O(1) : O(n) | O(log n) : O(log n) |
       | avg  :worst | average  : worst    |
_

ハッシュテーブルの問題は、ハッシュが衝突する可能性があることです。衝突を解決するためのさまざまなメカニズムがあります。オープンアドレス指定または個別のチェーン。絶対的に最悪のケースは、すべてのキーが同じハッシュコードを持つことです。その場合、ハッシュテーブルはリンクリストに分解されます。

それ以外の場合はすべて、ハッシュテーブルは実装が簡単で優れたパフォーマンスを提供する優れたデータ構造です。欠点は、テーブルをすばやく拡大してエントリを再配布できる実装では、実際に使用されているメモリとほぼ同じ量のメモリが無駄になる可能性があることです。

RBツリーは自己バランスであり、最悪の場合でもアルゴリズムの複雑さは変わりません。ただし、実装はより困難です。それらの平均的な複雑さは、ハッシュテーブルの複雑さよりも悪いです。

キーの制限

ハッシュテーブル内のすべてのキーは、ハッシュ可能であり、互いに同等であるために比較可能でなければなりません。これは文字列や整数の場合は特に簡単ですが、ユーザー定義型に拡張するのもかなり簡単です。 Javaなどの一部の言語では、これらのプロパティは定義により保証されています。

RBツリー内のキーは、全体の順序を持​​つ必要があります。各キーは他のキーと比較可能である必要があり、2つのキーは小さい、大きい、または等しいのいずれかである必要があります。この順序の等価性は、意味的等価性と同等でなければなりません。これは整数やその他の数値の場合は簡単で、文字列の場合もかなり簡単です(順序が一貫している必要があり、外部から観察できないため、順序はロケールを考慮する必要がありません。[1])、ただし、固有の順序を持​​たない他のタイプでは困難です。それらの間のいくつかの比較が可能でない限り、異なるタイプのキーを持つことは絶対に不可能です。

[1]:実際、私はここで間違っています。 2つの文字列はバイトが等しくない場合がありますが、一部の言語の規則によれば同等です。たとえば、 2つの等しい文字列が異なる方法でエンコードされる1つの例のUnicode正規化。ユニコード文字の構成がハッシュキーにとって重要かどうかは、ハッシュテーブルの実装では認識できません。

RBツリーキーの安価なソリューションは、最初に等価性をテストしてから、同一性を比較する(つまり、ポインターを比較する)ことだと考えるかもしれません。ただし、この順序は推移的ではありません。_a == b_およびid(a) > id(c)の場合、id(b) > id(c)にも従う必要がありますが、ここでは保証されません。したがって、代わりに、キーのハッシュコードをルックアップキーとして使用できます。ここでは、順序付けは正しく機能しますが、RBツリーの同じノードに割り当てられる、同じハッシュコードを持つ複数の異なるキーになる可能性があります。これらのハッシュの衝突を解決するために、ハッシュテーブルの場合と同様に個別のチェーンを使用できますが、これはハッシュテーブルの最悪の場合の動作(両方の世界で最悪の場合)も継承します。

その他の側面

  • ハッシュテーブルは本質的に単なる配列なので、ハッシュテーブルはツリーよりもメモリの局所性が高いと期待しています。

  • 両方のデータ構造のエントリには、かなり高いオーバーヘッドがあります。

    • ハッシュテーブル:個別のチェーンの場合のキー、値、および次のエントリポインター。また、ハッシュコードを保存すると、サイズ変更を高速化できます。
    • RBツリー:キー、値、色、左の子ポインター、右の子ポインター。色は1ビットですが、位置合わせの問題は、ほぼすべてのポインター、または2の累乗のサイズのメモリブロックしか割り当てられない場合は、ほぼ4つのポインターのために十分なスペースを浪費していることを意味する可能性があることに注意してください。いずれの場合でも、RBツリーエントリは、ハッシュテーブルエントリよりも多くのメモリを消費します。
  • RBツリーでの挿入と削除には、ツリーのローテーションが含まれます。これらは実際には高価ではありませんが、オーバーヘッドが伴います。ハッシュでは、挿入と削除は単純なアクセスよりもコストがかかりません(ただし、挿入時にハッシュテーブルのサイズを変更することはO(n)の試みです)。

  • ハッシュテーブルは本質的に変更可能ですが、RBツリーは不変の方法で実装することもできます。ただし、これが役立つことはほとんどありません。

16
amon

mightが真である理由はさまざまですが、主な理由は次のとおりです。

  • ハッシュテーブルはツリーよりも実装が簡単です。どちらも完全に取るに足らないことではありませんが、ハッシュテーブルは少し簡単です。ハッシュ関数と等式関数が必要なだけなので、正当なキーのドメインへの影響はそれほど厳しくありません。木は全順序関数を必要とし、それを書くのはずっと難しいです。
  • ハッシュテーブルは、小さいサイズでパフォーマンスが向上する可能性があります。作業の大部分は理論的には大規模なデータセットのみを扱うため、これは非常に重要です。実際には、多くの場合、数百万ではなく数十または数百のキーでのみ機能します。小規模なパフォーマンスは非常に重要であり、漸近分析を使用して何が最善かを判断することはできません。実際に実装して測定する必要があります。

記述/保守がより簡単になり、一般的な使用例でパフォーマンスが向上しますか?サインアップしてください!

1
Donal Fellows