【问题标题】:Creating random numbers with no duplicates创建没有重复的随机数
【发布时间】:2011-05-01 16:46:53
【问题描述】:

在这种情况下,MAX 只有 5,所以我可以一一检查重复项,但我怎样才能以更简单的方式做到这一点?例如,如果 MAX 的值为 20,该怎么办? 谢谢。

int MAX = 5;

for (i = 1 , i <= MAX; i++)
{
        drawNum[1] = (int)(Math.random()*MAX)+1;

        while (drawNum[2] == drawNum[1])
        {
             drawNum[2] = (int)(Math.random()*MAX)+1;
        }
        while ((drawNum[3] == drawNum[1]) || (drawNum[3] == drawNum[2]) )
        {
             drawNum[3] = (int)(Math.random()*MAX)+1;
        }
        while ((drawNum[4] == drawNum[1]) || (drawNum[4] == drawNum[2]) || (drawNum[4] == drawNum[3]) )
        {
             drawNum[4] = (int)(Math.random()*MAX)+1;
        }
        while ((drawNum[5] == drawNum[1]) ||
               (drawNum[5] == drawNum[2]) ||
               (drawNum[5] == drawNum[3]) ||
               (drawNum[5] == drawNum[4]) )
        {
             drawNum[5] = (int)(Math.random()*MAX)+1;
        }

}

