5

std::vector内の要素をランダムにシャッフルし、いくつかの操作の後、元の順序を復元する適切な方法を理解するのに問題があります。これはかなり些細なアルゴリズムであるべきだと私は知っていますが、私はあまりにも疲れていると思います...

カスタム乱数ジェネレータークラスを使用するように制約さstd::random_shuffleれているため、元の順序も保持する必要があるため、とにかく役に立たない、を使用できないと思います。したがって、私のアプローチは、std::map次のように、元の位置とランダムな位置の間のマッピングとして機能するを作成することでした。

std::map<unsigned int, unsigned int> getRandomPermutation (const unsigned int &numberOfElements)
{
    std::map<unsigned int, unsigned int> permutation;

    //populate the map
    for (unsigned int i = 0; i < numberOfElements; i++)
    {
        permutation[i] = i;
    }

    //randomize it
    for (unsigned int i = 0; i < numberOfElements; i++)
    {
        //generate a random number in the interval [0, numberOfElements)
        unsigned long randomValue = GetRandomInteger(numberOfElements - 1U);

        //broken swap implementation
        //permutation[i] = randomValue;
        //permutation[randomValue] = i;

        //use this instead:
        std::swap(permutation[i], permutation[randomValue]);
    }

    return permutation;
}

上記のアルゴリズムがランダム置換の適切な実装であるかどうかはわかりません。したがって、改善を歓迎します。

さて、これが私がこの順列マップを利用することに成功した方法です:

std::vector<BigInteger> doStuff (const std::vector<BigInteger> &input)
{
    /// Permute the values in a random order
    std::map<unsigned int, unsigned int> permutation = getRandomPermutation(static_cast<unsigned int>(input.size()));

    std::vector<BigInteger> temp;

    //permute values
    for (unsigned int i = 0; i < static_cast<unsigned int>(input.size()); ++i)
    {
        temp.push_back(input[permutation[i]]);
    }

    //do all sorts of stuff with temp

    /// Reverse the permutation
    std::vector<BigInteger> output;
    for (unsigned int i = 0; i < static_cast<unsigned int>(input.size()); ++i)
    {
        output.push_back(temp[permutation[i]]);
    }

    return output;
}

このアルゴリズムには1つしか使用できないはずだと言われていますstd::vector<BigInteger>が、現時点では、最適なソリューションを見つけることができません。正直なところ、私はのデータをあまり気にしないので、データをinput非定数にして上書きし、コピーの作成をスキップすることもできますが、問題はアルゴリズムを実装する方法ですか?

こういうことをすると、足を撃ってしまうんですよね?:)

for (unsigned int i = 0; i < static_cast<unsigned int>(input.size()); ++i)
{
    BigInteger aux = input[i];
    input[i] = input[permutation[i]];
    input[permutation[i]] = aux;
}

編集:「フィッシャー-イェーツ」シャッフルの使用に関するスティーブの発言に続いて、getRandomPermutationそれに応じて関数を変更しました。

std::map<unsigned int, unsigned int> getRandomPermutation (const unsigned int &numberOfElements)
{
    std::map<unsigned int, unsigned int> permutation;

    //populate the map
    for (unsigned int i = 0; i < numberOfElements; i++)
    {
        permutation[i] = i;
    }

    //randomize it
    for (unsigned int i = numberOfElements - 1; i > 0; --i)
    {
        //generate a random number in the interval [0, numberOfElements)
        unsigned long randomValue = GetRandomInteger(i);

        std::swap(permutation[i], permutation[randomValue]);
    }

    return permutation;
}
4

4 に答える 4

4

n個の要素のベクトルを「ランダム化」している場合は、別の、を作成しstd::vector<size_t> index(n)、に設定index[x] = xして0 <= x < nから、シャッフルすることができますindex。次に、ルックアップは次の形式になりますoriginal_vector[index[i]]。元のベクトルの順序は変更されないため、順序を復元する必要はありません。

...カスタム乱数ジェネレータークラスの使用に制約があるため、使用できないと思いますstd::random_shuffle...

この過負荷に気づきましたか?

template <class RandomAccessIterator, class RandomNumberGenerator>
void random_shuffle ( RandomAccessIterator first, RandomAccessIterator last,
                    RandomNumberGenerator& rand );

互換性のあるオブジェクトで乱数ジェネレーターをラップする方法の詳細については、http://www.sgi.com/tech/stl/RandomNumberGenerator.htmlを参照してください。

于 2012-06-22T01:42:28.690 に答える
2

コードで特定のエラーを探している場合:

permutation[i] = randomValue;
permutation[randomValue] = i;

