バイナリサーチツリーの定義で重複キーは許可されていますか?


139

私は二分探索木の定義を見つけようとしています、そして私は至る所で異なる定義を見つけ続けています。

特定のサブツリーで、左の子キーはルート以下であると言う人もいます。

特定のサブツリーについて、正しい子キーはルート以上であると言う人もいます。

そして、私の古い大学のデータ構造の本は、「すべての要素にキーがあり、2つの要素が同じキーを持つことはない」と述べています。

bstの普遍的な定義はありますか?特に、同じキーの複数のインスタンスを持つツリーをどうするかに関して。

編集:多分私は不明瞭だったかもしれません、私が見ている定義は

1)左<=ルート<右

2)左<ルート<=右

3)左<ルート<右。重複するキーは存在しません。

回答:


78

多くのアルゴリズムは、重複が除外されることを指定します。たとえば、MITアルゴリズムブックのアルゴリズムの例では、通常、重複のない例が示されています。複製を(ノードのリストとして、または特定の方向に)実装するのはかなり簡単です。

ほとんどの(私が見たもの)は、左の子を<=として指定し、右の子を>として指定します。実際には、右または左の子のいずれかがルートノードと等しくなることを許可するBSTでは、重複ノードが許可されている検索を完了するために追加の計算手順が必要になります。

ノードの片側に「=」値を挿入するには、その側のツリーを書き換えてノードを子として配置するか、ノードをグランドとして配置する必要があるため、ノードでリストを使用して重複を保存するのが最適です。 -子、以下のある時点で、検索効率の一部を排除します。

教室の例のほとんどは、コンセプトを描写して提供するために簡略化されていることを覚えておく必要があります。実際の状況では、しゃがむ価値はありません。しかし、「すべての要素にキーがあり、2つの要素に同じキーがない」というステートメントは、要素ノードでのリストの使用によって違反されません。

だからあなたのデータ構造の本が言ったことを行ってください!

編集:

バイナリ検索ツリーの普遍的な定義には、データ構造を2つの方向のいずれかにトラバースすることに基づいてキーを格納および検索することが含まれます。実用的な意味では、つまり、値が<>の場合、データ構造を2つの「方向」のいずれかでトラバースします。したがって、その意味では、値の重複はまったく意味がありません。

これはBSP(バイナリ検索パーティション)とは異なりますが、それほど大きな違いはありません。検索するアルゴリズムには、「旅行」の2つの方向のいずれかがあるか、それが行われたか(成功したかどうかにかかわらず)。トピック(バイナリ検索の一部としてではなく、検索が成功した後に処理するもの)


1
ノードでリストを使用することの欠点は何ですか?
パセリエ

1
@Pacerierリストを維持する代わりに、各ノードで参照カウントを維持し、重複が発生したときにカウントを更新できると思います。このようなアルゴリズムは、検索と保存がはるかに簡単で効率的です。また、重複をサポートしない既存のアルゴリズムに最小限の変更を加える必要があります。
SimpleGuy 2017

50

二分探索木が真っ赤な木である場合、または何らかの「木の回転」操作を行うつもりである場合は、ノードが重複すると問題が発生します。あなたのツリールールがこれだと想像してください:

左<ルート<=右

ルートが5、左の子がnil、右の子が5の単純なツリーを想像してください。ルートで左回転を行うと、左の子は5になり、右の子は5になります。なし。これで、左側のツリーの何かがルートと等しくなりますが、上記のルールでは、左側<ルートと見なされます。

私は何時間も費やして、なぜ赤/黒の木が時々順不同にトラバースするのかを理解するために費やしましたが、問題は上で説明したものでした。うまくいけば、誰かがこれを読んで、将来的にデバッグに費やす時間を節約できます。


18
等しいノードがある場合は回転しないでください!次のレベルに移動し、それを回転させます。
リッチ

2
他の解決策は、ツリールールをに変更するか、値が最初に現れるleft <= node <= right前にのみ挿入することです。
paxdiablo

これは実際にはどのような問題を引き起こす可能性がありますか?左<=ノード<=右に問題がなければ、赤黒ツリーのすべての操作がうまくいくと思います。
ビョルンLindqvist

39

3つの定義はすべて受け入れ可能で正しいものです。BSTのさまざまなバリエーションを定義します。

あなたの大学のデータ構造の本は、その定義が唯一の可能性ではなかったことを明確にすることができませんでした。

もちろん、重複を許可すると複雑さが増します。定義「左<=ルート<右」を使用し、次のようなツリーがある場合:

      3
    /   \
  2       4

