web-dev-qa-db-ja.com

並べ替えられていない配列の中央値を見つける

ソートされていない配列の中央値を見つけるには、n要素に対してO(nlogn)時間で最小ヒープを作成し、n/2要素を1つずつ抽出して中央値。ただし、このアプローチにはO(nlogn)時間かかります。

O(n)時間で何らかの方法で同じことを行うことができますか?可能であれば、何らかの方法を教えたり、提案してください。

45
Luv

Median of Medians アルゴリズムを使用して、並べ替えられていない配列の中央値を線形時間で検索できます。

38
dasblinkenlight

Medians of Mediansアルゴリズムは実際にO(n)の時間でこの問題を解決するため、@ dasblinkenlightの回答をすでに支持しています。ヒープを使用することで、O(n)の時間でこの問題を解決できることを付け加えたいだけです。ヒープの構築は、ボトムアップを使用してO(n)の時間で実行できます。詳細な説明については、次の記事をご覧ください ヒープソート

配列にN個の要素がある場合、最初のN/2個の要素を含むMaxHeap(またはNが奇数の場合は(N/2)+1)と残りの要素を含むMinHeapの2つのヒープを構築する必要があります。 Nが奇数の場合、中央値はMaxHeapの最大要素(最大値を取得することによるO(1))です。 Nが偶数の場合、中央値は(MaxHeap.max()+ MinHeap.min())/ 2です。これにはO(1)も必要です。したがって、操作全体の実際のコストは、O(n)であるヒープ構築操作です。

ところで、このMaxHeap/MinHeapアルゴリズムは、配列要素の数が事前にわからない場合にも機能します(たとえば、整数のストリームで同じ問題を解決する必要がある場合)。この問題を解決する方法の詳細については、次の記事をご覧ください 整数ストリームの中央値

16
rkachach

Quickselect O(n)で機能します。これはQuicksortのパーティション手順でも使用されます。

10
BrokenGlass

クイック選択アルゴリズムは、線形(O(n))実行時間で配列のk番目に小さい要素を見つけることができます。 Pythonでの実装は次のとおりです。

import random

def partition(L, v):
    smaller = []
    bigger = []
    for val in L:
        if val < v: smaller += [val]
        if val > v: bigger += [val]
    return (smaller, [v], bigger)

def top_k(L, k):
    v = L[random.randrange(len(L))]
    (left, middle, right) = partition(L, v)
    # middle used below (in place of [v]) for clarity
    if len(left) == k:   return left
    if len(left)+1 == k: return left + middle
    if len(left) > k:    return top_k(left, k)
    return left + middle + top_k(right, k - len(left) - len(middle))

def median(L):
    n = len(L)
    l = top_k(L, n / 2 + 1)
    return max(l)
9
doizuc

答えは「いいえ、線形時間で任意の未ソートのデータセットの中央値を見つけることができません」。 (私の知る限り)原則としてできることは、Median of Medians(まともなスタートを切るため)であり、その後にQuickselectが続きます。参照:[ https://en.wikipedia.org/wiki/Median_of_medians] [1]

1
AlanK

ウィキペディアが言うように、Median-of-Mediansは理論的にはo(N)ですが、「良い」ピボットを見つけるオーバーヘッドが遅くなりすぎるため、実際には使用されません。
http://en.wikipedia.org/wiki/Selection_algorithm

Java配列内のk番目の要素を見つけるためのQuickselectアルゴリズムのソース:

/**
 * Returns position of k'th largest element of sub-list.
 * 
 * @param list list to search, whose sub-list may be shuffled before
 *            returning
 * @param lo first element of sub-list in list
 * @param hi just after last element of sub-list in list
 * @param k
 * @return position of k'th largest element of (possibly shuffled) sub-list.
 */
static int select(double[] list, int lo, int hi, int k) {
    int n = hi - lo;
    if (n < 2)
        return lo;

    double pivot = list[lo + (k * 7919) % n]; // Pick a random pivot

    // Triage list to [<pivot][=pivot][>pivot]
    int nLess = 0, nSame = 0, nMore = 0;
    int lo3 = lo;
    int hi3 = hi;
    while (lo3 < hi3) {
        double e = list[lo3];
        int cmp = compare(e, pivot);
        if (cmp < 0) {
            nLess++;
            lo3++;
        } else if (cmp > 0) {
            swap(list, lo3, --hi3);
            if (nSame > 0)
                swap(list, hi3, hi3 + nSame);
            nMore++;
        } else {
            nSame++;
            swap(list, lo3, --hi3);
        }
    }
    assert (nSame > 0);
    assert (nLess + nSame + nMore == n);
    assert (list[lo + nLess] == pivot);
    assert (list[hi - nMore - 1] == pivot);
    if (k >= n - nMore)
        return select(list, hi - nMore, hi, k - nLess - nSame);
    else if (k < nLess)
        return select(list, lo, lo + nLess, k);
    return lo + k;
}

Compareおよびswapメソッドのソースを含めていないため、double []ではなくObject []で動作するようにコードを変更するのは簡単です。

実際には、上記のコードはo(N)であると期待できます。

0
Adam Gawne-Cain

O(n)のQuickselect Algorithmを使用して実行できます。K次統計(ランダム化アルゴリズム)を参照してください。

0
Imposter