web-dev-qa-db-ja.com

選択アルゴリズムO(n)のランタイムはなぜですか?

Wikipedia によると、quickselectなどのパーティションベースの選択アルゴリズムにはO(n)のランタイムがありますが、確信はありません。それがO(n)である理由を誰かが説明できますか?

通常のクイックソートでは、ランタイムはO(n log n)です。ブランチを2つのブランチ(ピボットより大きく、ピボットより小さい)に分割するたびに、bothブランチでプロセスを続行する必要がありますが、 quickselectはoneブランチのみを処理する必要があります。私はこれらの点を完全に理解しています。ただし、バイナリ検索アルゴリズムで考えると、中央の要素を選択した後、ブランチのone側のみも検索しています。それで、アルゴリズムはO(1)になりますか? いいえもちろん、バイナリ検索アルゴリズムはO(log N)ではなくO(1)です。これは、バイナリ検索ツリーの検索要素と同じです。 one側のみを検索しますが、O(log n)ではなくO(1)を検討します。

クイックセレクトでピボットのone側で検索を続けると、O(1)ではなくO(log n)と見なされる理由を誰かが説明できますか?アルゴリズムは、パーティショニングの場合はO(n log n)O(N)、検索を続ける回数の場合はO(log n)であると考えています。

32
user926958

はるかに単純なクイック選択(予想されるO(n)、最悪の場合のO(n2))より中央の中央値アルゴリズム(n(n))に。これらのアルゴリズムは両方とも、クイックソートパーティショニングステップ(time O(n))を使用して要素を再配置し、1つの要素を適切な位置に配置します。その要素が問題のインデックスにある場合、これで、その要素を返すことができます。それ以外の場合は、どちら側で再帰し、そこで再帰するかを決定します。

ここで、非常に強力な仮定をしてみましょう。クイックセレクト(ピボットをランダムに選択)を使用していて、各反復で配列の真ん中を正確に推測できるとします。その場合、アルゴリズムは次のように機能します。パーティションステップを実行し、配列の半分を破棄してから、配列の半分を再帰的に処理します。つまり、再帰呼び出しのたびに、そのレベルで配列の長さに比例して作業を行うことになりますが、その長さは反復ごとに2の因数で減少し続けます。定数を無視して数学を計算すると、次の時間が得られます。

  • 最初のレベルで働く:n
  • 1回の再帰呼び出しで作業する:n/2
  • 2つの再帰呼び出しの後に作業する:n/4
  • 3回の再帰呼び出しの後の作業:n/8
  • ...

これは、行われた総作業量が

n + n/2 + n/4 + n/8 + n/16 + ... = n(1 + 1/2 + 1/4 + 1/8 + ...)

この最後の項は、1、1/2、1/4、1/8などの合計のn倍であることに注意してください。この無限の合計を計算すると、無限に多くの項があるという事実にもかかわらず、合計は正確に2.これは、総作業量が

n + n/2 + n/4 + n/8 + n/16 + ... = n(1 + 1/2 + 1/4 + 1/8 + ...)= 2n

これは奇妙に思われるかもしれませんが、アイデアは、各レベルで線形の作業を行いながら、配列を半分に切断し続けると、おおよそ2nの作業しか行わないということです。

ここでの重要な詳細は、実際にはO(log n)の異なる反復があることですが、それらすべてが同じ量の作業を行っているわけではありません。実際、各反復は前の反復の半分の作業を行います。仕事が減少しているという事実を無視すると、仕事はO(n log n)であると結論付けることができます。これは正しいですが、厳密な制限ではありません。実行される作業が反復ごとに減少し続けるという事実を使用するこのより正確な分析は、O(n)ランタイムを提供します。