次に、このツリーに「3」の重複キーを追加すると、次のようになります。

      3
    /   \
  2       4
    \
     3

重複は隣接するレベルにないことに注意してください。

これは、上記のようにBST表現で重複を許可する場合の大きな問題です。重複は任意の数のレベルで区切られる可能性があるため、重複の存在を確認することは、ノードの直接の子を確認することほど簡単ではありません。

この問題を回避するオプションは、重複を構造的に(個別のノードとして)表すのではなく、キーの発生数をカウントするカウンターを使用することです。前の例は、次のようなツリーになります。

      3(1)
    /     \
  2(1)     4(1)

重複する「3」キーを挿入すると、次のようになります。

      3(2)
    /     \
  2(1)     4(1)

これにより、いくつかの余分なバイトとカウンター操作を犠牲にして、検索、削除、挿入操作が簡略化されます。


これが私が使っている教科書にも載っていなかったことにとても驚いています。教授はそれについても言及していませんでした。また、重複キーが問題であるという事実もありませんでした...
Biermann

22

BSTでは、ノードの左側に降順のすべての値は、ノード自体よりも小さい(または後で参照)ノード自体です。同様に、ノードの右側にあるすべての値は、ノードの値(a)よりも大きい(または等しいです。

一部のBSTは重複する値を許可することを選択する場合があるため、上記の「または等しい」修飾子。

次の例で明らかになります。

            |
      +--- 14 ---+
      |          |
+--- 13    +--- 22 ---+
|          |          |
1         16    +--- 29 ---+
                |          |
               28         29

これは、重複を許可するBSTを示しています。値を見つけるには、ルートノードから始めて、検索値がノード値よりも小さいか大きいかに応じて、左または右のサブツリーに移動します。

これは次のようなもので再帰的に行うことができます:

def hasVal (node, srchval):
    if node == NULL:
         return false
    if node.val == srchval:
        return true
    if node.val > srchval:
        return hasVal (node.left, srchval)
    return hasVal (node.right, srchval)

そしてそれを呼び出す:

foundIt = hasVal (rootNode, valToLookFor)

同じ値の他のノードの値が見つかったら、検索を続ける必要がある場合があるため、重複は少し複雑になります。


(a)特定のキーの検索方法を調整したい場合は、実際に逆方向に並べ替えることができます。BSTは、昇順または降順に関係なく、ソートされた順序を維持するだけで済みます。


重複するケースの場合、右の子がnode.val == srchval:句の現在のノードと同じであるかどうかを確認して、正しい場合は正しいですか?
bneil 2013

9

Cormen、Leiserson、Rivest、Stein著の「Introduction to algorithm」第3版では、バイナリ検索ツリー(BST)が重複許可するように明示的に定義されています。これは、図12.1と以下(287ページ)で確認できます。

「バイナリ検索ツリーのキーは、常にバイナリ検索ツリーのプロパティを満たすようなAの形で格納されますさせるxバイナリ検索ツリー内のノードである場合には。yの左サブツリー内のノードでありx、その後、y:key <= x:keyIfは。yありますの右側のサブツリーのノードx、次にy:key >= x:key。」

さらに、赤黒木はページ308で次のように定義されます。

「赤黒ツリーは、ノードごとに1ビットのストレージがある2進検索ツリーです:その色」

したがって、この本で定義されている赤黒木は重複をサポートしています。


4

定義はすべて有効です。実装に一貫性がある限り(常に等しいノードを右側に配置するか、常に左側に配置するか、または決して許可しない)、問題はありません。私はそれらを許可しないことが最も一般的だと思いますが、それらが許可され、左または右のいずれかに配置されている場合は、BSTです。


1
重複するキーを含むデータのセットがある場合、それらのアイテムはすべて、別の方法(リンクされたリストなど)を介してツリーの1つのノード内に保存する必要があります。ツリーには一意のキーのみを含める必要があります。
nickf 2008年

1
また、wikiから、ルートの「以上」の値がサブツリーに含まれていることにも注意してください。したがって、wikiの定義は自己矛盾しています。
SoapBox 2008年

1
+1:人によって定義は異なります。新しいBSTを実装する場合は、重複するエントリについてどのような仮定をしているのかを明確にする必要があります。
Fooz氏、2008年

1
重複を許可する場合、コンセンサスは(左<=ルート<=右)のようです。しかし、BSTの一部の人々の定義では、重複を許可していません。あるいは、追加の複雑さを回避するためにその方法を教える人もいるでしょう。
Tim Merrifield、

1
不正解です。左<=ルート<右OR左<ルート<=右、または左>ルート> =右OR左> =ルート>右
ミッチウィート

3

赤黒ツリーの実装に取り​​組んでいたところ、赤黒の挿入ローテーションでは制約を緩める必要があることに気づくまで、複数のキーでツリーを検証する問題が発生していました

left <= root <= right

私が調べていたドキュメントには重複キーが許可されておらず、それを考慮してローテーションメソッドを書き直したくなかったので、ノード内に複数の値を許可するようにノードを変更し、木。


2

あなたが言ったこれら3つのことはすべて真実です。

  • キーは一意です
  • 左側はこれよりも小さいキーです
  • 右側にはこれより大きなキーがあります

ツリーを逆にして小さなキーを右側に置くことができると思いますが、実際には「左」と「右」の概念はそれだけです。左を持たないデータ構造について考えるのに役立つ視覚的な概念またはそう、それは本当に重要ではありません。


1

1.)左<=ルート<右

