二分木のアプリケーションは何ですか?


323

二分木の特定のアプリケーションは何ですか。実際の例をいくつか挙げていただけますか?

回答:


425

バイナリツリーのパフォーマンスについて論じることは意味がありません。それらはデータ構造ではなく、すべて異なるパフォーマンス特性を持つデータ構造のファミリーです。不均衡な二分木は、検索のための自己均衡二分木よりもパフォーマンスがはるかに悪いことは事実ですが、「均衡」が意味を持たない多くの二分木(二分試行など)があります。

二分木の応用

  • バイナリ検索ツリー - 多くの言語のライブラリにあるオブジェクトやオブジェクトなど、データが絶えず出入りする多くの検索アプリケーションで使用されます。mapset
  • Binary Space Partition-ほとんどすべての3Dビデオゲームで、レンダリングする必要があるオブジェクトを決定するために使用されます。
  • Binary Tries-ほとんどすべての高帯域幅ルーターでルーターテーブルを格納するために使用されます。
  • ハッシュツリー -p2pプログラムおよびハッシュを検証する必要がある特殊な画像署名で使用されますが、ファイル全体は使用できません。
  • ヒープ -効率的な優先度キューの実装に使用されます。これは、多くのオペレーティングシステムのスケジューリングプロセス、ルーターのサービス品質、およびA * (ロボット工学やビデオゲームを含むAIアプリケーションで使用されるパス検出アルゴリズム)に使用されます。。ヒープソートでも使用されます。
  • ハフマンコーディングツリーChip Uni)-.jpegおよび.mp3ファイル形式で使用されるものなどの圧縮アルゴリズムで使用されます。
  • GGMツリー -暗号アプリケーションで使用され、疑似乱数のツリーを生成します。
  • 構文ツリー -コンパイラーと(暗黙的に)計算機によって構築され、式を解析します。
  • Treap-ワイヤレスネットワークとメモリ割り当てで使用されるランダム化されたデータ構造。
  • Tツリー -ほとんどのデータベースはドライブ上にデータを格納するために何らかのBツリーを使用しますが、すべて(ほとんど)のデータをメモリに保持するデータベースは、Tツリーを使用してデータを保存します。

二分木がn-aryツリーよりも頻繁に検索に使用される理由は、n-aryツリーはより複雑ですが、通常、実際の速度上の利点はありません。

mノードのある(バランスのとれた)バイナリツリーでは、1つのレベルから次のレベルに移動するには1つの比較が必要でありlog_2(m)log_2(m)比較の合計にはレベルがあります。

対照的に、n分木は次のレベルに移動するlog_2(n)ために(二分探索を使用した)比較が必要になります。あるのでlog_n(m)、合計レベルは、検索が必要になりますlog_2(n)*log_n(m)= log_2(m)比較は合計します。したがって、n-aryツリーはより複雑ですが、必要な合計比較の点で利点はありません。

