ヒープがあります(バイナリツリーのように実装されています。各ノードには、子への2つのポインターと、親への1つのポインターがあります)。
要素の数を指定して、k番目の要素を(BFSの順序で)見つけるにはどうすればよいですか?O(logn)時間でできると思います。
ヒープがあります(バイナリツリーのように実装されています。各ノードには、子への2つのポインターと、親への1つのポインターがあります)。
要素の数を指定して、k番目の要素を(BFSの順序で)見つけるにはどうすればよいですか?O(logn)時間でできると思います。
(「k番目の要素(BFS順)」とは、入力の上から下、左から右へのスキャンの観点から、k番目の要素を意味すると想定しています。)
バイナリヒープは完全なバイナリツリー(おそらく最後のレベルを除く)であることがわかっているので、ツリーの形状は、いくつかの高さ(いくつかのkに対して2 kのノードを含む)の完全なバイナリツリーであり、いくつかの数があります。ノードは左から右に入力されます。これらのツリーの本当に気の利いたプロパティは、画像内のノードの数を書き出すときに発生します。値のインデックスを1つ作成します。
1
2 3
4 5 6 7
8 9 10 11 12 13 14 15
各レイヤーは、2の累乗のノードで始まることに注意してください。それで、仮に、あなたが数13を調べたいと仮定しましょう。13以下の2の最大の累乗は8であるため、13が行に表示されなければならないことがわかります。
8 9 10 11 12 13 14 15
これで、この知識を使用して、13からツリーのルートまでのパスをリバースエンジニアリングできます。たとえば、13はこの行の数字の後半にあることがわかります。つまり、13はルートの右側のサブツリーに属します(左側のサブツリーに属している場合は、次のサブツリーに属します)。 8、9、10、および11)これは、ルートから直接移動して、数値の半分を捨てて取得できることを意味します。
12 13 14 15
これで、ツリーのノード3にいます。それで、私たちは左に行くのですか、それとも右に行くのですか?13はこれらの数値の前半にあるので、この時点でノード3の左側のサブツリーに降りる必要があることがわかります。これでノード6に移動し、前半が残ります。番号:
12 13
13はこれらのノードの右半分にあるので、右に降りてノード13に移動する必要があります。があった!
では、このプロセスはどのように機能しましたか?さて、私たちが使用できる本当に、本当にかわいいトリックがあります。上記と同じツリーをバイナリで書きましょう。
0001
0010 0011
0100 0101 0110 0111
1000 1001 1010 1011 1100 1101 1110 1111
^^^^
ノード13の場所を指摘しました。アルゴリズムは次のように機能しました。
これがバイナリで何を意味するかを考えてみましょう。ノードを含むレイヤーを見つけることは、数値に設定されている最上位ビットを見つけることと同じです。バイナリ表現1101を持つ13では、MSBは8ビットです。これは、8から始まるレイヤーにいることを意味します。
では、左側のサブツリーにいるのか、右側のサブツリーにいるのかをどのように判断するのでしょうか。そのためには、このレイヤーの前半にいるのか後半にいるのかを確認する必要があります。そして今、かわいいトリックのために-MSBの後の次のビットを見てください。このビットが0に設定されている場合、範囲の前半にあり、そうでない場合は、範囲の後半にあります。したがって、数値の次のビットを確認するだけで、範囲の半分を判別できます。これは、数値の次のビットを見ることで、どのサブツリーに降りるかを決定できることを意味します。
それが済んだら、このプロセスを繰り返すことができます。次のレベルで何をしますか?次のビットがゼロの場合は左に移動し、次のビットが1の場合は右に移動します。これが13にとって何を意味するかを見てください。
1101
^^^
|||
||+--- Go right at the third node.
||
|+---- Go left at the second node.
|
+----- Go right at the first node.
つまり、MSBの後の数字のビットを見るだけで、ツリーのルートから問題のノードまでのパスを詳しく説明できます。
これは常に機能しますか?あなたは賭けます!番号7を試してみましょう。これはバイナリ表現0111です。MSBは4の場所にあります。私たちのアルゴリズムを使用して、これを行います:
0111
^^
||
|+--- Go right at the second node.
|
+---- Go right at the first node.
元の写真を見ると、これが正しい道です。
このアルゴリズムの大まかなC/C++擬似コードは次のとおりです。
Node* NthNode(Node* root, int n) {
/* Find the largest power of two no greater than n. */
int bitIndex = 0;
while (true) {
/* See if the next power of two is greater than n. */
if (1 << (bitIndex + 1) > n) break;
bitIndex++;
}
/* Back off the bit index by one. We're going to use this to find the
* path down.
*/
bitIndex--;
/* Read off the directions to take from the bits of n. */
for (; bitIndex >= 0; bitIndex--) {
int mask = (1 << bitIndex);
if (n & mask)
root = root->right;
else
root = root->left;
}
return root;
}
私はこのコードをテストしていません! Don Knuthを言い換えると、概念的には正しいことをしていることを示しました。ここで1つずつエラーが発生する可能性があります。
では、このコードはどれくらい速いのでしょうか?最初のループは、nより大きい2の1乗が見つかるまで実行されます。これにはO(log n)時間がかかります。ループの次の部分は、一度に1つずつnのビットを逆方向にカウントし、各ステップでO(1)作業を実行します。したがって、アルゴリズム全体には合計O(log n)時間がかかります。
お役に立てれば!