web-dev-qa-db-ja.com

「中央値の中央値」アルゴリズムを理解する

次の例の「中央値の中央値」アルゴリズムを理解したい:

45個の異なる番号があり、それぞれが5つの要素を持つ9つのグループに分けられています。

_48 43 38 33 28 23 18 13 8

49 44 39 34 29 24 19 14 9 

50 45 40 35 30 25 20 15 10

51 46 41 36 31 26 21 16 53

52 47 42 37 32 27 22 17 54
_
  1. 最初のステップは、すべてのグループをソートすることです(この場合、それらはすでにソートされています)
  2. 2番目のステップは再帰的に、中央値の「真の」中央値(_50 45 40 35 30 25 20 15 10_)を見つけます。つまり、セットは2つのグループに分割されます。

    _50 25
    
    45 20 
    
    40 15
    
    35 10
    
    30
    _

    これら2つのグループを並べ替える

    _30 10
    
    35 15 
    
    40 20
    
    45 25
    
    50
    _

中央値は40と15です(数値が左中央値をとった場合でも)ので、戻り値は15ですが、中央値の「真の」中央値(_50 45 40 35 30 25 20 15 10_)は30であり、さらに15未満の5つの要素があります wikipedia に記載されている45の30%をはるかに下回っています

したがって、T(n) <= T(n/5) + T(7n/10) + O(n)は失敗します。

ところで、Wikipediaの例では、再帰の結果は36になっていますが、真の中央値は47です。

したがって、場合によっては、この再帰が真の中央値の中央値を返さない場合があります。私の間違いはどこにあるのかを知りたい。

55
simon

問題は、中央値の真の中央値を見つけるためにあなたが言うステップにあります。あなたの例では、これらの中央値がありました:

50 45 40 35 30 25 20 15 10

このデータセットの真の中央値は15ではなく30です。グループを5つのブロックに分割し、それらの中央値の中央値を取ることによって、この中央値を見つけるのではなく、この小さなグループで選択アルゴリズムを再帰的に呼び出します。ロジックのエラーは、上記のシーケンスを2つのブロックに分割することにより、このグループの中央値が見つかると仮定しています。

50 45 40 35 30

そして

25 20 15 10

次に、各ブロックの中央値を見つけます。代わりに、中央値の中央値アルゴリズムは、完全なデータセット50 45 40 35 30 25 20 15 10。内部的には、これによりグループが5つのブロックに分割され、並べ替えなどが行われますが、パーティション分割ステップのパーティションポイントを決定するために行われます。このパーティション分割ステップで、再帰呼び出しが中央値の真の中央値を見つけます。 、この場合は30になります。元のアルゴリズムの分割ステップとして中央値として30を使用すると、必要に応じて非常に良好な分割が得られます。

お役に立てれば!

33
templatetypedef

これは 擬似コード 中央値アルゴリズムの中央値です(例に合わせて少し変更)。ウィキペディアの擬似コードは、selectIdx関数呼び出しの内部動作の描写に失敗します。

説明のためにコードにコメントを追加しました。

// L is the array on which median of medians needs to be found.
// k is the expected median position. E.g. first select call might look like:
// select (array, N/2), where 'array' is an array of numbers of length N

select(L,k)
{

    if (L has 5 or fewer elements) {
        sort L
        return the element in the kth position
    }

    partition L into subsets S[i] of five elements each
        (there will be n/5 subsets total).

    for (i = 1 to n/5) do
        x[i] = select(S[i],3)

    M = select({x[i]}, n/10)

    // The code to follow ensures that even if M turns out to be the
    // smallest/largest value in the array, we'll get the kth smallest
    // element in the array

    // Partition array into three groups based on their value as
    // compared to median M

    partition L into L1<M, L2=M, L3>M

    // Compare the expected median position k with length of first array L1
    // Run recursive select over the array L1 if k is less than length
    // of array L1
    if (k <= length(L1))
        return select(L1,k)

    // Check if k falls in L3 array. Recurse accordingly
    else if (k > length(L1)+length(L2))
        return select(L3,k-length(L1)-length(L2))

    // Simply return M since k falls in L2
    else return M

}

あなたの例を挙げます:

Medians関数の中央値は、(k = 45/2 = 22のような)45個の要素の配列全体で呼び出されます:

median = select({48 49 50 51 52 43 44 45 46 47 38 39 40 41 42 33 34 35 36 37 28 29 30 31 32 23 24 25 26 27 18 19 20 21 22 13 14 15 16 17 8 9 10 53 54}, 45/2)
  1. M = select({x[i]}, n/10)が初めて呼び出されたとき、配列{x[i]}には次の番号が含まれます:50 45 40 35 30 20 15 10。この呼び出しでは、n = 45、したがってselect関数呼び出しはM = select({50 45 40 35 30 20 15 10}, 4)になります

  2. 2回目M = select({x[i]}, n/10)が呼び出されると、配列{x[i]}には次の番号が含まれます:40 20。この呼び出しでは、n = 9、したがって呼び出しはM = select({40 20}, 0)になります。このselect呼び出しは戻り、値M = 20を割り当てます。

    今、あなたが疑問を抱くようになったところで、配列_LM = 20の周りでk = 4で分割します。

    配列Lを覚えておいてください:50 45 40 35 30 20 15 10

    ルール配列L1, L2L3、およびL1 < Mに従って、配列はL2 = MおよびL3 > Mに分割されます。したがって:
    L1: 10 15
    L2: 20
    L3: 30 35 40 45 50

    k = 4以降、length(L1) + length(L2) = 3よりも大きくなっています。したがって、検索は次の再帰呼び出しで続行されます。
    return select(L3,k-length(L1)-length(L2))

    これは次のように翻訳されます:
    return select({30 35 40 45 50}, 1)

    結果として30を返します。 (Lには5つ以下の要素があるため、k番目の要素、つまり、ソートされた配列の最初の位置、つまり30を返します)。

これで、M = 30は45個の要素の配列全体に対する最初のselect関数呼び出しで受信され、M = 30の周りの配列Lを分離する同じパーティションロジックが適用され、中央値。

ふう!中央値アルゴリズムの中央値を説明するのに十分な詳細さと明確さを望んでいます。

26
sultan.of.swing