(ただし、n-aryツリーはニッチな状況でも依然として有用です。すぐに頭に浮かぶ例は、クワッドツリーおよびその他のスペース分割ツリーです。レベルごとに2つのノードのみを使用してスペースを分割すると、ロジックが不必要に複雑になります。多くのデータベースで使用されるBツリー。制限要因は、各レベルで実行される比較の数ではなく、ハードドライブから一度にロードできるノードの数です。


3
> Treap-ワイヤレスネットワークとメモリ割り当てで使用されるランダム化されたデータ構造。それらは、メモリ割り当てとワイヤレスネットワークでどの程度正確に使用されますか?
2013年

1
「binary」という単語を利用する便利なデータ構造とアルゴリズムはたくさんありますが、「binary SEARCH tree」は実際にはその1つですが、質問されたのではありません。単純な古い「二分木」には、ソートされたもの、バランスのとれたもの、完全なものではなく、どのような用途がありますか。単純な古いランダムツリーですか?
マイケルエリクソン2014年

4
@MichaelEricksonあなたは、ええと、答えを読みましたか?それがまさに私が答えた質問だからです。
BlueRaja-Danny Pflughoeft 2014年

1
ハッシュツリーは、少なくともビットコインやイーサリアムのコミュニティ、IPFSなどでは、一般的にマークルツリーと呼ばれていると思います
Duke

1
残念ながら、この回答には多くの間違いが含まれています。最新のハードウェアのn進ツリーは、ほとんどの場合、バイナリツリーよりも望ましいです。名前が付けられたアプリケーションは、ほとんどバイナリツリーを使用しません。
Stephan Eggermont

290

ほとんどの人が二分木について話すとき、彼らは二分探索木について考えるよりも頻繁であるので、最初にそれをカバーします。

非平衡型の二分探索木は、データ構造について学生を教育することよりも実際に役立ちます。これは、データが比較的ランダムな順序で到着しない限り、単純なバイナリツリーのバランスが取れていないため、ツリーがリンクリストであるワーストケースの形式に簡単に縮退する可能性があるためです。

適切な事例として、操作や検索のためにデータをバイナリツリーに読み込むソフトウェアを修正する必要がありました。ソートされた形式でデータを書き出しました。

Alice
Bob
Chloe
David
Edwina
Frank

読み返すと、次のようなツリーになります。

  Alice
 /     \
=       Bob
       /   \
      =     Chloe
           /     \
          =       David
                 /     \
                =       Edwina
                       /      \
                      =        Frank
                              /     \
                             =       =

これは退化した形式です。そのツリーでフランクを探しに行く場合、彼を見つける前に6つすべてのノードを検索する必要があります。

二分木は、バランスをとると、検索に非常に役立ちます。これには、ルートノードを介してサブツリーを回転させて、2つのサブツリー間の高さの差が1以下になるようにします。バランスツリーに一度に1つ以上の名前を追加すると、次のシーケンスが得られます。

1.   Alice
    /     \
   =       =

 

2.   Alice
    /     \
   =       Bob
          /   \
         =     =

 

3.        Bob
        _/   \_
   Alice       Chloe
  /     \     /     \
 =       =   =       =

 

4.        Bob
        _/   \_
   Alice       Chloe
  /     \     /     \
 =       =   =       David
                    /     \
                   =       =

 

5.           Bob
        ____/   \____
   Alice             David
  /     \           /     \
 =       =     Chloe       Edwina
              /     \     /      \
             =       =   =        =

 

6.              Chloe
            ___/     \___
         Bob             Edwina
        /   \           /      \
   Alice     =      David        Frank
  /     \          /     \      /     \
 =       =        =       =    =       =

エントリが追加されると、サブツリー全体が左に回転して(ステップ3と6で)実際に見ることができます。これにより、退化フォームが与える)O(log N)ではなく、最悪の場合のルックアップが行われるバランスのとれたバイナリツリーがO(N得られます。最高のNULL(=)が最低のレベルと2レベル以上異なることはありません。そして、上記の最後のツリーでは、3つのノード(ChloeEdwinaそして最後にFrank)を見るだけでフランクを見つけることができます。

もちろん、二分木ではなく多方向ツリーのバランスをとると、さらに便利になります。つまり、各ノードは複数のアイテムを保持します(技術的には、NアイテムとN + 1ポインターを保持します。バイナリツリーは、1アイテムと2ポインターを持つ1ウェイマルチウェイツリーの特別なケースです)。

3方向ツリーでは、次のようになります。

  Alice Bob Chloe
 /     |   |     \
=      =   =      David Edwina Frank
                 /     |      |     \
                =      =      =      =

これは通常、アイテムのインデックスのキーを維持するために使用されます。ノードがディスクブロックのサイズ(たとえば512バイト)であるハードウェア用に最適化されたデータベースソフトウェアを作成し、単一のノードにできるだけ多くのキーを配置しました。この場合のポインタは、実際には、インデックスファイルとは別の固定長レコードの直接アクセスファイルへのレコード番号Xでした(したがって、を検索するだけでレコード番号を見つけることができますX * record_length)。

たとえば、ポインタが4バイトで、キーサイズが10の場合、512バイトノードのキーの数は36です。これは36キー(360バイト)と37ポインタ(148バイト)で、合計508バイトです。ノードごとに4バイトが無駄になります。

多方向キーを使用すると、2フェーズ検索(正しいノードを見つける多方向検索と小さなシーケンシャル(または線形バイナリ)検索を組み合わせてノード内の正しいキーを見つける)が複雑になりますが、これを補うよりも多くのディスクI / Oを実行します。

インメモリ構造でこれを行う理由はないと思います。バランスの取れたバイナリツリーを使い続け、コードをシンプルに保つ方が良いでしょう。

またの利点があることに注意してくださいO(log N)以上がO(N)あなたのデータセットが小さい場合、実際には表示されません。多方向ツリーを使用して15人をアドレス帳に保存している場合、多すぎるでしょう。過去10年間で10万人の顧客からのすべての注文のようなものを保管しているときに、利点が生まれます。

big-O表記の要点は、N無限に近づくにつれて何が起こるかを示すことです。一部の人々は同意しないかもしれませんが、データセットが特定のサイズ未満に留まることが確実であり、他に何も容易に利用できない限り、バブルソートを使用しても大丈夫です:-)


