繁体   English   中英

随机排列

[英]Random Permutations

我无法找出一个随机改组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;
}

有些东西告诉我,我应该只能使用一个std::vector<BigInteger>这个算法,但是,现在,我无法找出最佳解决方案。 老实说,我并不真正关心input的数据,所以我甚至可以使它成为非const,覆盖它,并跳过创建它的副本,但问题是如何实现算法?

如果我做这样的事情,我最终会用脚射击自己,对吧? :)

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;
}

编辑:在史蒂夫关于使用“Fisher-Yates”shuffle的评论之后,我相应地改变了我的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;
}

如果你正在“随机化”一个n个元素的向量,你可以创建另一个std::vector<size_t> index(n) ,设置index[x] = x0 <= x < n ,然后是shuffle 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

如果您在代码中查找特定错误:

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

是错的。 观察一旦完成,每个值不一定在地图的值中恰好出现一次。 因此,它不是一种排列,更不用说均匀分布的随机排列。

产生随机排列的正确方法是Tony所说的,在最初代表身份置换的向量上使用std::random_shuffle 或者,如果您想知道如何正确执行洗牌,请查看“Fisher-Yates”。 一般来说,任何从0 .. N-1统一进行N随机选择的方法都注定要失败,因为这意味着它有N^N种可能的运行方式。 但是有N! N项目的可能排列, N^N通常不能被N!整除N! 因此,每个排列都不可能是相同数量的随机选择的结果,即分布不均匀。

问题是如何实现算法?

所以,你有你的排列,并且你想根据那个排列重新排序input的元素。

要知道的关键是每个排列都是“循环”的组合。 也就是说,如果您反复按照给定起点的排列,则返回到您开始的位置(此路径是该起点所属的循环)。 可以存在多于一个这样的周期在给定的排列,并且如果permutation[i] == i一些i ,然后循环i具有长度1。

循环都是不相交的,也就是说每个元素恰好出现在一个循环中。 由于循环不会相互“干扰”,我们可以通过应用每个循环来应用排列,我们可以按任何顺序执行循环。 因此,对于每个索引i ,我们需要:

  • 检查我们是否已经完成了i 如果是这样,请转到下一个索引。
  • 设定current = i
  • 交换index[current]index[permutation[current]] 因此,将index[current]设置为正确的值(循环中的下一个元素),并将其旧值沿循环“向前”推进。
  • current标记为“已完成”
  • 如果permutuation[current]i ,我们就完成了循环。 因此,循环的第一个值最终位于以前由循环的最后一个元素占据的位置,这是正确的。 转到下一个索引。
  • set current = permutation[current]并返回交换步骤。

根据所涉及的类型,您可以围绕交换进行优化 - 复制/移动到临时变量和每个周期的开始可能更好,然后在周期的每个步骤执行复制/移动而不是交换,以及最后复制/移动临时到循环结束。

反转过程是相同的,但使用置换的“逆”。 置换perm的逆inv是排列,使得对于每个iinv[perm[i]] == i i 您可以计算逆并使用上面的确切代码,或者您可以使用与上面类似的代码,除了沿每个循环沿相反方向移动元素。

作为所有这些的替代方案,因为您自己实现了Fisher-Yates - 当您运行Fisher-Yates时,对于每个交换,您执行记录在vector<pair<size_t,size_t>>交换的两个索引。 那你就不用担心周期了。 您可以通过应用相同的交换序列将置换应用于向量。 您可以通过应用相反的互换序列来反转排列。

请注意,根据您的应用程序,如果您具有真正均匀分布的排列很重要,则不能使用任何一次调用典型伪随机数生成器的算法。

原因是大多数伪随机数生成器(例如clib中的伪随机数生成器)是线性同余的。 那些有弱点的地方它们会产生在平面上聚集的数字 - 所以你的排列不会完全均匀分布。 使用更高质量的发电机应该可以解决这个问题。

http://en.wikipedia.org/wiki/Linear_congruential_generator

或者,您可以只生成0 ..(n!-1)范围内的单个随机数,并将其传递给排列函数以进行排列。 对于足够小的n,你可以存储它们并获得一个恒定时间算法,但如果n太大,那么最好的unrank函数是O(n)。 无论如何,应用得到的排列将是O(n)。

给定元素a,b,c,d,e的有序序列,首先创建一个新的索引序列: X=(0,a),(1,b),(2,c),(3,d),(4,e) 然后,您随机地移动该序列并获得每对的第二个元素以获得随机序列。 要恢复原始序列,请使用每对中的第一个元素逐步对X集进行排序。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM