【问题标题】:C - Generate random sequence with no repeats without shufflingC - 生成没有重复的随机序列而不改组
【发布时间】:2017-02-01 12:03:09
【问题描述】:

我想以随机顺序不改组生成序列[0...1'000'000]的数组。

这表示我不想做:

int arr[1000000];

for (int i = 0; i < 1000000; i++)
{
    arr[i] = i; 
}

shuffle(arr);
shuffle(arr);

我想弄清楚如何在没有“黑盒”shuffle 功能的情况下做到这一点。我也不想在11'000'000 之间随机选择一个索引,因为在编号999'999 上只有1/1'000'000 机会继续。

我一直在想一个解决方案,我认为关键是并行数组和向后循环,然后使用模数来限制你还没有去过的索引,但是我不能保证我得到的价值是独一无二的。

我也不想使用 HashSet 或 TreeSet 实现。

【问题讨论】:

  • 您想要生成 1M 个数字的随机排列。这就是问题定义,我说的对吗?为什么您认为在您定义的约束范围内有解决方案?
  • 是的。我想用从 11M 的值填充一个 1M 大小的数组,没有任何重复,并且在不使用洗牌算法的情况下以良好的 O 复杂度完成这一切。
  • 洗牌是准确地做到这一点的方式。您对这种技术有什么问题?
  • 你可以有一个理论算法,从O(n!)个排列中选择一个哥德尔编码的排列。但当然 n=1M 是不可行的。
  • 随机选择不重复的1,...,n 几乎是随机播放的定义,无论您喜欢与否,您想出的任何算法都将是随机播放。

标签: c algorithm random


【解决方案1】:

O(N) 的答案很好,但这里是使用二进制搜索和binary indexed tree 在 O(NlogN) 中执行此操作的另一种方法。

arr = []
N = 1000,000
for i from 0 to N-1
     low = 0
     high = N-1
     mid = (low+high)/2
     while low < high
         if full(low,mid)
             low = mid+1
         else if full(mid+1,high)
             high = mid
         else
             if rand() < 0.5
                  low = mid+1
             else
                  high = mid
     mark(low) // marking the element in binary indexed tree
     arr[i] = low

函数full是使用二叉索引树实现的,它检查给定范围内的所有元素是否都被标记。 markfull 都具有 O(logN) 复杂度。

【讨论】:

  • 您似乎是根据范围的大小而不是范围的占用情况随机选择一半的空间。这将导致一个小的偏差潜入结果。如果您通过将随机数与左侧可用值与可用总值的比率进行比较来选择使用哪一半,则该算法将起作用,但它仍然是不必要的复杂。 (您可以使用二叉索引树来维护 O(log N) 中的可用性计数,因此正确操作不会产生额外费用。)
  • OP 说他不想使用树。
【解决方案2】:

这可以通过两个列表在O(n) 时间完成,一个按顺序(最初)按数字顺序排列,另一个按结果顺序排列。

您从源列表中的n 元素开始。然后你选择一个随机数 mod n。这为您提供了下一个元素,您将其放置在目标列表中。

现在是关键部分。如果您每次都在0n-1 之间选择一个随机数,就像您认为的随机播放一样,那么您选择之前选择的数字的机会就会增加。那么你如何处理呢?通过减少可供选择的数字列表。

在源列表中,选择一个数字后,将列表的最后一个元素移动到刚刚使用的索引处。您现在有一个可供选择的n-1 号码列表。所以在下一次迭代中,你取一个随机数 mod n-1。继续,直到您的源列表只有一个元素。

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define LEN 10

int main()
{
    int a[LEN], b[LEN];
    int i, val;
    int count = LEN;

    srand(time(NULL));

    for (i=0;i<LEN;i++) {
        a[i]=i+1;
    }
    for (i=0;i<LEN;i++) {
        val = rand() % count;
        b[i] = a[val];
        a[val] = a[count-1];
        count--;
    }
    for (i=0;i<LEN;i++) {
        printf("%d ", b[i]);
    }
    printf("\n");

    return 0;
}

编辑:

这是一个稍微更高效的版本,它不使用两个数组,因此是O(1) 空间:

int a[LEN];
int i, val, tmp;

srand(time(NULL));

for (i=0;i<LEN;i++) {
    a[i]=i+1;
}
for (i=0;i<LEN-1;i++) {
    val = (rand() % (LEN - 1 - i)) + i + 1;
    tmp = a[i];
    a[i] = a[val];
    a[val] = tmp;
}
for (i=0;i<LEN;i++) {
    printf("%d ", a[i]);
}
printf("\n");

【讨论】:

  • 代码违反了without shuffling. 的限制(因此不回答问题)。 (您的第二个 for 循环几乎是 fisher yates shuffle
  • @amit 我认为他的意思是我们不允许使用黑盒随机播放功能。
  • @sudomakeinstall2 我没有看到任何问题,也没有 cmets 支持该假设。
  • @amit 好吧,你是对的。我猜对了,否则它没有任何意义
  • 无论如何,这是可以得到的最好的算法。但是应该提到的是O(n)时间空间复杂度
猜你喜欢
  • 1970-01-01
  • 2015-07-12
  • 2018-07-03
  • 2016-03-01
  • 1970-01-01
  • 1970-01-01
  • 2012-08-30
  • 1970-01-01
相关资源
最近更新 更多