誰かがどのようにしてヒープを構築することがO(n)複雑になるかを説明するのを手伝ってくれる?
アイテムをヒープに挿入することはO(log n)であり、挿入はn / 2回繰り返されます(残りはリーフであり、ヒーププロパティに違反することはできません)。つまり、これは複雑さがであることを意味しO(n log n)ます。
つまり、「ヒープ化」する各アイテムについて、これまでのヒープの各レベル(log nレベル)に対して1回フィルターをかける必要がある可能性があります。
何が欠けていますか?
誰かがどのようにしてヒープを構築することがO(n)複雑になるかを説明するのを手伝ってくれる?
アイテムをヒープに挿入することはO(log n)であり、挿入はn / 2回繰り返されます(残りはリーフであり、ヒーププロパティに違反することはできません)。つまり、これは複雑さがであることを意味しO(n log n)ます。
つまり、「ヒープ化」する各アイテムについて、これまでのヒープの各レベル(log nレベル)に対して1回フィルターをかける必要がある可能性があります。
何が欠けていますか?
回答:
このトピックにはいくつかの疑問が潜んでいると思います。
buildHeapで実行できるようにどのように実装しますか?buildHeap、O(n)時間で実行されることをどのように示しますか?buildHeapで実行できるようにどのように実装しますか?多くの場合、これらの質問に対する答えは違いに焦点を当てるsiftUpとsiftDown。間に正しい選択をするsiftUpとsiftDown取得することが重要であるO(N)のパフォーマンスをbuildHeap、しかし、1つの違いを理解する助けに何もしませんbuildHeapし、heapSort一般的に。実際、両方の適切な実装buildHeapとheapSortなりますのみ使用しますsiftDown。このsiftUp操作は、既存のヒープへの挿入を実行するためにのみ必要であるため、たとえば、バイナリヒープを使用して優先度キューを実装するために使用されます。
最大ヒープがどのように機能するかを説明するためにこれを書きました。これは、ヒープのソートまたは優先度キューに通常使用されるヒープのタイプであり、値が高いほど優先度が高いことを示します。最小ヒープも役立ちます。たとえば、昇順の整数キーまたはアルファベット順の文字列を持つアイテムを取得する場合などです。原則はまったく同じです。ソート順を切り替えるだけです。
ヒーププロパティバイナリヒープ内の各ノードは、少なくともその子の両方として大きいようでなければならないことを指定します。特に、これはヒープ内の最大のアイテムがルートにあることを意味します。ふるいにかけることとふるいにかけることは、本質的に反対の方向に同じ操作です。問題のあるノードを、ヒーププロパティを満たすまで移動します。
siftDown 小さすぎるノードをその最大の子と入れ替えます(その結果、ノードが下に移動します)。少なくとも、その下の両方のノードと同じ大きさになります。 siftUp 大きすぎるノードをその親ノードと入れ替えます(それにより、そのノードを上に移動します)。 操作の数のために必要siftDownとsiftUpノードが移動する必要がある可能性があり、距離に比例しています。の場合siftDown、これはツリーの最下部までの距離であるため、ツリーsiftDownの最上位にあるノードではコストが高くなります。を使用するsiftUpと、作業はツリーの最上部までの距離に比例するため、ツリーsiftUpの最下部にあるノードではコストが高くなります。最悪の場合、両方の操作はO(log n)ですが、ヒープでは、1つのノードのみが一番上にあり、半分のノードが一番下のレイヤーにあります。したがって、すべてのノードに操作を適用する必要がある場合は、を優先siftDownすることはそれほど驚くべきことではありませんsiftUp。
このbuildHeap関数は、並べ替えられていない項目の配列を受け取り、それらがすべてヒーププロパティを満たすまでそれらを移動して、有効なヒープを生成します。ここで説明buildHeapしたsiftUpとのsiftDown操作を使用するには、2つの方法があります。
ヒープの最上部(配列の先頭)から開始し、siftUp各項目を呼び出します。各ステップで、以前にふるいにかけられたアイテム(配列内の現在のアイテムの前のアイテム)が有効なヒープを形成し、次のアイテムをふるいにかけることで、ヒープ内の有効な位置に配置します。各ノードをふるいにかけた後、すべてのアイテムがヒーププロパティを満たします。
または、反対方向に進みます。アレイの最後から開始して、前方に向かって後方に移動します。各反復で、アイテムが正しい位置に来るまで下にふるいにかけます。
buildHeapがより効率的ですか?これらのソリューションはどちらも有効なヒープを生成します。当然のことながら、より効率的なのは、を使用する2番目の操作ですsiftDown。
ましょH =ログNヒープの高さを表しています。siftDownアプローチに必要な作業は合計で与えられます
(0 * n/2) + (1 * n/4) + (2 * n/8) + ... + (h * 1).
合計の各項には、指定された高さのノードが移動しなければならない最大距離(最下層の場合はゼロ、ルートの場合はh)にその高さのノード数を掛けた値があります。対照的に、siftUp各ノードの呼び出しの合計は
(h * n/2) + ((h-1) * n/4) + ((h-2)*n/8) + ... + (0 * 1).
2番目の合計が大きいことは明らかです。最初の項だけではhn / 2 = 1/2 n log nなので、このアプローチはせいぜいO(n log n)の複雑さです。
siftDownアプローチの合計が確かにO(n)であることをどのように証明しますか?1つの方法(これも機能する他の分析があります)は、有限和を無限級数に変換してから、テイラー級数を使用することです。ゼロである最初の項は無視できます。
これらの各ステップが機能する理由がわからない場合は、ここにプロセスの正当性を言葉で説明します。
無限和は正確にnであるため、有限和はそれ以上大きくなく、したがってO(n)であると結論付けます。
buildHeap線形時間で実行できる場合、ヒープのソートにO(n log n)時間を必要とするのはなぜですか?ヒープのソートは2つの段階で構成されています。まず、buildHeap配列を呼び出します。最適に実装されている場合、O(n)時間を必要とします。次の段階は、ヒープ内の最大の項目を繰り返し削除して、配列の最後に配置することです。ヒープからアイテムを削除するため、ヒープの終わりの直後に、アイテムを格納できるオープンスポットが常にあります。したがって、ヒープソートは、次に大きいアイテムを連続的に削除し、最後の位置から開始して前に向かって配列に配置することで、ソートされた順序を実現します。ヒープのソートで支配的なのは、この最後の部分の複雑さです。ループは次のようになります。
for (i = n - 1; i > 0; i--) {
arr[i] = deleteMax();
}
明らかに、ループはO(n)回実行されます(正確にはn-1、最後の項目は既に配置されています)。deleteMaxヒープの複雑さはO(log n)です。これは通常、ルート(ヒープ内に残っている最大のアイテム)を削除し、それをヒープ内の最後のアイテム(リーフ)に置き換えることによって実装されるため、最小のアイテムの1つになります。この新しいルートはほぼ確実にヒーププロパティに違反するためsiftDown、許容可能な位置に戻すまで呼び出す必要があります。これには、次に大きいアイテムをルートまで移動する効果もあります。buildHeapほとんどのノードでsiftDownツリーの下部から呼び出しているのとは対照的に、siftDown各反復でツリーの上部から呼び出していることに注意してください。ツリーは収縮していますが、十分に速くは収縮していません。ノードの前半を削除するまで(最下層を完全に取り除くと)、ツリーの高さは一定のままです。次の四半期の高さはh-1です。したがって、この第2ステージの総作業は
h*n/2 + (h-1)*n/4 + ... + 0 * 1.
スイッチに注意してください。今度はゼロの作業ケースが単一のノードに対応し、hの作業ケースがノードの半分に対応します。この合計はO(n log n)でありbuildHeap、siftUpを使用して実装された非効率なバージョンと同じです。しかし、この場合は、ソートしようとしているため、次に選択できるアイテムはありません。次に大きいアイテムを次に削除する必要があります。
要約すると、ヒープの並べ替えの作業は、2つの段階の合計です: buildHeapのO(n)時間と各ノードを順番に削除するO(n log n)なので、複雑度はO(n log n)です。比較ベースの並べ替えの場合、とにかくO(n log n)が期待できる最高であることを(情報理論のいくつかのアイデアを使用して)証明できるため、これに失望したり、ヒープの並べ替えを期待して理由を達成したりする必要はありません。 O(n)時間制限buildHeap。
siftUpして各項目を呼び出すか、最後に後方に移動してを呼び出しsiftDownます。どちらの方法を選択しても、配列の並べ替えられていない部分の次の項目を選択し、適切な操作を実行して配列の順序付けられた部分の有効な位置に移動します。唯一の違いはパフォーマンスです。
あなたの分析は正しいです。ただし、タイトではありません。
ヒープの構築が線形操作である理由を説明するのは簡単ではありません。よく読んでください。
偉大な解析アルゴリズムのを見ることができるここに。
主な考え方は、build_heapアルゴリズムでは実際のheapifyコストがO(log n)すべての要素にかかるわけではないということです。
heapifyが呼び出されたときの実行時間は、プロセスが終了するまでに要素がツリー内でどれだけ下に移動するかによって異なります。つまり、ヒープ内の要素の高さに依存します。最悪の場合、要素はリーフレベルまでずっと下がることがあります。
レベルごとに行われた作業をカウントしてみましょう。
最下位レベルには2^(h)ノードがありますがheapify、これらのいずれも呼び出さないため、作業は0です。次のレベルには2^(h − 1)ノードがあり、それぞれが1レベル下に移動する可能性があります。下から3番目のレベルには2^(h − 2)ノードがあり、それぞれが2レベル下に移動する場合があります。
すべてのheapifyオペレーションがそうであるとは限らないのでO(log n)、これがあなたが得ている理由ですO(n)。
i高さhのツリーの最下部から高さのノードに対して行われた#比較2* log(h-i)も同様に比較を行う必要があり、@ The111も考慮する必要があるようです。どう思いますか?
「複雑さはO(nLog n)である必要があります...「ヒープ化」する各アイテムについて、これまでのヒープの各レベル(log nレベル)に対して一度フィルターをかける必要がある可能性があります。」
結構です。あなたのロジックはタイトなバウンドを生成しません-それは各ヒープ化の複雑さを過大評価します。ボトムアップで構築した場合、挿入(ヒープ化)はをはるかに下回ることがありO(log(n))ます。プロセスは次のとおりです。
(手順1) 最初のn/2要素はヒープの一番下の行に配置されます。h=0、heapifyは必要ありません。
(手順2) 次の要素は、下から1行目に配置されます。、ヒープ化フィルターは1レベル下にあります。n/22h=1
(ステップi)
次の要素は下から上に並んでいきます。、フィルターレベルをheapify します。n/2iih=ii
(Step log(n)) 最後の要素は下から上に並んでいます。、フィルターレベルをheapify します。n/2log2(n) = 1log(n)h=log(n)log(n)
注意:ステップ1の後1/2、要素(n/2)は既にヒープ内にあり、一度heapifyを呼び出す必要すらありませんでした。また、1つの要素(ルート)だけが実際に完全なlog(n)複雑さを招くことにも注意してください。
Nサイズのヒープを構築するための合計手順はn、数学的に書き出すことができます。
高さiでは、heapifyを呼び出す必要のある要素があることを(上で)示しましたが、heapify at height はであることがわかっています。これは与える:n/2i+1iO(i)
最後の総和の解は、よく知られた幾何級数方程式の両側の導関数を取ることによって見つけることができます。
最後x = 1/2に、上記の方程式にプラグインすると、が得られ2ます。これを最初の方程式に代入すると、次のようになります。
したがって、ステップの総数は O(n)
要素を繰り返し挿入してヒープを構築した場合は、O(n log n)になります。ただし、要素を任意の順序で挿入し、アルゴリズムを適用してそれらを適切な順序に「ヒープ化」することにより、新しいヒープをより効率的に作成できます(もちろん、ヒープのタイプによって異なります)。
例については、http://en.wikipedia.org/wiki/Binary_heap、「ヒープの構築」を参照してください。この場合、基本的にツリーの最下位レベルから作業を進め、ヒープ条件が満たされるまで親ノードと子ノードを交換します。
すでにいくつかの素晴らしい答えがありますが、少し視覚的な説明を追加したいと思います

