まず、Mark Weiss から返信があり、彼のメールには基本的にコードが正しいと書かれていました (この回答の最後に完全な返信があります)。
彼はまたこう言った:
したがって、findMin に示すように、最小項目は配列インデックス 1 にあります。挿入を行うには、下からルートまでのパスをたどります。
インデックス 1? うーん...その後、戻って章の大部分を読み直す必要があり、図 6.3 を見てクリックしました。
配列は 0 ベースですが、ヒープの一部と見なされる要素はインデックス 1 以降に格納されます。図 6.3 は次のようになります。
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| | A | B | C | D | E | F | G | H | I | J | | | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
0 1 2 3 4 5 6 7 8 9 10 11 12 13
要素 0 に値を配置することは、ループを終了させるためのセンチネル値です。
したがって、上記のツリーを使用して、挿入機能がどのように機能するかを見てみましょう。H
下が穴のマーク。
まずx
、0 番目の要素 (ヒープの外側) に配置し、配列内の次の使用可能な要素に穴を配置します。
H
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| x | A | B | C | D | E | F | G | H | I | J | | | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
0 1 2 3 4 5 6 7 8 9 10 11 12 13
次に、穴をバブルアップ (パーコレート) し、x
.
図 6.5 と 6.6 を見て、実際の値を配列に入れましょう。
H/2 H
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
| 14 | 13 | 21 | 16 | 24 | 31 | 19 | 68 | 65 | 26 | 32 | | | |
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
0 1 2 3 4 5 6 7 8 9 10 11 12 13
挿入する値である 14 をインデックス 0 に配置したことに注意してください。ただし、これはヒープの外側にあり、ループを確実に終了させるためのセンチネル値です。
次に、値x
を の値と比較しますhole / 2
。現在は 11/2 = 5 です。x は 31 より小さいので、値を上に移動し、穴を移動します。
H/2 H <---------------------------
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
| 14 | 13 | 21 | 16 | 24 | 31 | 19 | 68 | 65 | 26 | 32 | 31 | | |
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
0 1 2 3 4 5 6 7 8 9 10 11 12 13
| ^
+--------- move 31 -----------+
もう一度比較すると、14 は 21 よりも小さい (5 / 2 = 2) ため、もう一度:
H/2 H <------------
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
| 14 | 13 | 21 | 16 | 24 | 21 | 19 | 68 | 65 | 26 | 32 | 31 | | |
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
0 1 2 3 4 5 6 7 8 9 10 11 12 13
| ^
+-- move 21 ---+
ただし、14 は 13 より小さくない (穴 / 2 --> 2 / 1 = 1) ため、次の正しい場所を見つけましたx
。
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
| 14 | 13 | 14 | 16 | 24 | 21 | 19 | 68 | 65 | 26 | 32 | 31 | | |
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
0 1 2 3 4 5 6 7 8 9 10 11 12 13
^
x
図 6.6 と 6.7 を見るとわかるように、これは予想される動作と一致します。
したがって、コードは間違っていませんが、おそらく本の範囲外である小さな障害が 1 つあります。
挿入されるタイプがx
参照タイプの場合、現在のヒープには、挿入された同じオブジェクトへの 2 つの参照があります。次に、ヒープからオブジェクトをすぐに削除すると、0 番目の要素がまだ参照を保持しているように見えます (ただし、最初の場所にあるように見えます...)。
ここに隠された議題がないことを確認するために、マークからの完全な回答を次に示します。
ラッセさん、こんにちは。
コードは正しいです。
バイナリ ヒープは完全なバイナリ ツリーであり、底からルートまでのどのパスでも、値が増加することはありません。したがって、最小項目はルートにあります。配列表現はルートをインデックス 1 に配置し、インデックス i の任意のノードについて、親は i/2 (切り捨て) にあります (左の子は 2i にあり、右の子は 2i+1 にありますが、これは必要ありません)ここ)。
したがって、findMin に示すように、最小項目は配列インデックス 1 にあります。挿入を行うには、下からルートまでのパスをたどります。
for ループでは:
hole /= 2 は、穴を親に移動するという考えを表しています。
x.compareTo( array[ hole / 2 ]) < 0 は、x が親よりも小さい限り、ループにとどまるという考えを表しています。
問題は、x が新しい最小値である場合、安全にループから抜け出せないことです (技術的には、x と配列 [0] を比較しようとするとクラッシュします)。コーナーケースを処理するために追加のテストを入れることができます。または、コードは最初に x を array[0] に配置することで回避します。ノード i の「親」は i/2 であるため、インデックス 1 にあるルートの「親」はインデックスで見つけることができます。 0. これにより、x が新しい最小値である場合にループが終了することが保証されます (そして、インデックス 1 のルートに新しい最小値である x が配置されます)。
より長い説明は本にあります... しかし、ここでの基本的な概念は、センチネル (またはダミー) 値を使用して、境界ケースの余分なコードを回避することです。
よろしく、
マーク・ワイス