間違っている。終了すると、各値がマップの値の中で1回だけ表示されるとは限らないことに注意してください。したがって、これは順列ではなく、均一に分散されたランダムな順列は言うまでもありません。

ランダム順列を生成する適切な手段は、トニーが言うことstd::random_shuffleです。最初に単位元順列を表すベクトルで使用します。または、シャッフルが適切に実行される方法を知りたい場合は、「Fisher-Yates」を検索してください。N一般に、ランダムな選択を均一に行うアプローチは、実行できる可能性0 .. N-1があることを意味するため、失敗する運命にありN^Nます。ただし、アイテムN!の順列が存在する可能性があり、通常はで割り切れません。したがって、各順列が同数のランダムな選択の結果であるということは不可能です。つまり、分布は均一ではありません。NN^NN!

問題は、アルゴリズムをどのように実装するかです。

inputつまり、順列があり、その順列に従って、インプレースの要素を並べ替えたいと考えています。

知っておくべき重要なことは、すべての順列は「サイクル」の構成であるということです。つまり、特定の開始点から順列を繰り返したどると、開始した場所に戻ります(このパスは、その開始点が属するサイクルです)。与えられた順列にそのようなサイクルが複数存在する可能性があり、ある場合permutation[i] == iiのサイクルのi長さは1になります。

サイクルはすべて互いに素です。つまり、各要素は正確に1つのサイクルで表示されます。サイクルは互いに「干渉」しないため、各サイクルを適用することで順列を適用でき、任意の順序でサイクルを実行できます。したがって、インデックスごとに次のことを行うi必要があります。

  • すでに完了しているかどうかを確認してくださいi。その場合は、次のインデックスに進みます。
  • セットするcurrent = i
  • と交換index[current]index[permutation[current]]ます。したがってindex[current]、は正しい値(サイクルの次の要素)に設定され、その古い値はサイクルに沿って前方に「プッシュ」されます。
  • current「完了」としてマーク
  • の場合、サイクルは終了しましたpermutuation[current]iしたがって、サイクルの最初の値は、以前はサイクルの最後の要素によって占められていたスポットになります。これは正しいことです。次のインデックスに移動します。
  • 設定current = permutation[current]して、スワップ手順に戻ります。

関係するタイプによっては、スワップを最適化できます。一時変数と各サイクルの開始にコピー/移動してから、サイクルの各ステップでスワップの代わりにコピー/移動を実行する方がよい場合があります。最後に、一時変数をサイクルの最後にコピー/移動します。

プロセスを逆にすることは同じですが、順列の「逆」を使用します。inv順列の逆は、各permのような順列です。逆数を計算して上記の正確なコードを使用するか、各サイクルに沿って要素を反対方向に移動することを除いて、上記と同様のコードを使用できます。inv[perm[i]] == ii

これに代わる方法として、Fisher-Yatesを自分で実装したため、Fisher-Yatesを実行しているときに、実行するスワップごとに、スワップされた2つのインデックスを記録しますvector<pair<size_t,size_t>>。そうすれば、サイクルについて心配する必要はありません。同じ一連のスワップを適用することにより、順列をベクトルに適用できます。スワップの逆のシーケンスを適用することにより、順列を逆にすることができます。

于 2012-06-22T02:00:32.883 に答える
1

アプリケーションによっては、真に均一に分散された順列があることが重要な場合、一般的な疑似乱数ジェネレーターを複数回呼び出すアルゴリズムを使用できないことに注意してください。

その理由は、clibにあるものなど、ほとんどの疑似乱数ジェネレーターが線形合同法であるためです。それらには、平面にクラスター化する数を生成するという弱点があります。そのため、順列は完全に均一に分散されません。高品質のジェネレーターを使用すると、それを回避できます。

http://en.wikipedia.org/wiki/Linear_congruential_generatorを参照してください

または、0 ..(n!-1)の範囲で単一の乱数を生成し、それを順列のランク付けされていない関数に渡すこともできます。nが十分に小さい場合は、それらを格納して定数時間アルゴリズムを取得できますが、nが大きすぎる場合は、ランク付けされていない関数として最適なのはO(n)です。結果の順列を適用すると、とにかくO(n)になります。

于 2013-02-27T16:03:08.207 に答える
0

要素の順序付けられたシーケンスが与えられたa,b,c,d,e場合、最初に新しいインデックス付きシーケンスを作成しますX=(0,a),(1,b),(2,c),(3,d),(4,e)。次に、そのシーケンスをランダムにシャッフルし、各ペアの2番目の要素を取得して、ランダムシーケンスを取得します。元のシーケンスを復元するにはX、各ペアの最初の要素を使用してセットを段階的に並べ替えます。

于 2012-06-22T01:15:55.303 に答える