今、画像を見て、ある
n/2^1 緑色のノードとの高さ0(ここで2分の23 = 12)
n/2^2 の赤色のノードと高さ1(ここでは4分の23 = 6)
n/2^3 ブルーノードと高さ2(ここで23/8 = 3)
n/2^4 紫色のノードと高さ3(ここでは16分の23 = 2)
があるので、n/2^(h+1)高さのため、ノードH
複雑さをカウントすることができます時間を見つけるために行われた仕事の量や反復の最大無いが行われ、各ノードが
、今では、各ノードできることに気づいたことができます実行(最大)反復==ノードの高さ
Green = n/2^1 * 0 (no iterations since no children)
red = n/2^2 * 1 (heapify will perform atmost one swap for each red node)
blue = n/2^3 * 2 (heapify will perform atmost two swaps for each blue node)
purple = n/2^4 * 3 (heapify will perform atmost three swaps for each purple node)
したがって、高さがhのノードの場合、実行される最大の作業はn / 2 ^(h + 1)* hです。
今、行われる総作業は
->(n/2^1 * 0) + (n/2^2 * 1)+ (n/2^3 * 2) + (n/2^4 * 3) +...+ (n/2^(h+1) * h)
-> n * ( 0 + 1/4 + 2/8 + 3/16 +...+ h/2^(h+1) )
ここで、hの任意の値について、シーケンス
-> ( 0 + 1/4 + 2/8 + 3/16 +...+ h/2^(h+1) )
したがって、 1を超えることはありません。したがって、ヒープを構築するための時間の複雑さは O(n)を超えることはありません。
我々は、ヒープの高さを知っているように、ログ(N) elements.Letsの総数はとして表現されているN、H
我々はheapify操作を実行すると、最後のレベル(AT要素hが)も単一の移動はありませんステップ。
最後から2番目のレベル(h-1)の要素の数は2 h-1で、最大1レベル(ヒープ化中)に移動できます。
同様に、i 番目のレベルには、hiの位置に移動できる2つのi要素があります。
したがって、移動の総数= S = 2 h * 0 + 2 h-1 * 1 + 2 h-2 * 2 + ... 2 0 * h
S = 2 h {1/2 + 2/2 2 + 3/2 3 + ... h / 2 h } ----------------------- -------------------------- 1
これはAGPシリーズです。これを解決するには、両側を2
S / 2 = 2 hで 除算します{1/2 2 + 2/2 3 + ... h / 2 h + 1 } --------------------------------- ---------------- 2 1から
式2を引くと、S / 2 = 2 h { 1/2 + 1/2 2 + 1/2 3 + ... + 1 / 2 時間 + h / 2 時間+ 1 }
S = 2 H + 1 {1/2 + 1/2 2 + 1/2 3 + ... + 1/2 H + H / 2 H + 1 }
今1/2 + 1/2 2 + 1/2 3 + ... + 1/2 hは、合計が1未満のGPの減少です(hが無限大になる傾向がある場合、合計は1になる傾向があります)。さらなる分析では、上部1である和に結合者がみましょう
。これは、得られるS = 2 H + 1 {1 + H / 2 H + 1 } = 2 、H + 1つの + H 〜2 H + HとしてH =ログを(n)、2 時間 = n
したがって、S = n + log(n)
T(C)= O(n)
ヒープを構築しているときに、ボトムアップのアプローチを取っているとしましょう。
ヒープを構築する場合、高さlogn -1(ここで、lognはn要素のツリーの高さ)から始めます 。高さ「h」にある各要素について、最大(logn -h)の高さまで下げます。
So total number of traversal would be:-
T(n) = sigma((2^(logn-h))*h) where h varies from 1 to logn
T(n) = n((1/2)+(2/4)+(3/8)+.....+(logn/(2^logn)))
T(n) = n*(sigma(x/(2^x))) where x varies from 1 to logn
and according to the [sources][1]
function in the bracket approaches to 2 at infinity.
Hence T(n) ~ O(n)
あなたが持っていると仮定しましょNのヒープ内の要素を。次に、その高さはLog(N)になります
今、あなたは複雑のようになり、別の要素を挿入したい:ログ(N) 、我々はすべての方法を比較する必要がUPルートに。
これで、N + 1個の要素と高さ= Log(N + 1)が得られました
誘導技術を使用すると、挿入の複雑さが∑logiであることが証明できます。
現在使用しています
ログa +ログb =ログab
これにより、次のように簡略化されます:∑logi = log(n!)
これは実際にはO(NlogN)です
だが
私たちはここで何か間違っているのです。したがって、ほとんどの場合実行中に、ツリーを途中まで上ることはありません。そこから、上記の回答で与えられた数学を使用することにより、この境界を最適化して別のより厳しい境界を設定できます。
この実現は、詳細とヒープの実験の後に私に起こりました。
私はジェレミー・ウェストの説明が本当に好きです...本当に理解しやすい別のアプローチがここにあり ますhttp://courses.washington.edu/css343/zander/NotesProbs/heapcomplexity
なぜなら、buildheapはheapifyに依存して使用し、shiftdownアプローチはすべてのノードの高さの合計に依存して使用されるためです。したがって、S =(2 ^ i *(hi))のi = 0からi = hまでの合計で与えられるノードの高さの合計を見つけるには、h = lognはsを解く木の高さです。 s = 2 ^(h + 1)-1-(h + 1)、n = 2 ^(h + 1)-1 s = n-h-1 = n- logn-1 s = O(n)、ビルドヒープの複雑さはO(n)です。
各ノードが実行できる最大の移動を計算することにより、ヒープビルドのランタイムを取得します。したがって、各行にあるノードの数と、各ノードがノードからどれだけ離れることができるかを知る必要があります。
ルートノードから開始すると、次の各行は前の行の2倍のノードを持っているので、ノードの数がなくなるまでノードの数を2倍にできるかという質問に答えることで、ツリーの高さを取得します。または数学的には、ツリーの高さはlog2(n)です。nは配列の長さです。
後ろから始まる1つの行のノードを計算するには、n / 2ノードが一番下にあることを知っているため、2で割ると、前の行が得られます。
これに基づいて、シフトダウンアプローチの次の式を取得します:(0 * n / 2)+(1 * n / 4)+(2 * n / 8)+ ... +(log2(n)* 1)
最後の括弧内の項は、ツリーの高さにルートにある1つのノードを掛けたものであり、最初の括弧内の項は、最下行のすべてのノードにそれらが移動できる長さを掛けたものです。スマートの同じ式:

nを戻すと、2 * nが得られます。2は定数であり、Siftdownアプローチの最悪の場合の実行時間があるため、破棄できます:n。
あなたは間違いを犯していると思います。これを見てください:http : //golang.org/pkg/container/heap/ヒープの構築はO(n)ではありません。ただし、挿入はO(lg(n)です。ヒープサイズb / cを設定した場合、ヒープは領域を割り当ててデータ構造を設定する必要があるため、初期化はO(n)であると想定しています。ヒープに挿入してはい、各挿入はlg(n)であり、n個の項目があるため、uが述べたようにn * lg(n)を取得します