もちろん、これは非常に楽観的な仮定です-50/50の分割はほとんどありません! -しかし、この分析のより強力なバージョンを使用すると、any定数因子分割を保証できる場合、実行される総作業は一部の定数のみであると言えますnの倍数。各反復で完全にランダムな要素を選択する場合(クイック選択で行うように)、期待どおり、配列の中央の50%でピボット要素を選択する前に、2つの要素を選択するだけで済みます。つまり、予想では、ピボットを選択するのに必要なのは2回だけで、25/75の分割を行うものを選択することになります。これは、quickselectのO(n)の予想されるランタイムが由来するところです。

中央値の中央値アルゴリズムの正式な分析は、再発が困難で分析が容易ではないため、はるかに困難です。直感的には、アルゴリズムは、適切なピボットが選択されることを保証するために少量の作業を行うことで機能します。ただし、2つの異なる再帰呼び出しが行われるため、上記のような分析は正しく機能しません。 Akra-Bazzi theorem と呼ばれる高度な結果を使用するか、big-Oの正式な定義を使用して、ランタイムがO(n)であることを明示的に証明できます。より詳細な分析については、Cormen、Leisserson、Rivest、およびSteinによる「Introduction to Algorithms、Third Edition」を参照してください。

お役に立てれば!

67
templatetypedef

選択と二分探索の違いを説明してみましょう。

各ステップの二分探索アルゴリズムはO(1)操作を行います。完全にlog(N)ステップがあり、これによりO(log(N))になります)

各ステップの選択アルゴリズムはO(n)演算を実行します。ただし、この「n」は毎回半分ずつ減少し続けます。完全にlog(N)ステップあります。これにより、N + N/2 + N/4 + ... + 1(log(N)回)= 2N = O(N)

バイナリ検索の場合、1 + 1 + ...(log(N)回)= O(logN)

11
Rajendran T

クイックソートでは、再帰ツリーはlg(N)レベルの深さであり、これらの各レベルにはO(N)の作業量が必要です。したがって、実行時間の合計はO(NlgN)です。

クイック選択では、リクリエーションツリーはlg(N)レベルの深さであり、各レベルはその上のレベルの半分の作業しか必要としません。これにより、以下が生成されます。

N * (1/1 + 1/2 + 1/4 + 1/8 + ...)

または

N * Summation(1/i^2)
    1 < i <= lgN

ここで注意すべき重要な点は、iが1からlgNに移動するが、1からNに移動しないこと、および1から無限大に移動しないことです。

合計は2と評価されます。したがって、Quickselect = O(2N)です。

2
amrish

QuicksortにはnlognのビッグOがありません-ランタイムがn ^ 2の最悪の場合です。

私はあなたがO(kn)である単純な選択アルゴリズムではなく、Hoareの選択アルゴリズム(またはクイック選択)について尋ねていると思います。 quicksortと同様に、quickselectはO(n)ではなくO(n ^ 2)(不良ピボットが選択されている場合)の最悪の場合のランタイムを持っています。ご指摘のとおり、片側のみをソートするため、期待時間nで実行できます。

0
Kane

ソート=要素の再配置はO(n log n)ですが、選択はi番目の要素=インデックスを取得するようなものです。そして、リンクされたリスト、またはバイナリツリーの両方でそれはO(n)です。

0
Joop Eggen

選択のために、必ずしも並べ替えではないからです。与えられた値を持つアイテムの数を数えるだけです。したがって、O(n)中央値は、各値が出現する回数をカウントし、その上下に50%のアイテムがある値を選択することで実行できます。これは、配列を1回通過します。 、配列の各要素のカウンターをインクリメントするだけなので、O(n)になります。

たとえば、8ビットの数値の配列 "a"がある場合、次のことを実行できます。

int histogram [ 256 ];
for (i = 0; i < 256; i++)
{
    histogram [ i ] = 0;
}
for (i = 0; i < numItems; i++)
{
    histogram [ a [ i ] ]++;
}
i = 0;
sum = 0;
while (sum < (numItems / 2))
{
    sum += histogram [ i ];
    i++;
}

最後に、変数「i」には中央値の8ビット値が含まれます。配列 "a"を約1.5回通過しました。一度配列全体を通過して値をカウントし、もう一度半分通過して最終的な値を取得します。

0
user1118321