【问题讨论】:

    标签: java random


    【解决方案1】:

    使用 Java 8 使用以下代码,您可以在 1000 范围内创建 10 个不同的随机整数。

    Random random = new Random();
    Integer[] input9 = IntStream.range(1, 10).map(i -> random.nextInt(1000)).boxed().distinct()
                    .toArray(Integer[]::new);
    System.out.println(Arrays.toString(input9));
    

    修改范围以生成更多数字示例:range(1,X)。它将生成 X 个不同的随机数。

    修改nextInt值选择随机数范围:random.nextInt(Y)::随机数将在Y范围内生成

    【讨论】:

      【解决方案2】:

      这在java-8 中会简单得多:

      Stream.generate(new Random()::ints)
                  .flatMap(IntStream::boxed)
                  .distinct()
                  .limit(16) // whatever limit you might need
                  .toArray(Integer[]::new);
      

      【讨论】:

        【解决方案3】:

        在 Java 8 及更高版本中,您可以使用 IntStream 接口中的 ints 方法:

        返回一个实际上无限的伪随机 int 值流。

        Random r = new Random();
        int randomNumberOrigin = 0;
        int randomNumberBound = 10;
        int size = 5;
        int[] unique = r.ints(randomNumberOrigin, randomNumberBound)
                        .distinct()
                        .limit(size)
                        .toArray();
        

        【讨论】:

          【解决方案4】:

          最简单的方法是使用 nano DateTime 作为长格式。 System.nanoTime();

          【讨论】:

            【解决方案5】:

            我创建了一个不生成重复随机整数的 sn-p。这个sn-p的好处是可以给它分配一个数组的列表,也可以生成随机项。

            No duplication random generator class

            【讨论】:

              【解决方案6】:

              以下代码在[1,m]之间创建一个之前未生成的序列随机数。

              public class NewClass {
              
                  public List<Integer> keys = new ArrayList<Integer>();
              
                  public int rand(int m) {
                      int n = (int) (Math.random() * m + 1);
                      if (!keys.contains(n)) {
                          keys.add(n);
                          return n;
                      } else {
                          return rand(m);
                      }
                  }
              
                  public static void main(String[] args) {
                      int m = 4;
                      NewClass ne = new NewClass();
                      for (int i = 0; i < 4; i++) {
                          System.out.println(ne.rand(m));
                      }
                      System.out.println("list: " + ne.keys);
                  }
              }
              

              【讨论】:

                【解决方案7】:

                您的问题似乎减少了从 n 个元素的集合中随机选择 k 个元素。因此,Collections.shuffle 答案是正确的,但正如指出的那样效率低下:它的 O(n)。

                Wikipedia: Fisher–Yates shuffle 在数组已经存在时有一个 O(k) 版本。在您的情况下,没有元素数组,并且创建元素数组可能非常昂贵,例如 max 是 10000000 而不是 20。

                shuffle 算法涉及初始化一个大小为 n 的数组,其中每个元素都等于其索引,在一个范围内的每个数字中选取 k 个随机数,最大值小于前一个范围,然后将元素交换到数组的末尾.

                你可以在 O(k) 时间内用 hashmap 做同样的操作,虽然我承认这有点痛苦。请注意,这仅在 k 远小于 n 时才值得。 (即 k ~ lg(n) 左右),否则应该直接使用 shuffle。

                您将使用哈希图作为 shuffle 算法中支持数组的有效表示。数组中与其索引相等的任何元素都不需要出现在映射中。这使您可以在恒定时间内表示一个大小为 n 的数组,无需花费时间对其进行初始化。

                1. 选择 k 个随机数:第一个在 0 到 n-1 的范围内,第二个在 0 到 n-2 的范围内,第三个从 0 到 n-3 以此类推,直到 n-k。

                2. 将您的随机数视为一组交换。第一个随机索引交换到最终位置。第二个随机索引交换到倒数第二个位置。但是,不要使用支持数组,而是使用您的哈希图。您的 hashmap 将存储每一个不在位置的项目。

                int getValue(i) { if (map.contains(i)) return map[i]; return i; } void setValue(i, val) { if (i == val) map.remove(i); else map[i] = val; } int[] chooseK(int n, int k) { for (int i = 0; i < k; i++) { int randomIndex = nextRandom(0, n - i); //(n - i is exclusive) int desiredIndex = n-i-1; int valAtRandom = getValue(randomIndex); int valAtDesired = getValue(desiredIndex); setValue(desiredIndex, valAtRandom); setValue(randomIndex, valAtDesired); } int[] output = new int[k]; for (int i = 0; i < k; i++) { output[i] = (getValue(n-i-1)); } return output; }

                【讨论】:

                • creating the array of elements could be very expensive -- 为什么创建数组比洗牌更昂贵?我认为在这一点上绝对没有理由悲观:-)
                【解决方案8】:

                最简单的方法是创建一个可能的数字列表(1..20 或其他),然后用Collections.shuffle 随机排列它们。然后随便取多少你想要的元素。如果你的范围等于你最终需要的元素数量(例如洗一副牌),那就太好了。

                如果您想要(比如说)1..10,000 范围内的 10 个随机元素,那效果就不太好了 - 您最终会做很多不必要的工作。到那时,最好保留到目前为止生成的一组值,并在循环中不断生成数字,直到下一个不存在:

                if (max < numbersNeeded)
                {
                    throw new IllegalArgumentException("Can't ask for more numbers than are available");
                }
                Random rng = new Random(); // Ideally just create one instance globally
                // Note: use LinkedHashSet to maintain insertion order
                Set<Integer> generated = new LinkedHashSet<Integer>();
                while (generated.size() < numbersNeeded)
                {
                    Integer next = rng.nextInt(max) + 1;
                    // As we're adding to a set, this will automatically do a containment check
                    generated.add(next);
                }
                

                但请注意设置选择 - 我非常故意使用 LinkedHashSet,因为它维护插入顺序,我们在这里关心。

                另一种选择是总是取得进展,方法是每次缩小范围并补偿现有值。例如,假设您想要 0..9 范围内的 3 个值。在第一次迭代中,您将生成 0..9 范围内的任何数字 - 假设您生成 4。

                在第二次迭代中,您将生成一个 0..8 范围内的数字。如果生成的数字小于 4,您将保持原样......否则您将添加一个。这样得到的结果范围是 0..9 而没有 4。假设我们这样得到 7。

                在第三次迭代中,您将生成一个 0..7 范围内的数字。如果生成的数字小于 4,则保持原样。如果是 4 或 5,你会加一个。如果是 6 或 7,您将添加两个。这样,结果范围是 0..9,没有 4 或 6。

                【讨论】:

                  【解决方案9】:

                  另一种方法允许您使用 size 以及返回的数字的 minmax 值指定所需的数字数量

                  public static int getRandomInt(int min, int max) {
                      Random random = new Random();
                  
                      return random.nextInt((max - min) + 1) + min;
                  }
                  
                  public static ArrayList<Integer> getRandomNonRepeatingIntegers(int size, int min,
                          int max) {
                      ArrayList<Integer> numbers = new ArrayList<Integer>();
                  
                      while (numbers.size() < size) {
                          int random = getRandomInt(min, max);
                  
                          if (!numbers.contains(random)) {
                              numbers.add(random);
                          }
                      }
                  
                      return numbers;
                  }
                  

                  使用它返回 0 到 25 之间的 7 个数字。

                      ArrayList<Integer> list = getRandomNonRepeatingIntegers(7, 0, 25);
                      for (int i = 0; i < list.size(); i++) {
                          System.out.println("" + list.get(i));
                      }
                  

                  【讨论】:

                    【解决方案10】:

                    生成序列的所有索引通常不是一个好主意,因为它可能需要很多时间,特别是如果要选择的数字与MAX 的比率很低(复杂性由O(MAX) 主导)。如果要选择的数字与 MAX 的比率接近 1,则情况会变得更糟,因为从所有序列中删除所选索引也会变得昂贵(我们接近 O(MAX^2/2))。但对于少数人来说,这通常效果很好,而且不会特别容易出错。

                    使用集合过滤生成的索引也是一个坏主意,因为将索引插入序列中会花费一些时间,并且不能保证进度,因为可以多次绘制相同的随机数(但对于足够大的MAX 不太可能)。这可能接近于复杂性
                    O(k n log^2(n)/2),忽略重复项并假设集合使用树进行高效查找(但分配树节点的固定成本k 和可能不得不rebalance)。

                    另一种选择是从一开始就生成唯一的随机值,以确保取得进展。这意味着在第一轮中,会在[0, MAX] 中生成一个随机索引:

                    items i0 i1 i2 i3 i4 i5 i6 (total 7 items)
                    idx 0       ^^             (index 2)
                    

                    第二轮只生成[0, MAX - 1](因为已经选择了一项):

                    items i0 i1    i3 i4 i5 i6 (total 6 items)
                    idx 1          ^^          (index 2 out of these 6, but 3 out of the original 7)
                    

                    然后需要调整索引的值:如果第二个索引落在序列的后半部分(在第一个索引之后),则需要增加它以弥补差距。我们可以将其实现为一个循环,允许我们选择任意数量的唯一项目。

                    对于短序列,这是相当快的O(n^2/2) 算法:

                    void RandomUniqueSequence(std::vector<int> &rand_num,
                        const size_t n_select_num, const size_t n_item_num)
                    {
                        assert(n_select_num <= n_item_num);
                    
                        rand_num.clear(); // !!
                    
                        // b1: 3187.000 msec (the fastest)
                        // b2: 3734.000 msec
                        for(size_t i = 0; i < n_select_num; ++ i) {
                            int n = n_Rand(n_item_num - i - 1);
                            // get a random number
                    
                            size_t n_where = i;
                            for(size_t j = 0; j < i; ++ j) {
                                if(n + j < rand_num[j]) {
                                    n_where = j;
                                    break;
                                }
                            }
                            // see where it should be inserted
                    
                            rand_num.insert(rand_num.begin() + n_where, 1, n + n_where);
                            // insert it in the list, maintain a sorted sequence
                        }
                        // tier 1 - use comparison with offset instead of increment
                    }
                    

                    n_select_num 是你的 5,n_number_num 是你的 MAXn_Rand(x)[0, x](含)中返回随机整数。如果通过使用二分搜索找到插入点来选择很多项目(例如,不是 5 个而是 500 个),这可以更快一些。为此,我们需要确保满足要求。

                    我们将进行二分搜索,比较 n + j &lt; rand_num[j]
                    n &lt; rand_num[j] - j 相同。我们需要证明rand_num[j] - j 仍然是排序序列rand_num[j] 的排序序列。幸运的是,这很容易显示,因为原始 rand_num 的两个元素之间的最小距离为 1(生成的数字是唯一的,因此始终存在至少 1 的差异)。同时,如果我们从所有元素
                    rand_num[j] 中减去索引j,则索引的差值正好是 1。因此,在“最坏”的情况下,我们得到一个恒定的序列 - 但永远不会减少。因此可以使用二分搜索,产生O(n log(n)) 算法:

                    struct TNeedle { // in the comparison operator we need to make clear which argument is the needle and which is already in the list; we do that using the type system.
                        int n;
                    
                        TNeedle(int _n)
                            :n(_n)
                        {}
                    };
                    
                    class CCompareWithOffset { // custom comparison "n < rand_num[j] - j"
                    protected:
                        std::vector<int>::iterator m_p_begin_it;
                    
                    public:
                        CCompareWithOffset(std::vector<int>::iterator p_begin_it)
                            :m_p_begin_it(p_begin_it)
                        {}
                    
                        bool operator ()(const int &r_value, TNeedle n) const
                        {
                            size_t n_index = &r_value - &*m_p_begin_it;
                            // calculate index in the array
                    
                            return r_value < n.n + n_index; // or r_value - n_index < n.n
                        }
                    
                        bool operator ()(TNeedle n, const int &r_value) const
                        {
                            size_t n_index = &r_value - &*m_p_begin_it;
                            // calculate index in the array
                    
                            return n.n + n_index < r_value; // or n.n < r_value - n_index
                        }
                    };
                    

                    最后:

                    void RandomUniqueSequence(std::vector<int> &rand_num,
                        const size_t n_select_num, const size_t n_item_num)
                    {
                        assert(n_select_num <= n_item_num);
                    
                        rand_num.clear(); // !!
                    
                        // b1: 3578.000 msec
                        // b2: 1703.000 msec (the fastest)
                        for(size_t i = 0; i < n_select_num; ++ i) {
                            int n = n_Rand(n_item_num - i - 1);
                            // get a random number
                    
                            std::vector<int>::iterator p_where_it = std::upper_bound(rand_num.begin(), rand_num.end(),
                                TNeedle(n), CCompareWithOffset(rand_num.begin()));
                            // see where it should be inserted
                    
                            rand_num.insert(p_where_it, 1, n + p_where_it - rand_num.begin());
                            // insert it in the list, maintain a sorted sequence
                        }
                        // tier 4 - use binary search
                    }
                    

                    我在三个基准测试中对此进行了测试。首先,从 7 个项目中选择了 3 个数字,并且选择的项目的直方图累积了 10,000 次运行:

                    4265 4229 4351 4267 4267 4364 4257
                    

                    这表明这 7 个项目中的每一个项目被选择的次数大致相同,并且没有明显的算法导致的偏差。还检查了所有序列的正确性(内容的唯一性)。

                    第二个基准测试涉及从 5000 个项目中选择 7 个数字。该算法的几个版本的时间累积超过 10,000,000 次运行。结果在代码中用 cmets 表示为b1。算法的简单版本稍快。

                    第三个基准测试涉及从 5000 个项目中选择 700 个数字。该算法的几个版本的时间再次被累积,这次超过 10,000 次运行。结果在代码中的 cmets 中表示为b2。该算法的二分查找版本现在比简单版本快两倍以上。

                    第二种方法在我的机器上选择超过 cca 75 个项目时开始变得更快(请注意,任何一种算法的复杂性都不取决于项目的数量,MAX)。

                    值得一提的是,上述算法是按升序生成随机数的。但是添加另一个数组会很简单,数字将按照生成的顺序保存到该数组中,然后将其返回(附加成本可以忽略不计O(n))。没有必要对输出进行洗牌:那会慢得多。

                    请注意,源代码是 C++,我的机器上没有 Java,但概念应该很清楚。

                    编辑

                    为了娱乐,我还实现了生成包含所有索引的列表
                    0 .. MAX 的方法,随机选择它们并将它们从列表中删除以保证唯一性。由于我选择了相当高的MAX(5000),所以性能是灾难性的:

                    // b1: 519515.000 msec
                    // b2: 20312.000 msec
                    std::vector<int> all_numbers(n_item_num);
                    std::iota(all_numbers.begin(), all_numbers.end(), 0);
                    // generate all the numbers
                    
                    for(size_t i = 0; i < n_number_num; ++ i) {
                        assert(all_numbers.size() == n_item_num - i);
                        int n = n_Rand(n_item_num - i - 1);
                        // get a random number
                    
                        rand_num.push_back(all_numbers[n]); // put it in the output list
                        all_numbers.erase(all_numbers.begin() + n); // erase it from the input
                    }
                    // generate random numbers
                    

                    我还使用 set(一个 C++ 集合)实现了该方法,它实际上在基准 b2 上排名第二,仅比使用二分搜索的方法慢约 50%。这是可以理解的,因为set 使用二叉树,其中插入成本类似于二叉搜索。唯一的区别是获得重复项目的机会,这会减慢进度。

                    // b1: 20250.000 msec
                    // b2: 2296.000 msec
                    std::set<int> numbers;
                    while(numbers.size() < n_number_num)
                        numbers.insert(n_Rand(n_item_num - 1)); // might have duplicates here
                    // generate unique random numbers
                    
                    rand_num.resize(numbers.size());
                    std::copy(numbers.begin(), numbers.end(), rand_num.begin());
                    // copy the numbers from a set to a vector
                    

                    完整源代码为here

                    【讨论】:

                      【解决方案11】:

                      这完全取决于您需要随机生成的确切内容,但这是我的看法。

                      首先,创建一个独立的方法来生成随机数。 一定要考虑限制。

                      public static int newRandom(int limit){
                          return generatedRandom.nextInt(limit);  }
                      

                      接下来,您需要创建一个非常简单的决策结构来比较值。这可以通过以下两种方式之一来完成。如果要验证的数字非常有限,一个简单的 IF 语句就足够了:

                      public static int testDuplicates(int int1, int int2, int int3, int int4, int int5){
                          boolean loopFlag = true;
                          while(loopFlag == true){
                              if(int1 == int2 || int1 == int3 || int1 == int4 || int1 == int5 || int1 == 0){
                                  int1 = newRandom(75);
                                  loopFlag = true;    }
                              else{
                                  loopFlag = false;   }}
                          return int1;    }
                      

                      上面将 int1 与 int2 到 int5 进行比较,并确保随机数中没有零。

                      有了这两种方法,我们可以做到以下几点:

                          num1 = newRandom(limit1);
                          num2 = newRandom(limit1);
                          num3 = newRandom(limit1);
                          num4 = newRandom(limit1);
                          num5 = newRandom(limit1);
                      

                      关注者:

                              num1 = testDuplicates(num1, num2, num3, num4, num5);
                              num2 = testDuplicates(num2, num1, num3, num4, num5);
                              num3 = testDuplicates(num3, num1, num2, num4, num5);
                              num4 = testDuplicates(num4, num1, num2, num3, num5);
                              num5 = testDuplicates(num5, num1, num2, num3, num5);
                      

                      如果您要验证的列表更长,那么更复杂的方法将在代码清晰度和处理资源方面产生更好的结果。

                      希望这会有所帮助。这个网站对我帮助很大,我觉得至少也有义务尝试提供帮助。

                      【讨论】:

                        【解决方案12】:

                        这个伪代码解释了获得非重复随机数的最有效、最基本的方法。无需嵌套循环或散列查找:

                        // get 5 unique random numbers, possible values 0 - 19
                        // (assume desired number of selections < number of choices)
                        
                        const int POOL_SIZE = 20;
                        const int VAL_COUNT = 5;
                        
                        declare Array mapping[POOL_SIZE];
                        declare Array results[VAL_COUNT];
                        
                        declare i int;
                        declare r int;
                        declare max_rand int;
                        
                        // create mapping array
                        for (i=0; i<POOL_SIZE; i++) {
                           mapping[i] = i;
                        }
                        
                        max_rand = POOL_SIZE-1;  // start loop searching for maximum value (19)
                        
                        for (i=0; i<VAL_COUNT; i++) {
                            r = Random(0, max_rand); // get random number
                            results[i] = mapping[r]; // grab number from map array
                            mapping[r] = max_rand;  // place item past range at selected location
                        
                            max_rand = max_rand - 1;  // reduce random scope by 1
                        }
                        

                        假设第一次迭代生成随机数 3 开始(从 0 到 19)。这将使 results[0] = mapping[3],即值 3。然后我们将 mapping[3] 分配给 19。

                        在下一次迭代中,随机数为 5(从 0 到 18)。这将使 results[1] = mapping[5],即值 5。然后我们将 mapping[5] 分配给 18。

                        现在假设下一次迭代再次选择了 3(从 0 到 17)。 results[2] 会被赋值为 mapping[3] 的值,但是现在这个值不是 3,而是 19。

                        即使您连续 5 次获得相同的号码,这种保护也适用于所有号码。例如,如果随机数生成器连续五次给你 0,结果将是:[ 0, 19, 18, 17, 16 ]。

                        你永远不会两次得到相同的数字。

                        【讨论】:

                        • 我怀疑这是否像你说的那样随机。它是否通过了标准的随机性测试?它似乎将数字集中在频谱末端附近。
                        • 这是一个基本案例。池是 { a, b, c }。我们需要 2 个不重复的元素。遵循算法,以下是我们可以绘制的组合及其结果: 0,0 : a,c 0,1 : a,b 1,0 : b,a 1,1 : b,c 2,0 : c,a 2, 1 : c,b 得分:a-4, b-4, c-4
                        【解决方案13】:
                        //random numbers are 0,1,2,3 
                        ArrayList<Integer> numbers = new ArrayList<Integer>();   
                        Random randomGenerator = new Random();
                        while (numbers.size() < 4) {
                        
                            int random = randomGenerator .nextInt(4);
                            if (!numbers.contains(random)) {
                                numbers.add(random);
                            }
                        }
                        

                        【讨论】:

                        • 这对于大量数据会产生可怕的性能。 ArrayList.contains 正在遍历列表。更清洁的是有一个 Set 代替 - 你不需要检查它是否包含,只需添加并且性能会更好。
                        【解决方案14】:

                        还有另一种用LFSR做“随机”有序数的方法,看看:

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

                        使用这种技术,您可以通过索引获得有序随机数,并确保值不重复。

                        但这些不是真正的随机数,因为随机生成是确定性的。

                        但是根据您的情况,您可以使用这种技术来减少使用洗牌时随机数生成的处理量。

                        这里是java中的LFSR算法,(我把它带到了我不记得的地方):

                        public final class LFSR {
                            private static final int M = 15;
                        
                            // hard-coded for 15-bits
                            private static final int[] TAPS = {14, 15};
                        
                            private final boolean[] bits = new boolean[M + 1];
                        
                            public LFSR() {
                                this((int)System.currentTimeMillis());
                            }
                        
                            public LFSR(int seed) {
                                for(int i = 0; i < M; i++) {
                                    bits[i] = (((1 << i) & seed) >>> i) == 1;
                                }
                            }
                        
                            /* generate a random int uniformly on the interval [-2^31 + 1, 2^31 - 1] */
                            public short nextShort() {
                                //printBits();
                        
                                // calculate the integer value from the registers
                                short next = 0;
                                for(int i = 0; i < M; i++) {
                                    next |= (bits[i] ? 1 : 0) << i;
                                }
                        
                                // allow for zero without allowing for -2^31
                                if (next < 0) next++;
                        
                                // calculate the last register from all the preceding
                                bits[M] = false;
                                for(int i = 0; i < TAPS.length; i++) {
                                    bits[M] ^= bits[M - TAPS[i]];
                                }
                        
                                // shift all the registers
                                for(int i = 0; i < M; i++) {
                                    bits[i] = bits[i + 1];
                                }
                        
                                return next;
                            }
                        
                            /** returns random double uniformly over [0, 1) */
                            public double nextDouble() {
                                return ((nextShort() / (Integer.MAX_VALUE + 1.0)) + 1.0) / 2.0;
                            }
                        
                            /** returns random boolean */
                            public boolean nextBoolean() {
                                return nextShort() >= 0;
                            }
                        
                            public void printBits() {
                                System.out.print(bits[M] ? 1 : 0);
                                System.out.print(" -> ");
                                for(int i = M - 1; i >= 0; i--) {
                                    System.out.print(bits[i] ? 1 : 0);
                                }
                                System.out.println();
                            }
                        
                        
                            public static void main(String[] args) {
                                LFSR rng = new LFSR();
                                Vector<Short> vec = new Vector<Short>();
                                for(int i = 0; i <= 32766; i++) {
                                    short next = rng.nextShort();
                                    // just testing/asserting to make 
                                    // sure the number doesn't repeat on a given list
                                    if (vec.contains(next))
                                        throw new RuntimeException("Index repeat: " + i);
                                    vec.add(next);
                                    System.out.println(next);
                                }
                            }
                        }
                        

                        【讨论】:

                          【解决方案15】:

                          对于整数,有一种比 Collections.shuffle 更有效、更简单的解决方案。

                          这个问题与只从一组中未选择的项目中连续挑选项目并在其他地方按顺序排列它们是相同的。这就像随机发牌或从帽子或垃圾箱中抽奖一样。

                          此算法适用于加载任何数组并在加载结束时实现随机顺序。它还适用于添加到 List 集合(或任何其他索引集合)中,并在添加结束时在集合中实现随机序列。

                          可以使用单个数组(创建一次)或按数字顺序排列的集合(例如列表)来完成。对于数组,初始数组大小需要是包含所有预期值的确切大小。如果您事先不知道可能会出现多少个值,那么使用大小不可变的数字排序集合(例如 ArrayList 或 List)也可以。它适用于任何大小的数组,最大为 Integer.MAX_VALUE,刚好超过 2,000,000,000。列表对象将具有相同的索引限制。您的机器可能会在达到该大小的数组之前耗尽内存。在加载数组之后,加载类型为对象类型的数组并将其转换为某个集合可能更有效。如果目标集合没有数字索引,则尤其如此。

                          这个算法,正如所写的那样,将创建一个没有重复的非常均匀的分布。非常重要的一个方面是,下一个项目的插入必须有可能发生到当前大小 + 1。因此,对于第二个项目,可以将其存储在位置 0 或位置 1 . 对于第 20 个项目,可以将其存储在 0 到 19 的任何位置。第一个项目尽可能保留在位置 0,因为它最终会存储在任何其他位置。下一个新项目尽可能去任何地方,包括下一个新位置。

                          序列的随机性将与随机数生成器的随机性一样随机。

                          此算法还可用于将引用类型加载到数组中的随机位置。由于这适用于数组,因此它也适用于集合。这意味着您不必创建集合,然后对其进行洗牌或按照插入对象的任何顺序对其进行排序。集合只需要能够在集合中的任何位置插入项目或附加它。

                          // RandomSequence.java
                          import java.util.Random;
                          public class RandomSequence {
                          
                              public static void main(String[] args) {
                                  // create an array of the size and type for which
                                  // you want a random sequence
                                  int[] randomSequence = new int[20];
                                  Random randomNumbers = new Random();
                          
                                  for (int i = 0; i < randomSequence.length; i++ ) {
                                      if (i == 0) { // seed first entry in array with item 0
                                          randomSequence[i] = 0; 
                                      } else { // for all other items...
                                          // choose a random pointer to the segment of the
                                          // array already containing items
                                          int pointer = randomNumbers.nextInt(i + 1);
                                          randomSequence[i] = randomSequence[pointer]; 
                                          randomSequence[pointer] = i;
                                          // note that if pointer & i are equal
                                          // the new value will just go into location i and possibly stay there
                                          // this is VERY IMPORTANT to ensure the sequence is really random
                                          // and not biased
                                      } // end if...else
                                  } // end for
                                  for (int number: randomSequence) {
                                          System.out.printf("%2d ", number);
                                  } // end for
                              } // end main
                          } // end class RandomSequence
                          

                          【讨论】:

                            【解决方案16】:

                            不是做这一切,而是通过Math.random() 函数创建一个LinkedHashSet 对象和随机数......如果出现任何重复条目,LinkedHashSet 对象不会将该数字添加到其列表中......因为在这个集合类中不允许重复值..最后你得到一个没有重复值的随机数列表..:D

                            【讨论】:

                              【解决方案17】:

                              Here 是快速创建随机数组的有效解决方案。随机化后,您可以简单地选择数组的n-th 元素e,递增n 并返回e。这个解决方案需要 O(1) 来获取随机数和 O(n) 来初始化,但作为权衡,如果 n 变得足够大,则需要大量内存。

                              【讨论】:

                                【解决方案18】:

                                我会这样做

                                import java.util.ArrayList;
                                import java.util.Random;
                                
                                public class Test {
                                    public static void main(String[] args) {
                                        int size = 20;
                                
                                        ArrayList<Integer> list = new ArrayList<Integer>(size);
                                        for(int i = 1; i <= size; i++) {
                                            list.add(i);
                                        }
                                
                                        Random rand = new Random();
                                        while(list.size() > 0) {
                                            int index = rand.nextInt(list.size());
                                            System.out.println("Selected: "+list.remove(index));
                                        }
                                    }
                                }
                                

                                正如受人尊敬的斯基特先生所指出的那样:
                                如果 n 是您希望选择的随机选择的数字的数量,而 N 是可供选择的数字的总样本空间:

                                1. 如果n N,您应该只存储您选择的号码并检查列表以查看所选号码是否在其中。
                                2. 如果 n ~= N,您可能应该使用我的方法,通过填充包含整个样本空间的列表,然后在选择它们时从中删除数字。

                                【讨论】:

                                • list应该是LinkedList,从arraylist中移除随机索引效率很低
                                • @RiccardoCasatta 你有断言的来源吗?我也无法想象遍历链表会非常高效。另见:stackoverflow.com/a/6103075/79450
                                • 我测试过了,你是对的,我应该删除我的评论吗?
                                • @RiccardoCasatta 其他人可能会发现我们的来回很有用
                                【解决方案19】:

                                您可以使用实现 Set 接口的类之一 (API),然后您生成的每个数字,使用 Set.add() 插入它。

                                如果返回值为false,则说明该数字之前已经生成过。

                                【讨论】:

                                  【解决方案20】:

                                  有卡片批处理算法:您创建有序的数字数组(“卡片批处理”),并在每次迭代中从随机位置选择一个数字(当然从“卡片批处理”中删除选定的数字) .

                                  【讨论】:

                                    猜你喜欢
                                    • 1970-01-01
                                    • 1970-01-01
                                    • 1970-01-01
                                    • 2015-02-27
                                    • 1970-01-01
                                    • 1970-01-01
                                    相关资源
                                    最近更新 更多