二分木の他の用途としては、次のような多くのものがあります。

  • 上位のキーが左側のキーではなく下位のキーと同じかそれよりも小さいバイナリヒープ。
  • ハッシュテーブルと同様のハッシュツリー。
  • コンピュータ言語のコンパイルのための抽象構文木。
  • データ圧縮用のハフマンツリー。
  • ネットワークトラフィックのルーティングツリー。

検索ツリーについて説明した説明の量を考えると、他のものについては詳しく説明するのは控えめですが、必要に応じて、それらを調査するには十分です。


28
+1そのような私たちが書いた答え; 加えて、これまでに出会ったことのない、バランスのとれた多方向ツリーを紹介します。
Tony

3
学生を教育すること以外には役に立たないというあなたの主張には同意しません。単純な静的データ構造としても、非常に便利です。ただし、これは非常によく書かれ、図解された答えなので、残りすべてに対して+1します。:-)
Benson

1
最新のハードウェアでは、ほぼすべての木が多方向である必要があります。
Stephan Eggermont、2011

89

組織モールス信号は、バイナリツリーです。

二分木

モールス信号


4
これが私のお気に入りの答えです。リストに沿ってさらに文字に到達するために必要な計算の複雑さの軽減を直接示しています。
口ひげ2018

7
私はこの答えを本当に楽しんだ!
ダンカンエドワーズ

2
私は以前、ハムラジオ用のフィルターを作った会社で働いていました。これは私を「元に戻す」のに
役立ち

62

二分木は、各ノードに最大2つの子ノードがあり、通常「左」と「右」として区別されるツリーデータ構造です。子を持つノードは親ノードであり、子ノードには親への参照が含まれる場合があります。ツリーの外には、「ルート」ノード(すべてのノードの祖先)が存在する場合、それへの参照があることがよくあります。データ構造のどのノードにも、ルートノードから開始して、左または右の子への参照を繰り返したどることで到達できます。バイナリツリーでは、すべてのノードの次数は最大2つです。

バイナリツリー

バイナリツリーは便利です。図からわかるように、ツリー内のノードを見つけたい場合は、最大6回見ればよいだけです。たとえば、ノード24を検索する場合は、ルートから始めます。

  • ルートの値は31であり、24より大きいため、左側のノードに移動します。
  • 左側のノードの値は15で24未満なので、右側のノードに移動します。
  • 右側のノードの値は23で24未満なので、右側のノードに移動します。
  • 右側のノードの値は27で24より大きいため、左側のノードに移動します。
  • 左側のノードの値は25で、24より大きいため、左側のノードに移動します。
  • ノードの値は24で、これが探しているキーです。

この検索を以下に示します。 ツリー検索

最初のパスでツリー全体のノードの半分を除外できることがわかります。2番目の左側のサブツリーの半分。これは非常に効果的な検索になります。これが40億個の要素に対して行われた場合、最大32回検索するだけで済みます。したがって、ツリーに含まれる要素が多いほど、検索の効率が上がります。

削除は複雑になる可能性があります。ノードに0または1の子がある場合は、いくつかのポインターを移動して、削除するポインターを除外するだけです。ただし、2つの子を持つノードを簡単に削除することはできません。だから私たちは近道をします。ノード19を削除したいとしましょう。

削除1

左と右のポインターをどこに移動するかを決定するのは簡単ではないので、それを置き換えるポインターを見つけます。左側のサブツリーに行き、できる限り右に行きます。これにより、削除するノードの次に大きい値が得られます。

削除3

次に、左と右のポインタを除くすべての18のコンテンツをコピーし、元の18ノードを削除します。

削除4


これらのイメージを作成するために、私はAVLツリー、セルフバランシングツリーを実装しました。これにより、ツリーはいつでも、リーフノード(子のないノード)間で最大で1レベルの違いを持つようになります。これにより、ツリーが歪むのを防ぎ、最大のO(log n)検索時間を維持しますが、挿入と削除に必要な時間が少し長くなります。

これは、私のAVLツリーが可能な限りコンパクトでバランスの取れた状態を維持する方法を示すサンプルです。

ここに画像の説明を入力してください

ソートされた配列ではO(log(n))、ツリーと同様に、ルックアップにはが必要ですが、ランダムな挿入と削除には、ツリーのの代わりにO(n)が必要O(log(n))です。一部のSTLコンテナーはこれらのパフォーマンス特性を有利に使用しているため、挿入と削除の時間は最大でO(log n)となり、非常に高速です。これらのコンテナのいくつかはmapmultimapset、とmultiset

AVLツリーのサンプルコードは、http://ideone.com/MheW8にあります。


5
O(log n)を検索する必要があるのは、バイナリ検索ツリー(それ自体がバランスが取れている)を処理している場合のみです。任意のバイナリツリーには順序の制約がなく、ランダムBSTの検索の複雑度はO(log h)です。
dlev、

それらは、関連する標準コンテナに保存されている種類ではありません。
パピー

12

主なアプリケーションは、バイナリ検索ツリーです。これらは、検索、挿入、および削除がすべて非常に高速なデータ構造です(log(n)操作について)。


1
二分探索木はアプリケーションではなく、特定のタイプの二分木です。
nbro 2016

@nbro:あなたは無意味なセマンティクスを主張していますが、どちらも同じことを言うのに有効な方法です。ここでの「アプリケーション」は「コンピュータアプリケーション」と同じ意味ではないことに注意してください
BlueRaja-Danny Pflughoeft

問題は、実際のアプリケーションに関連しており、特定の実装や特定の種類のバイナリツリーではなかったと思います。そしてところで、質問者はどのデータ構造が特定のバイナリツリーであるかを尋ねていません。それは無意味ではありません、IMO。しかし、いずれにしてもあいまいであることに同意します。たとえば、他の回答では、構文ツリーについて言及しています。これは、実際のアプリケーションでのツリー(ただし、必ずしもバイナリではない)データ構造のアプリケーションです。あなたの推論に基づいて、私が知っているすべての二分木を列挙することができました。アイテムの量のために私たちは皆幸せです。
nbro 2016


11

言及されていないバイナリツリーの興味深い例の1つは、再帰的に評価される数式の例です。基本的には実用的ではありませんが、そういう表現を考えるのも面白いですね。

基本的に、ツリーの各ノードには、それ自体に固有の値、またはその子の値を操作して再帰的に評価される値があります。

たとえば、式(1+3)*2は次のように表すことができます。

    *
   / \
  +   2
 / \
1   3

式を評価するために、親の値を要求します。このノードは、子、プラス演算子、および「2」のみを含むノードから値を取得します。次に、plus演算子は、値「1」と「3」を持つ子から値を取得して加算し、4を乗算ノードに返し、8を返します。

バイナリツリーのこの使用法は、操作が実行される順序が同じであるという意味で、ポリッシュ表記を逆にしたものと似ています。また、注意する必要があるのは、必ずしもバイナリツリーである必要はなく、最も一般的に使用される演算子がバイナリであるということだけです。最も基本的なレベルでは、ここのバイナリツリーは実際には非常に単純な純粋に関数型のプログラミング言語です。



9

「純粋な」二分木の使用法はないと思います。(教育目的を除く)黒木AVLツリーなどのバランスのとれた二分 O(logn)操作を保証するため、はるかに便利です。通常のバイナリツリーは最終的にリスト(またはほとんどリスト)になる可能性があり、多くのデータを使用するアプリケーションでは実際には役立ちません。

