创建无重复的随机数序列

时间:2009-03-29 00:40:36

标签: c++ c algorithm random

重复:

  

Unique random numbers in O(1)?

我想要一个伪随机数生成器,它可以按随机顺序生成没有重复的数字。

例如:

随机(10)

可能会回来 5,9,1,4,2,8,3,7,6,10

除了制作数字范围并对其进行改组,或者检查生成的重复列表之外,还有更好的方法吗?


编辑:

此外,我希望它能够在没有整个范围的情况下有效地生成大数字。


编辑:

我看到每个人都建议使用随机算法。但是,如果我想生成大的随机数(1024字节+),那么该方法将占用更多的内存,而不是我刚使用常规RNG并插入到Set中,直到它是指定的长度,对吧?对此没有更好的数学算法。

28 个答案:

答案 0 :(得分:28)

您可能对线性反馈移位寄存器感兴趣。 我们曾经用硬件来构建它们,但我也用软件完成了它们。它使用一个移位寄存器,其中一些位被xor'ed并反馈到输入,如果你选择正确的“抽头”,你可以得到一个与寄存器大小一样长的序列。也就是说,一个16位的lfsr可以生成一个长度为65535且没有重复的序列。它在统计上是随机的,但当然是非常可重复的。此外,如果它做错了,你可以得到一些令人尴尬的短序列。如果您查找lfsr,您将找到如何正确构造它们的示例(也就是说,“最大长度”)。

答案 1 :(得分:17)

shuffle是一种非常好的方法(如果您不使用朴素算法引入偏差)。请参阅Fisher-Yates shuffle

答案 2 :(得分:16)

为了确保列表不重复,必须保留先前返回的数字列表。因为它必须在算法结束时生成整个列表,这相当于生成有序列表然后改组的存储要求。

更多关于改组的信息:Creating a random ordered list from an ordered list

但是,如果随机数的范围非常大,但所需数字的数量很少(您已经暗示这是评论中的实际要求),那么生成一个完整的列表并将其洗牌是浪费的。大型阵列上的随机播放涉及以某种方式访问​​虚拟内存页面(根据定义)将破坏操作系统的分页系统(在较小的范围内,CPU的内存缓存会出现同样的问题)。

