我无法找出一种在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
中的数据,所以我甚至可以将其设为非常量,覆盖它,并跳过创建它的副本,但问题是如何实现算法? / p>
如果我做这样的事情,我最终会用脚射击自己,对吧? :)
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;
}
编辑:在Steve关于使用“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;
}
答案 0 :(得分: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
答案 1 :(得分:2)
如果您在代码中查找特定错误:
permutation[i] = randomValue;
permutation[randomValue] = i;
错了。观察一旦完成,每个值不一定在地图的值中恰好出现一次。所以这不是一种排列,更不用说均匀分布的随机排列了。
产生随机排列的正确方法是Tony所说的,在最初代表身份排列的向量上使用std::random_shuffle
。或者,如果您想知道如何正确执行洗牌,请查看“Fisher-Yates”。通常,任何从N
统一0 .. N-1
随机选择的方法都注定要失败,因为这意味着它有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
,我们就完成了这个周期。因此,循环的第一个值最终位于以前由循环的最后一个元素占据的位置,这是正确的。转到下一个索引。current = permutation[current]
并返回交换步骤。根据所涉及的类型,您可以围绕交换优化 - 复制/移动到临时变量和每个周期的开始可能更好,然后在每个步骤执行复制/移动而不是交换循环,最后复制/移动临时到循环结束。
反转过程是相同的,但使用置换的“逆”。排列inv
的倒数perm
是排列,inv[perm[i]] == i
为每个i
。你可以计算逆变量并使用上面的确切代码,或者你可以使用类似于上面的代码,除了在每个循环中沿相反方向移动元素。
除此之外,由于您自己实施了Fisher-Yates,因为您正在运行Fisher-Yates,对于每个交换,您执行记录在vector<pair<size_t,size_t>>
中交换的两个索引。那你就不用担心周期了。您可以通过应用相同的交换序列将置换应用于向量。您可以通过应用相反的互换序列来反转置换。
答案 2 :(得分:1)
请注意,根据您的应用程序,如果您具有真正均匀分布的排列很重要,则不能使用任何一次调用典型伪随机数生成器的算法。
原因是大多数伪随机数生成器(例如clib中的伪随机数生成器)是线性同余的。那些有弱点的地方它们会产生在平面上聚集的数字 - 所以你的排列不会完全均匀分布。使用更高质量的发电机应该可以解决这个问题。
请参阅http://en.wikipedia.org/wiki/Linear_congruential_generator
或者,您可以在0 ..(n!-1)范围内生成一个随机数,并将其传递给排列的unrank函数。对于足够小的n,你可以存储它们并获得一个恒定时间算法,但如果n太大,那么最好的unrank函数是O(n)。无论如何,应用得到的排列将是O(n)。
答案 3 :(得分:0)
给定有序的元素序列a,b,c,d,e
,首先要创建一个新的索引序列:X=(0,a),(1,b),(2,c),(3,d),(4,e)
。然后,您随机地移动该序列并获得每对的第二个元素以获得随机序列。要恢复原始序列,请使用每对中的第一个元素逐步对X
集进行排序。