バランスツリーは、マップまたはセットの実装によく使用されます。また、O(nlogn)での並べ替えにも使用できます。これを行うには、より良い方法があります。

検索/挿入/削除にも ハッシュテーブルを使用こともできます。これは通常、バイナリサーチツリーよりもパフォーマンスが優れています(バランスが取れているかどうかにかかわらず)。

(バランスのとれた)バイナリ検索ツリーが役立つアプリケーションは、検索、挿入、削除、並べ替えが必要な場合です。ソートは、準備が整ったビルドバランスツリーを前提として、インプレース(ほとんどの場合、再帰に必要なスタックスペースを無視)にすることができます。それはまだO(nlogn)ですが、定数係数は小さく、余分なスペースは必要ありません(新しい配列を除き、データを配列に入れる必要があると想定しています)。一方、ハッシュテーブルは(少なくとも直接では)ソートできません。

多分それらは何かをするためのいくつかの洗練されたアルゴリズムでも有用ですが、私の頭には何も思い浮かびません。もっと見つけたら、投稿を編集します。

fe B + treesのような他のツリーは、データベースで広く使用されています


9

最も一般的なアプリケーションの1つは、格納された要素にすばやくアクセスして検索するために、ソートされた形式でデータを効率的に格納することです。たとえば、std::mapまたはstd::setC ++標準ライブラリ。

データ構造としてのバイナリツリーは、式パーサーおよび式ソルバーのさまざまな実装に役立ちます。

また、インデックス作成など、データベースの問題の一部を解決するためにも使用できます。

一般に、バイナリツリーは特定のツリーベースのデータ構造の一般的な概念であり、さまざまな特定のタイプのバイナリツリーを異なるプロパティで構築できます。


7

C ++ STL、およびJavaやC#など、他の言語のその他の多くの標準ライブラリ。バイナリ検索ツリーは、セットとマップを実装するために使用されます。


2
実際、C ++では、セット/マップは最も一般的には赤黒木に基づいています。これは、さらにいくつかの制限があるバイナリ検索ツリーです。
Idan K

6

二分木の最も重要なアプリケーションの1つは、次のような平衡二分探索木です。

これらのタイプのツリーには、ノードが挿入または削除されるたびに回転などの操作を実行することにより、左サブツリーと右サブツリーの高さの差が小さく維持されるという特性があります。

このため、ツリー全体の高さはlog nのオーダーのままであり、ノードの検索、挿入、削除などの操作はO(log n)時間で実行されます。C ++のSTLも、これらのツリーをセットとマップの形式で実装します。


5

データをすばやく並べ替える方法として使用できます。O(log(n))のバイナリ検索ツリーにデータを挿入します。次に、それらをソートするためにツリーをトラバースします。


2

プログラムの構文、またはさらに言えば、自然言語などの他の多くのものは、バイナリツリーを使用して解析できます(ただし、必ずしもそうとは限りません)。



2

最近のハードウェアでは、キャッシュとスペースの動作が悪いため、バイナリツリーはほとんど常に最適ではありません。これは(準)バランスのバリアントにも当てはまります。それらを見つけた場合、パフォーマンスが考慮されない(または比較機能によって支配される)か、または歴史的または無知の理由により可能性が高い場所です。


2
何と比べて最適ではありませんか?

多方向木。1回のメモリアクセスから取得したすべてのデータを線形検索する方が、新しいメインメモリアクセスを実行するよりもはるかに高速です
Stephan Eggermont

私はあなたを信じたいのですが、あなたの答えにはあなたの主張を裏付けるものは何もありません。ソース、ビッグ表記、または何か。詳しく説明してください。
PhilT


0

ASTの表現にバイナリツリーを使用するコンパイラは、postorder、inorderのようなツリーを解析するための既知のアルゴリズムを使用できます。プログラマは独自のアルゴリズムを考案する必要はありません。ソースファイルのバイナリツリーはn-aryツリーよりも高いため、ビルドに時間がかかります。このプロダクションを取る:selstmnt:= "if" "(" expr ")" stmnt "ELSE" stmntバイナリツリーでは、3レベルのノードがありますが、n項ツリーには1レベル(チャッド)があります

UnixベースのOSが遅いのはそのためです。

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