假设您想以随机顺序迭代序列[0到n],只访问每个元素一次。有没有办法在 O (1)内存中执行此操作,即不使用std::iota
创建[1..n]序列并通过std::random_shuffle
运行它?
某种迭代器以随机顺序吐出序列将是最佳的。
要求是应该可以通过选择另一个种子来获得另一个随机顺序。
答案 0 :(得分:8)
如果你可以就地改变序列,你可以简单地从0-N重复绘制一个随机数,然后删除你访问过的元素,或者将它交换到最后,或者这样的方案。
答案 1 :(得分:6)
理论上,如果你构建了一个随机数生成器,其周期正好是 n ,并且覆盖了0..n中的所有值,那么运行一次这样可以得到你喜欢的东西。
当然,这可能不是一般解决方案,至少如果你正在寻找动态的东西,因为你必须预先创建PRNG,你如何做到这一点取决于n。
答案 2 :(得分:1)
嗯......想一想。你怎么知道之前访问过哪些元素?
简短的回答:你做不到。 (编辑好吧,除非你计算无状态的伪随机生成器,但正如你在命令中说明的那样,这对于一般情况似乎不可行)
然而,根据实际顺序,'标记'元素可能是可行的访问 _in-place _ 因此技术上需要O(n)存储,但是算法的没有额外的存储
示例:
const int VISITED_BIT = 0x8000; // arbitrary example
bool extract(int i) { return (i & ~VISITED_BIT); }
bool visited(int i) { return (i & VISITED_BIT); }
bool markvisited(int& i) { i |= VISITED_BIT); }
int main()
{
std::vector<int> v = {2,3,4,5,6};
int remain = v.size();
while (remain>0)
{
size_t idx = rand(); // or something
if (visited(v[idx]))
continue;
std::cout << "processing item #" << idx << ": " << extract(v[idx]) << "\n";
markvisited(v[idx]);
remain--;
}
}
答案 3 :(得分:1)
与大多数算法问题一样,存在时空权衡;如果您乐意使用O(n ^ 2)时间生成所有排列,则可以在O(1)空间中解决此问题。除了几个临时变量之外,唯一需要的存储是随机数种子本身(或者,在本例中是PRNG对象),因为这足以重新生成伪随机数序列。
请注意,您必须在每次通话时为此功能提供相同的PRNG,并且您不能将其用于任何其他目的。
#include <random>
template<typename PRNG, typename INT>
INT random_permutation_element(INT k, INT n, PRNG prng) {
typedef std::uniform_int_distribution<INT> dis;
INT i = 0;
for (; i < k; ++i) dis(0, i)(prng);
INT result = dis(0, i)(prng);
for (++i; i < n; ++i) if (dis(0, i)(prng) <= result) ++result;
return result;
}
这是一个快速而肮脏的安全带。 ./test 1000 3
生成1000个完整的长度为3的排列; ./test 10 1000000 0 5
生成长度为100万的10种排列中的每一种的前五个元素。
#include <iostream>
int main(int argc, char** argv) {
std::random_device rd;
std::mt19937 seed_gen(rd());
int count = std::stoi(argv[1]);
int size = std::stoi(argv[2]);
int seglow = 0;
int seglim = size;
if (argc > 3) seglow = std::stoi(argv[3]);
if (argc > 4) seglim = std::stoi(argv[4]);
while (count-- > 0) {
std::mt19937 prng(seed_gen());
for (int i = seglow; i < seglim; ++i)
std::cout << random_permutation_element(i, size, prng)
<< (i < seglim - 1 ? ' ' : '\n');
}
return 0;
}
如果你不太可能完成任何给定的排列,有一种更快的方法可以做到这一点,但这种写作方式看起来更好,并且可能更容易理解。 (另一种方法是以相反的顺序生成数字,这意味着你可以在生成k之后停止,但你必须做两次,首先得到结果然后调整它。)
答案 4 :(得分:0)
不,没有,想一想,程序必须记住它访问过的地方。如果有一个可以随机访问它们的迭代器,迭代器内部必须以某种方式跟踪它,你仍然会使用内存。
答案 5 :(得分:0)
我刚为这类事物构建了一个结构 - 我生成了一个Heap结构(min或max,无关紧要)。但是为了比较,我使用随机数而不是使用键值。因此,插入堆中的项目以随机顺序放置。然后你可以返回形成堆的基本结构的数组(将随机排序),或者你可以逐个弹出元素,然后以随机顺序返回它们。如果将这种类型的容器用作主存储(而不是将数组与堆分开),则不存在额外的内存复杂性,因为无论如何它只是一个数组。时间复杂度是插入的O(log N),弹出顶部元素的O(log N)。改组就像弹出和重新插入每个元素一样简单,O(N log N)。
我甚至构建了一个花哨的枚举器(它是C#,但你可以用C ++迭代器做同样的事情),在你迭代到最后之后自动改组。这意味着每次你可以多次遍历列表(没有弹出)并且每次都获得不同的顺序,代价是每次完整迭代后的O(N log N)shuffle。 (想想就像一副纸牌。在每张卡片进入丢弃堆后,你重新洗牌,以便下次不能以相同的顺序进行。)