どの自己バランス型バイナリツリーをお勧めしますか?


18

私はHaskellを学び、演習としてバイナリツリーを作成しています。通常の二分木を作成したので、それを自己均衡化に適応させたいと思います。そう:

  • どちらが最も効率的ですか?
  • どちらが最も簡単に実装できますか?
  • 最もよく使用されるのはどれですか?

しかし、決定的に、どちらをお勧めしますか?

これは議論の余地があるため、ここに属していると思います。


効率と実装の容易さの観点から、一般的な効率は明確に定義されていますが、実装については、できる限り多く実装し、どちらが最適かをお知らせください...
glenatron

回答:


15

私はあなたがいずれかで始まる推薦する赤黒木、またはAVL木

赤黒ツリーは挿入が高速ですが、AVLツリーには検索用のわずかなエッジがあります。AVLツリーの実装はおそらく少し簡単ですが、私自身の経験に基づいたものではありません。

AVLツリーは、挿入または削除のたびにツリーのバランスを取ります(1 / -1より大きいバランス係数を持つサブツリーはありませんが、赤黒ツリーは、ツリーがいつでも合理的にバランスを取ることを保証します。


1
個人的には、赤黒インサートはAVLインサートよりも簡単だと思います。その理由は、Bツリーとの(不完全な)類推によるものです。挿入は面倒ですが、削除は悪です(考慮すべきケースが非常に多い)。実際、私自身のC ++赤黒削除実装はもうありません-(1)使用したことがないことに気付いたときに削除しました-削除するたびに複数のアイテムを削除していたので、ツリーからリストから削除し、リストから削除してからツリーに戻します。(2)とにかく壊れました。
Steve314

2
@ Steve314、赤黒木は簡単ですが、機能する実装を作成できていませんか?そのときのAVLツリーとは何ですか?
dan_waterworth

@dan_waterworth-まだ機能する挿入メソッドさえ実装していません-メモを持ち、基本原理を理解しましたが、動機、時間、自信の正しい組み合わせがありませんでした。単に動作するバージョンが必要な場合、それは単なる教科書からの擬似コードの翻訳(およびC ++に標準のライブラリコンテナがあることを忘れないでください)ですが、その面白さはどこにありますか?
Steve314

ところで-かなり人気のある教科書には、バランスのとれたバイナリツリーアルゴリズムの1つのバグのある実装が含まれていると思います(ただし、参照を提供することはできません)。だからそれは私だけではありません;-)
Steve314

1
@ Steve314は、命令型言語では木が非常に複雑になる可能性があることを知っていますが、驚くべきことに、Haskellでの木を実装するのは簡単です。週末に通常のAVLツリーと1D空間バリアントを作成しましたが、どちらも約60行です。
dan_waterworth

10

ランダム化されたデータ構造に問題がない場合は、別の方法を検討します:Skip Lists

高レベルの観点から見ると、ツリー構造ですが、ツリーとしてではなく、複数のリンク層を持つリストとして実装されています。

O(log N)の挿入/検索/削除を取得し、これらのトリッキーなリバランスのケースすべてに対処する必要はありません。

私はそれらを関数型言語で実装することを考えたことはありません、そしてウィキペディアのページには何も表示されないので、それは簡単ではないかもしれません


関数型言語ではありませんが、スキップリストを本当に楽しんでいます。私はこの後にそれらを試みると思いますが、今は自己バランスの取れた木にいます。
dan_waterworth

また、多くの場合、並行データ構造にスキップリストを使用します。不変を強制する代わりに、haskellの並行処理プリミティブ(MVarやTVarなど)を使用する方が良い場合があります。ただし、これは機能的なコードの記述についてはあまり教えません。
dan_waterworth

2
@ Fanatic23、スキップリストはADTではありません。ADTは、セットまたは連想配列のいずれかです。
dan_waterworth

@dan_waterworth私の悪い、あなたは正しい。
-Fanatic23

5

比較的簡単な構造(AVLツリーと赤黒ツリーの両方が厄介です)から始める場合、1つのオプションは「ツリー」と「ヒープ」の組み合わせとして名前が付けられたトレープです。

各ノードは「優先度」の値を取得します。多くの場合、ノードの作成時にランダムに割り当てられます。ノードは、キーの順序が尊重されるように、また優先順位値のヒープのような順序が尊重されるように、ツリーに配置されます。ヒープのような順序とは、親の両方の子が親よりも優先順位が低いことを意味します。

EDITは 上記の「キー値内」を削除しました-優先順位とキーの順序は一緒に適用されるため、一意のキーであっても優先順位は重要です。

面白い組み合わせです。キーが一意であり、優先度が一意である場合、ノードのセットには一意のツリー構造があります。それでも、挿入と削除は効率的です。厳密に言えば、ツリーは事実上リンクリストになるまで不均衡になる可能性がありますが、これは(標準バイナリツリーとは異なり)キーが順番に挿入されるなどの通常の場合を含め(標準バイナリツリーとは異なり)非常にまれです。


1
+1。Treapsは私の個人的な選択であり、それらがどのように実装されているかについてのブログ投稿書いています。
P Shved

5

どちらが最も効率的ですか?

あいまいで答えるのが難しい。計算の複雑さはすべて明確に定義されています。それが効率という意味であれば、本当の議論はありません。実際、すべての優れたアルゴリズムには、証明と複雑さの要因が付属しています。

「ランタイム」または「メモリ使用」を意味する場合、実際の実装を比較する必要があります。次に、言語、ランタイム、OS、およびその他の要因が作用し、質問への回答が難しくなります。

どちらが最も簡単に実装できますか?

あいまいで答えるのが難しい。一部のアルゴリズムは複雑に見えるかもしれませんが、私には些細なことです。

最もよく使用されるのはどれですか?

あいまいで答えるのが難しい。最初に「誰によって」があります これの一部?Haskellのみ?CまたはC ++はどうですか?第二に、調査を行うためにソースにアクセスできない独自のソフトウェアの問題があります。

しかし、決定的に、どちらをお勧めしますか?

これは議論の余地があるため、ここに属していると思います。

正しい。あなたの他の基準はあまり役に立たないので、これがあなたが得ようとしているすべてです。

多数のツリーアルゴリズムのソースを取得できます。何かを学びたい場合は、見つけることができるものをすべて実装するだけです。「推奨」を求めるのではなく、見つけることができるすべてのアルゴリズムを収集してください。

リストは次のとおりです。

http://en.wikipedia.org/wiki/Self-balancing_binary_search_tree

6つの一般的な定義があります。それらから始めます。


3

スプレイツリーに興味がある場合は、アレンとマンローの論文で最初に説明されたと思われるものよりも簡単なバージョンがあります。同じパフォーマンスの保証はありませんが、「zig-zig」と「zig-zag」のリバランスを処理する際の複雑さを回避します。

基本的に、検索時(削除する挿入ポイントまたはノードの検索を含む)、検索したノードは、ルートに向かって、ボトムアップで直接回転します(たとえば、再帰的検索関数が終了するとき)。各ステップで、ルートに向かって別のステップをプルアップする子が右の子であるか左の子であるかに応じて、単一の左または右の回転を選択します(回転方向を正しく覚えていれば、それぞれです)。

Splayツリーのように、最近アクセスしたアイテムは常にツリーのルートの近くにあるため、すぐに再びアクセスできるという考え方です。これらのAllen-Munroeのroot-to-rootツリー(私がそれらを呼ぶもの-正式名称は知らない)はより簡単になりますが、同じ償却パフォーマンス保証はありません。

一つのこと-このデータ構造は定義上、検索操作に対しても変化するため、おそらく一元的に実装する必要があります。IOWは、関数型プログラミングには向いていないかもしれません。


スプレイは、見つけるときでもツリーを変更するため、少し面倒です。これは、マルチスレッド環境ではかなり苦痛です。そもそもHaskellのような関数型言語を使用する大きな動機の1つです。繰り返しになりますが、私は関数型言語を使用したことがないので、おそらくこれは要因ではありません。
クイックジョースミス

@Quick-ツリーの使用方法によって異なります。真の機能スタイルコードで使用している場合は、すべての検索で変異を削除するか(Splayツリーを少し馬鹿にする)、各検索でバイナリツリーのかなりの部分を複製することになります。作業の進行に応じて作業しているツリーの状態を追跡します(おそらくモナドスタイルを使用する理由)。新しいコピーが作成された後、古いツリーの状態を参照しなくなった場合、コンパイラによってそのコピーは最適化されます(関数型プログラミングでは同様の仮定が一般的です)。
Steve314

どちらのアプローチも努力する価値はありません。繰り返しますが、ほとんどの場合、純粋に関数型の言語は使用しません。
クイックジョースミス

1
@Quick-ツリーの複製は、挿入などのアルゴリズムを変更するための純粋な関数型言語のツリーデータ構造に対して行います。ソース用語では、コードはインプレース更新を行う命令型コードとそれほど違いはありません。おそらく、不均衡な二分木の場合、違いはすでに処理されています。ノードに親リンクを追加しようとしない限り、重複は最低限共通のサブツリーを共有し、Haskellの深い最適化は完全ではないにしてもかなりハードコアです。私は原則として自分自身に反ハスケルですが、これは必ずしも問題ではありません。
Steve314

2

非常に単純なバランスの取れたツリーは、AAツリーです。不変式は単純であるため、実装が簡単です。そのシンプルさのために、そのパフォーマンスは依然として良好です。

高度な演習として、GADTを使用して、タイプシステムタイプによって不変条件が適用されるバランスツリーのバリアントの1つを実装することができます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.