在这种情况下,搜索列表到目前为止效率会更高。因此,理想的做法是使用启发式(由实验确定)为给定的参数选择正确的实现。 (在C#而不是C ++中给出示例的道歉但ASFAC++B我正在训练自己用C#进行思考。

IEnumerable<int> GenerateRandomNumbers(int range, int quantity)
{
    int[] a = new int[quantity];

    if (range < Threshold)
    {
        for (int n = 0; n < range; n++)
            a[n] = n;

        Shuffle(a);
    }
    else
    {
        HashSet<int> used = new HashSet<int>();

        for (int n = 0; n < quantity; n++)
        {
            int r = Random(range);

             while (!used.Add(r))
                 r = Random(range);

             a[n] = r;
        }
    }

    return a;
}

检查重复数字的成本,存在冲突时的循环等将是昂贵的,但是可能会有一些Threshold值,它会比分配整个范围更快。< / p>

对于足够小的数量要求,使用used数组并在其中进行线性搜索可能会更快,因为地点更大,开销更低,比较便宜......

对于大量和大范围,最好在请求时返回在序列中产生数字的对象,而不是预先为结果分配数组。由于yield return关键字:

,这在C#中很容易实现
IEnumerable<int> ForLargeQuantityAndRange(int quantity, int range)
{
    for (int n = 0; n < quantity; n++)
    {
        int r = Random(range);

        while (!used.Add(r))
            r = Random(range);

        yield return r;
    }
}

答案 3 :(得分:7)

如果保证随机数永不重复,则随机数不再是随机数,随机性的数量会随着数字的生成而减少(9个数字random(10)之后是可预测的,甚至在只有八个你有50%的机会。)

答案 4 :(得分:4)

我知道tou不想要大范围的随机播放,因为你必须存储整个列表才能这样做。

相反,使用可逆的伪随机哈希。然后依次输入值0 1 2 3 4 5 6等。

这样有无限数量的哈希。如果它们被限制为2的幂,它们就不会太难产生,但可以使用任何基数。

例如,如果您想要遍历所有2 ^ 32 32位值,那么这将是有用的。这是最容易编写的,因为在这种情况下,整数数学的隐式mod 2 ^ 32对你有利。

unsigned int reversableHash(unsigned int x)
{
   x*=0xDEADBEEF;
   x=x^(x>>17);
   x*=0x01234567;
   x+=0x88776655;
   x=x^(x>>4);
   x=x^(x>>9);
   x*=0x91827363;
   x=x^(x>>7);
   x=x^(x>>11);
   x=x^(x>>20);
   x*=0x77773333;
   return x;
}

答案 5 :(得分:3)

对于特定范围内没有重复的随机数,您可以进行随机播放。您描述的方法(随机生成数字并将它们放入Set中直到达到指定长度)效率较低的原因是因为重复。从理论上讲,该算法可能永远不会完成。与shuffle相比,它最多会在不确定的时间内完成,而shuffle将始终在高度可预测的时间内运行。

<小时/> 对编辑和评论的回应:

如果您在评论中指出,数字的范围非常大,并且您希望随机选择相对较少的数字且没有重复,那么重复的可能性会迅速减少。范围和选择数量之间的大小差异越大,重复选择的可能性越小,并且您在问题中描述的选择和检查算法的性能越好。

答案 6 :(得分:3)

如果您不介意平庸的随机性属性,并且元素的数量允许,那么您可以使用linear congruential random number generator

答案 7 :(得分:2)

如何使用GUID生成器(如.NET中的那个)。虽然不能保证不会有重复,但获得一个的机会很低。

答案 8 :(得分:1)

第二个gbarry关于使用LFSR的答案。即使在软件中,它们也非常高效且易于实现,并且保证不会在(2 ^ N - 1)次使用中重复使用具有N位移位寄存器的LFSR。

然而,有一些缺点:通过观察来自RNG的少量输出,可以重建LFSR并预测它将生成的所有值,使得它们不能用于加密,并且任何地方都需要良好的RNG。第二个问题是根据LFSR实现,全零字或全部(以位为单位)字是无效的。与您的问题相关的第三个问题是LFSR产生的最大数量总是2 - 1(或2 - 2的幂)的幂。

根据您的应用,第一个缺点可能不是问题。从你给出的例子来看,似乎你并不期望零成为答案之一;所以,第二个问题似乎与你的情况无关。 通过重复使用LFSR直到得到范围内的数字,可以解决最大值(以及范围)问题。这是一个例子:

假设您希望数字介于1到10之间(如您的示例所示)。您将使用范围为[1,15]的4位LFSR。这是一个关于如何获得[1,10]范围内的数字的伪代码:

x = LFSR.getRandomNumber();
while (x > 10) {
   x = LFSR.getRandomNumber();
}

您应该将以前的代码嵌入RNG中;这样调用者就不会关心实现了。 请注意,如果您使用大型移位寄存器,这会降低您的RNG速度,并且您想要的最大数字不是2 - 1的幂。

答案 9 :(得分:1)

以前曾经问过这个问题 - 请参阅my answer to the previous question。简而言之:您可以使用分组密码在您想要的任何范围内生成安全(随机)排列,而无需在任何位置存储整个排列。

答案 10 :(得分:1)

如果要创建没有重复的大(例如,64位或更多)随机数,则只需创建它们。如果你使用一个好的随机数发生器,实际上有足够的熵,那么产生重复的几率非常微小,以至于不值得担心。

例如,在生成加密密钥时,实际上没有人会检查他们之前是否生成了相同的密钥;因为你相信你的随机数生成器,专用的攻击者无法获得相同的密钥,那么为什么你会不会意外地拿出相同的密钥呢?

当然,如果你有一个糟糕的随机数生成器(比如Debian SSL random number generator vulnerability),或者生成足够小的数字而birthday paradox给你很高的碰撞机会,那么你需要实际做一些事情,以确保你不重复。但对于具有良好生成器的大型随机数,只要相信概率不会给你任何重复。

答案 11 :(得分:1)

在生成数字时,使用Bloom filter检测重复项。这将使用最少量的内存。根本不需要在系列中存储早期的数字。

权衡是你的名单在你的范围内无法详尽无遗。如果你的数字确实在256 ^ 1024的数量级,那几乎没有任何折衷。

(当然,如果它们实际上是随机的那么规模,那么即使打扰检测重复也是浪费时间。如果地球上的每台计算机都产生了数万亿随机数,每秒钟大小,数万亿年,就会发生碰撞的可能性仍然可以忽略不计。)

答案 12 :(得分:0)

static std::unordered_set<long> s;
long l = 0;
for(; !l && (s.end() != s.find(l)); l = generator());
v.insert(l);

generator()是你的随机数生成器。只要条目不在您的集合中,您就会滚动数字,然后添加您在其中找到的内容。你明白了。

我为这个例子做了很长时间,但如果您的PRNG被模板化,那么你应该把它作为模板。

替代方案是使用加密安全的PRNG,生成两倍于相同数字的概率非常低。

答案 13 :(得分:0)

对于随机序列,不应该有任何自相关。数字不应重复的限制意味着下一个数字应该取决于所有以前的数字,这意味着它不再是随机数....

答案 14 :(得分:0)

如果您不是指生成序列的统计特性差,则有一种方法:

假设您要生成N个数字,每个数字各1024位。您可以牺牲一些生成的数字作为“计数器”。

因此,您生成每个随机数,但是在某些位中,您选择放置二进制编码计数器(从变量开始,每次生成下一个随机数时都会增加)。

您可以将该数字拆分为单个位,并将其放入生成数字的一些不太重要的位中。

这样你就可以确保每次获得唯一的号码。

我的意思是例如每个生成的数字看起来像这样: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxyyxxxxyxyyyyxxyxx 其中x直接来自生成器,ys取自计数器变量。

答案 15 :(得分:0)

Mersenne twister

其中的描述可以在维基百科上找到:Mersenne twister

请查看页面底部,了解各种语言的实现。

答案 16 :(得分:0)

问题是从1..M范围内选择N个唯一数字的“随机”序列,其中对N和M之间的关系没有约束(M可以更大,大约相同,甚至更小比N;他们可能不是相对素数)。

扩展线性反馈移位寄存器回答:对于给定的M,构造最大LFSR,用于大于M的2的最小幂。然后从LFSR中获取数字,丢弃大于M的数字。平均,你将抛出最多一半生成的数字(因为构造超过LFSR的一半范围小于M),因此获得数字的预期运行时间是O(1)。您不存储以前生成的数字,因此空间消耗也是O(1)。如果在获得N个数字之前循环,则M小于N(或者LFSR构造不正确)。

你可以在这里找到最大长度LFSR高达168位的参数(来自维基百科):http://www.xilinx.com/support/documentation/application_notes/xapp052.pdf

这是一些java代码:

/ **  *在[0,M)中生成一系列唯一的“随机”数字  * @author dkoes  *  * /

公共类UniqueRandom {     长lfsr;     长面具;     长最大;

private static long seed = 1;
//indexed by number of bits
private static int [][] taps = {
        null, // 0
        null, // 1
        null, // 2
        {3,2}, //3
        {4,3},
        {5,3},
        {6,5},
        {7,6},
        {8,6,5,4},
        {9,5},
        {10,7},
        {11,9},
        {12,6,4,1},
        {13,4,3,1},
        {14,5,3,1},
        {15,14},
        {16,15,13,4},
        {17,14},
        {18,11},
        {19,6,2,1},
        {20,17},
        {21,19},
        {22,21},
        {23,18},
        {24,23,22,17},
        {25,22},
        {26,6,2,1},
        {27,5,2,1},
        {28,25},
        {29,27},
        {30,6,4,1},
        {31,28},
        {32,22,2,1},
        {33,20},
        {34,27,2,1},
        {35,33},
        {36,25},
        {37,5,4,3,2,1},
        {38,6,5,1},
        {39,35},
        {40,38,21,19},
        {41,38},
        {42,41,20,19},
        {43,42,38,37},
        {44,43,18,17},
        {45,44,42,41},
        {46,45,26,25},
        {47,42},
        {48,47,21,20},
        {49,40},
        {50,49,24,23},
        {51,50,36,35},
        {52,49},
        {53,52,38,37},
        {54,53,18,17},
        {55,31},
        {56,55,35,34},
        {57,50},
        {58,39},
        {59,58,38,37},
        {60,59},
        {61,60,46,45},
        {62,61,6,5},
        {63,62},
};

//m is upperbound; things break if it isn't positive
UniqueRandom(long m)
{
    max = m;
    lfsr = seed; //could easily pass a starting point instead
    //figure out number of bits
    int bits = 0;
    long b = m;
    while((b >>>= 1) != 0)
    {
        bits++;
    }
    bits++;

    if(bits < 3)
        bits = 3; 

    mask = 0;
    for(int i = 0; i < taps[bits].length; i++)
    {
        mask |= (1L << (taps[bits][i]-1));
    }

}

//return -1 if we've cycled
long next()
{
    long ret = -1;
    if(lfsr == 0)
        return -1;
    do {
        ret = lfsr;
        //update lfsr - from wikipedia
        long lsb = lfsr & 1;
        lfsr >>>= 1;
        if(lsb == 1)
            lfsr ^= mask;

        if(lfsr == seed)            
            lfsr = 0; //cycled, stick

        ret--; //zero is stuck state, never generated so sub 1 to get it
    } while(ret >= max);

    return ret;
}

}

答案 17 :(得分:0)

这个答案提出了一些策略,可以使用一些已知的算法来获取您想要的内容并确保它们是随机顺序。

有一个内部版本的Fisher-Yates shuffle算法,称为Durstenfeld版本,在加载数组或集合时,将顺序获取的项目随机分配到数组和集合中。

要记住的一件事是Fisher-Yates(AKA Knuth)shuffle或在加载时使用的Durstenfeld版本对于对象数组是高效的,因为只有对象的引用指针被移动而对象本身没有作为算法的一部分,必须检查或与任何其他对象进行比较。

我将在下面进一步给出两种算法。

如果你想要非常大的随机数,大约1024字节或更多,一个非常好的随机生成器可以一次生成无符号字节或单词就足够了。随机生成所需数量的字节或单词,构造数字,使其成为一个带有引用指针的对象,嘿,你有一个非常大的随机整数。如果需要特定的非常大的范围,可以在字节序列的低位端添加零字节的基值,以将值向上移位。这可能是您的最佳选择。

如果你需要消除真正庞大的随机数的重复,那就更难了。即使有非常大的随机数,删除重复项也会使它们显着偏差而且根本不随机。如果你有一大堆非重复的非常庞大的随机数并且你从那些尚未选择的那些中随机选择,那么偏差只是为真正庞大的数字集创建巨大值的偏差。 Durstenfeld的Yates-Fisher版本的反向版本可用于从一组非常庞大的值中随机选择值,将其从剩余的值中移除,从中选择它们并将它们插入到一个新的数组中,这个数组是一个子集,可以做这只是源和目标数组原位。这将是非常有效的。

这可能是一个很好的策略,可以从一个非常大的值中获取具有巨大值的少量随机数,而不会重复它们。只需选择源集中的随机位置,获取其值,将其值与源集中的顶部元素交换,将源集的大小减小为1,并使用缩小的源集重复,直到选择了足够的值。这对于Durstenfeld版本的Fisher-Yates来说恰恰相反。然后,您可以使用Dursenfeld版本的Fisher-Yates算法将获取的值插入目标集。然而,这是过度的,因为它们应该随机选择并随机排序,如下所示。

两种算法都假设您有一些随机数实例方法nextInt(int setSize),它生成从零到setSize的随机整数,这意味着有setSize可能的值。在这种情况下,它将是数组的大小,因为数组的最后一个索引是size-1。

第一种算法是Durstenfeld版本的Fisher-Yates(又名Knuth)混洗算法,应用于任意长度的数组,简单地将整数从0到数组的长度随机定位到数组中。数组不必是整数数组,但可以是顺序获取的任何对象的数组,有效地使其成为引用指针数组。它简单,简短,非常有效

int size = someNumber;
int[] int array = new int[size]; // here is the array to load
int location; // this will get assigned a value before used
// i will also conveniently be the value to load, but any sequentially acquired
// object will work
for (int i = 0; i <= size; i++) { // conveniently, i is also the value to load
      // you can instance or acquire any object at this place in the algorithm to load
      // by reference, into the array and use a pointer to it in place of j
      int j = i; // in this example, j is trivially i
    if (i == 0) { // first integer goes into first location
        array[i] = j; // this may get swapped from here later
    } else { // subsequent integers go into random locations
            // the next random location will be somewhere in the locations
            // already used or a new one at the end
            // here we get the next random location
            // to preserve true randomness without a significant bias
            // it is REALLY IMPORTANT that the newest value could be
            // stored in the newest location, that is, 
            // location has to be able to randomly have the value i
            int location = nextInt(i + 1); // a random value between 0 and i
            // move the random location's value to the new location
            array[i] = array[location];
            array[location] = j; // put the new value into the random location
    } // end if...else
} // end for

瞧,你现在已经有了一个随机阵列。

如果你想随机洗牌你已经拥有的数组,这里是标准的Fisher-Yates算法。

type[] array = new type[size];

// some code that loads array...

// randomly pick an item anywhere in the current array segment, 
// swap it with the top element in the current array segment,
// then shorten the array segment by 1
// just as with the Durstenfeld version above,
// it is REALLY IMPORTANT that an element could get
// swapped with itself to avoid any bias in the randomization
type temp; // this will get assigned a value before used
int location; // this will get assigned a value before used
for (int i = arrayLength -1 ; i > 0; i--) {
    int location = nextInt(i + 1);
    temp = array[i];
    array[i] = array[location];
    array[location] = temp;
} // end for

对于有序集合和集合,即某种类型的列表对象,您可以使用带有索引值的添加/或插入,允许您在任何地方插入项目,但它必须允许在当前最后一项之后添加或追加避免在随机化中产生偏差。

答案 18 :(得分:0)

这是一种随机而不重复结果的方法。它也适用于字符串。它在C#中,但logig应该可以在很多地方使用。将随机结果放入列表中,并检查新的随机元素是否在该列表中。如果不是你有一个新的随机元素。如果它在该列表中,则重复随机,直到获得不在该列表中的元素。

List<string> Erledigte = new List<string>();
private void Form1_Load(object sender, EventArgs e)
{
    label1.Text = "";
    listBox1.Items.Add("a");
    listBox1.Items.Add("b");
    listBox1.Items.Add("c");
    listBox1.Items.Add("d");
    listBox1.Items.Add("e");
}

private void button1_Click(object sender, EventArgs e)
{
    Random rand = new Random();
    int index=rand.Next(0, listBox1.Items.Count);
    string rndString = listBox1.Items[index].ToString();

    if (listBox1.Items.Count <= Erledigte.Count)
    {
        return;
    }
    else
    {
        if (Erledigte.Contains(rndString))
        {
            //MessageBox.Show("vorhanden");
            while (Erledigte.Contains(rndString))
            {
                index = rand.Next(0, listBox1.Items.Count);
                rndString = listBox1.Items[index].ToString();
            }
        }

        Erledigte.Add(rndString);
        label1.Text += rndString;
    }
}

答案 19 :(得分:0)

之前我问了一个类似的问题,但我的整个范围都是Looking for a Hash Function /Ordered Int/ to /Shuffled Int/

答案 20 :(得分:0)

请在

查看答案

Generate sequence of integers in random order without constructing the whole list upfront

并且我的答案就在那里

 very simple random is 1+((power(r,x)-1) mod p) will be from 1 to p for values of x from 1 to p and will be random where r and p are prime numbers and r <> p.

答案 21 :(得分:0)

假设您想要生成一系列256个随机数而不重复。

  1. 创建一个用零初始化的256位(32字节)内存块,我们称之为b
  2. 您的循环变量将为n,即尚未生成的数字数
  3. n = 256循环到n = 1
  4. r
  5. 范围内生成随机数[0, n)
  6. 在内存块r中找到b - 第0位,我们称之为p
  7. p放入结果列表中,即名为q
  8. 的数组
  9. 将内存块p中的b位翻转为1
  10. n = 1通过后,您已完成生成数字列表
  11. 以下是我正在讨论的一个简短示例,最初使用n = 4:

    **Setup**
    b = 0000
    q = []
    
    **First loop pass, where n = 4**
    r = 2
    p = 2
    b = 0010
    q = [2]
    
    **Second loop pass, where n = 3**
    r = 2
    p = 3
    b = 0011
    q = [2, 3]
    
    **Third loop pass, where n = 2**
    r = 0
    p = 0
    b = 1011
    q = [2, 3, 0]
    
    ** Fourth and final loop pass, where n = 1**
    r = 0
    p = 1
    b = 1111
    q = [2, 3, 0, 1]
    

答案 22 :(得分:0)

假设你有一个随机数或伪随机数生成器,即使它不能保证返回唯一值,你也可以实现一个每次使用这个代码返回唯一值的假设,假设上限保持不变(即你总是请使用random(10)进行调用,不要使用random(10); random(11)调用它。

代码不会检查错误。如果您愿意,可以自己添加 如果你想要大量的数字,它还需要大量的内存。

/* the function returns a random number between 0 and max -1
 * not necessarily unique
 * I assume it's written
 */
int random(int max);

/* the function returns a unique random number between 0 and max - 1 */
int unique_random(int max)
{

    static int *list = NULL;    /* contains a list of numbers we haven't returned */
    static int in_progress = 0; /* 0 --> we haven't started randomizing numbers
                                 * 1 --> we have started randomizing numbers
                                 */

    static int count;
    static prev_max = 0;

    // initialize the list
    if (!in_progress || (prev_max != max)) {
        if (list != NULL) {
            free(list);
        }
        list = malloc(sizeof(int) * max);
        prev_max = max;
        in_progress = 1;
        count = max - 1;

        int i;
        for (i = max - 1; i >= 0; --i) {
            list[i] = i;
        }
    }

    /* now choose one from the list */
    int index = random(count);
    int retval = list[index];

    /* now we throw away the returned value.
     * we do this by shortening the list by 1
     * and replacing the element we returned with
     * the highest remaining number
     */
    swap(&list[index], &list[count]);

    /* when the count reaches 0 we start over */
    if (count == 0) {
        in_progress = 0;
        free(list);
        list = 0;
    } else { /* reduce the counter by 1 */
        count--;
    }
}

/* swap two numbers */
void swap(int *x, int *y)
{
    int temp = *x;
    *x = *y;
    *y = temp;
}

答案 23 :(得分:0)

改组N元素不会占用过多的内存......想一想。您一次只能交换一个元素,因此使用的最大内存是N + 1个元素。

答案 24 :(得分:-1)

实际上,这里有一个小问题;不允许重复的随机数生成器不是随机的。

答案 25 :(得分:-1)

要有非重复的随机数并避免使用检查双打号码的时间,并反复获取新数字,请使用以下方法,以确保Rand的最低使用率: 例如,如果您想获得100个非重复随机数: 1.填充数字从1到100的数组 2.使用范围为(1-100)的Rand函数得到一个随机数 3.使用genarted随机数作为索引从数组中获取值(Numbers [IndexGeneratedFromRandFunction] 4.将该索引后的数组中的数字移到左侧 5.从步骤2重复,但现在响铃应该是(1-99)并继续

答案 26 :(得分:-1)

如果你可以生成'小'随机数,你可以通过整合它们来生成'大'随机数:为每个'前一个'添加一个小的随机增量。

const size_t amount = 100; // a limited amount of random numbers
vector<long int> numbers; 
numbers.reserve( amount );
const short int spread = 250; // about 250 between each random number
numbers.push_back( myrandom( spread ) );
for( int n = 0; n != amount; ++n ) {
    const short int increment = myrandom( spread );
    numbers.push_back( numbers.back() + increment );
}

myshuffle( numbers );

我特此慷慨委托给他人的myrandommyshuffle函数:)

答案 27 :(得分:-1)

现在我们有一个不同数字的数组!

int main() {
    int b[(the number
    if them)];
    for (int i = 0; i < (the number of them); i++) {
    int a = rand() % (the number of them + 1) + 1;
    int j = 0;
    while (j < i) {
        if (a == b[j]) {
        a = rand() % (the number of them + 1) + 1;
        j = -1;
        }
        j++;
    }
    b[i] = a;
    }
}