n 個の中から k 個を選ぶ 質問する

n 個の中から k 個を選ぶ 質問する

同じ数字を 2 回選択せずに、k可能性のある要素を均一にランダムに選択したいと考えています。これには 2 つの簡単なアプローチがあります。n

  1. すべての可能性のリストを作成します。それらをシャッフルします ( Fisher Yates の最初の手順を実行することで、の数だけnをシャッフルする必要はありません)。最初の を選択します。このアプローチには、時間がかかり(サイズの配列の割り当てには時間がかかると仮定)、スペースもかかります。が に比べて非常に小さい場合、これは問題になります。nkkkO(k)nO(1)O(n)kn
  2. 見た要素のセットを保存します。 からランダムに数字を選択します[0, n-1]。要素がセット内にある間は、新しい数字を選択します。このアプローチはO(k)スペースを消費します。実行時間は分析が少し複雑になります。 の場合、k = theta(n)実行時間はO(k*lg(k))=O(n*lg(n))クーポン収集者の問題kが に比べて小さい場合、同じ数字を 2 回選択する確率 (低いとはいえ) のためn、 よりもわずかに時間がかかりますO(k)。これは、スペースの点では上記のソリューションよりも優れていますが、実行時間の点では劣ります。

私の質問:

およびすべてに対するO(k)時間、O(k)空間アルゴリズムは存在するか?kn

ベストアンサー1

O(1)ハッシュテーブル、部分フィッシャー・イェイツ法はO()時間と空間を節約します。秘訣は、かわったハッシュ テーブル内の配列の要素。

以下は Java での簡単な例です。

public static int[] getRandomSelection (int k, int n, Random rng) {
    if (k > n) throw new IllegalArgumentException(
        "Cannot choose " + k + " elements out of " + n + "."
    );

    HashMap<Integer, Integer> hash = new HashMap<Integer, Integer>(2*k);
    int[] output = new int[k];

    for (int i = 0; i < k; i++) {
        int j = i + rng.nextInt(n - i);
        output[i] = (hash.containsKey(j) ? hash.remove(j) : j);
        if (j > i) hash.put(j, (hash.containsKey(i) ? hash.remove(i) : i));
    }
    return output;
}

このコードは2×のHashMapを割り当てます変更された要素を格納するバケット (ハッシュ テーブルが再ハッシュされないようにするには十分なはずです) を作成し、そのバケットに対して部分的な Fisher-Yates シャッフルを実行します。

Ideoneの簡単なテストはこちら3 つの要素から 2 つの要素を 30,000 回選択し、各要素のペアが選択された回数をカウントします。偏りのないシャッフルでは、両方の要素が等しいというあり得ないケースを除いて、各順序付きペアは約 5,000 回 (±100 回程度) 出現するはずです。

おすすめ記事