2.)左<ルート<=右

3.)左<ルート<右。重複するキーは存在しません。

私はアルゴリズムの本を探し出さなければならないかもしれませんが、頭の上(3)には標準形があります。

(1)または(2)は、重複ノードの許可を開始し、重複ノード(リストを含むノードではなく)ツリー自体に配置したときにのみ発生します。


左<=ルート<=右が理想的でない理由を説明できますか?
Helin Wang 2018

@paxdiabloによる承認済みの回答を確認してください。重複した値がで存在して>=いることがわかります。理想は要件によって異なりますが、重複する値が多数あり、重複が構造内に存在することを許可している場合、bstは線形になる可能性があります(O(n)など)。
ロバートポールソン

1

重複するキー•同じキーを持つデータ項目が複数ある場合はどうなりますか?–これは赤黒木にわずかな問題を引き起こします。–同じキーを持つノードが、同じキーを持つ他のノードの両側に分散されていることが重要です。–つまり、キーが50、50、50の順に到着した場合、2番目の50を最初のキーの右側に移動し、3番目の50を最初のキーの左側に移動します。•それ以外の場合、ツリーは不均衡になります。•これは、挿入アルゴリズムのある種のランダム化プロセスで処理できます。–ただし、同じキーを持つすべてのアイテムを見つける必要がある場合、検索プロセスはさらに複雑になります。•同じキーを持つアイテムを非合法化する方が簡単です。–この説明では、重複は許可されていないと想定します

重複したキーを含むツリーの各ノードのリンクリストを作成し、リストにデータを保存できます。


1

@Robert Paulsonの回答にさらに情報を追加したいと思います。

ノードにキーとデータが含まれていると仮定しましょう。したがって、同じキーを持つノードには異なるデータが含まれる可能性があります。
(したがって、検索は同じキーを持つすべてのノードを見つける必要があります)

1)左<=曲<右

2)左<曲<=右

3)左<=曲<=右

4)left <cur <right && curには、同じキーを持つ兄弟ノードが含まれます。

5)left <cur <right、つまり重複するキーは存在しません。

1)および2)は、歪曲を防ぐためにツリーに回転関連の機能がない場合は正常に動作します。
ただし、このフォームAVLツリーまたはRed-Blackツリーは機能ません。回転するとプリンシパルが壊れるからです。
また、search()がキーを持つノードを検出した場合でも、重複するキーを持つノードのリーフノードまでトラバースする必要があります。
検索の時間を複雑にする= theta(logN)

3)ローテーション関連の機能を備えたあらゆる形式のBSTでうまく機能します。
ただし、検索にはO(n)が使用され、BSTを使用する目的が台無しになります。
次のようなツリーがあり、3)プリンシパルがあるとします。

         12
       /    \
     10     20
    /  \    /
   9   11  12 
      /      \
    10       12

このツリーでsearch(12)を実行すると、ルートで12が見つかったとしても、重複するキーを探すために、左と右の両方の子を検索し続ける必要があります。
私が言ったように、これにはO(n)時間かかります。

4)私の個人的なお気に入りです。さんが言ってみましょう兄弟は同じキーを持つノードを意味します。
上のツリーを下に変更できます。

         12 - 12 - 12
       /    \
10 - 10     20
    /  \    /
   9   11  12

これで、重複するキーの子を走査する必要がないため、すべての検索でO(logN)が使用されます。
また、このプリンシパルは、AVLまたはRBツリーでもうまく機能します


0

要素の順序関係<=は合計順序ですため、関係は再帰的である必要がありますが、通常、バイナリ検索ツリー(別名BST)は重複のないツリーです。

それ以外の場合、重複がある場合は、削除の同じ機能を2回以上実行する必要があります。

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