可能是重复的问题:
Unique random numbers in O(1)?
如何在C语言中用不重复的值(没有重复)填充整数数组?
int vektor[10];
for (i = 0; i < 10; i++) {
vektor[i] = rand() % 100 + 1;
}
//No uniqueness here
可能是重复的问题:
Unique random numbers in O(1)?
如何在C语言中用不重复的值(没有重复)填充整数数组?
int vektor[10];
for (i = 0; i < 10; i++) {
vektor[i] = rand() % 100 + 1;
}
//No uniqueness here
有几种方法可以解决你的问题,每种方法都有其优缺点。
首先,我想指出你已经得到了相当多的回答,这些回答都是生成随机数,然后以某种方式检查它是否已在数组中使用过,如果已被使用,则继续生成另一个数字,直到找到未使用的数字。
这是一种天真而且说实话,严重有缺陷的方法。问题在于数字生成的循环试错性质(“如果已使用,请重试”)。如果数字范围(如[1..N])接近所需数组的长度(如M),那么算法可能会花费大量时间来寻找下一个数字。如果随机数生成器有一点点问题(比如从不生成某个数字或极少生成该数字),那么当N==M时,该算法保证会无限循环(或者循环非常长的时间)。通常这种试错方法是毫无用处的,或者充其量也只是一个有缺陷的方法。
这里已经介绍了另一种方法,即在大小为N的数组中生成随机排列。 随机排列的想法很有前途,但是在大小为N的数组上执行它(当M << N时)肯定会造成更多的麻烦而不是解决问题。
例如,在Bentley的“编程珠玑”中可以找到此问题的好解决方案(其中一些取自Knuth)。
vektor
数组本身没有额外的内存),该算法不需要任何额外的内存。后者使它成为M << N情况下可行的算法。该算法的工作原理如下:遍历从1到N的所有数字,并以概率rm / rn
选择当前数字,其中rm
是我们仍需要查找的数字数,而rn
是我们仍需要迭代的数字数。以下是您的情况可能的实现:
#define M 10
#define N 100
int in, im;
im = 0;
for (in = 0; in < N && im < M; ++in) {
int rn = N - in;
int rm = M - im;
if (rand() % rn < rm)
/* Take it */
vektor[im++] = in + 1; /* +1 since your range begins from 1 */
}
assert(im == M);
这个循环之后,我们得到一个由随机选择的数字填充且以升序排列的数组vektor
。这里我们不需要“升序”这一点。因此,为了“修复”它,我们只需对vektor
的元素进行随机排列即可完成。请注意,这是一个O(M)置换,不需要额外的内存。(我不会介绍置换算法的实现。这里已经给出了许多链接。)这里是适用于您情况的可能实现方式。(有多种方法来跟踪已使用的数字。我将只使用标志数组,假设N不是过大)
#define M 10
#define N 100
unsigned char is_used[N] = { 0 }; /* flags */
int in, im;
im = 0;
for (in = N - M; in < N && im < M; ++in) {
int r = rand() % (in + 1); /* generate a random number 'r' */
if (is_used[r])
/* we already have 'r' */
r = in; /* use 'in' instead of the generated number */
assert(!is_used[r]);
vektor[im++] = r + 1; /* +1 since your range begins from 1 */
is_used[r] = 1;
}
assert(im == M);
为什么上述方法能够奏效并不是显而易见的,但它确实有效。从[1..N]范围内精确选取M个数字,且选取概率相等。
需要注意的是,对于较大的N,您可以使用基于搜索的结构来存储“已使用”的数字,从而获得一个漂亮的O(M log M)算法,其内存需求为O(M)。
(这种算法有一个问题:虽然结果数组不会被排序,但原始的1..N顺序在结果中仍会有一定的“影响”。例如,如果选择了数字N,则它只能成为结果数组的最后一个成员。如果不接受意外顺序的“污染”,则可以像Khuth算法一样对结果的vektor
数组进行随机重排。)
请注意这两个算法设计中观察到的非常关键的一点:它们从不尝试循环查找新的未使用随机数。任何使用随机数进行试错迭代的算法都从实际角度看是有缺陷的。此外,这些算法的内存消耗与M有关,而不是N。
对于OP,我推荐使用Floyd算法,因为在他的应用程序中,M似乎远小于N,并且不需要额外的排列。但是,对于如此小的N值,差异可能是可以忽略的。
int list[100], vektor[10];
for (i = 0; i < 100; i++) {
list[i] = i;
}
for (i = 0; i < 100; i++) {
int j = i + rand() % (100 - i);
int temp = list[i];
list[i] = list[j];
list[j] = temp;
}
for (i = 0; i < 10; i++) {
vektor[i] = list[i];
}
for (i = 0; i < 10; i++) {
int j = i + rand() % (100 - i);
int temp = list[i];
list[i] = list[j];
list[j] = temp;
vektor[i] = list[i];
}
我认为这个就可以了(我没有尝试过构建它,所以语法错误应该留给读者作为练习来修复)。可能还有更优雅的解决方案,但这是一种粗暴的解决方法:
int vektor[10];
int random;
int uniqueflag;
int i, j
for(i = 0; i < 10; i++) {
do {
/* Assume things are unique... we'll reset this flag if not. */
uniqueflag = 1;
random = rand() % 100+ 1;
/* This loop checks for uniqueness */
for (j = 0; j < i && uniqueflag == 1; j++) {
if (vektor[j] == random) {
uniqueflag = 0;
}
}
} while (uniqueflag != 1);
vektor[i] = random;
}
通常来说,仅仅生成随机数并验证它们是否符合要求是解决这个问题的一种不好的方法。这种方式会取出所有可能的值并将它们混合在一起,然后取前十个。这就像是洗牌并从牌堆顶端发牌。
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define randrange(N) rand() / (RAND_MAX/(N) + 1)
#define MAX 100 /* Values will be in the range (1 .. MAX) */
static int vektor[10];
int candidates[MAX];
int main (void) {
int i;
srand(time(NULL)); /* Seed the random number generator. */
for (i=0; i<MAX; i++)
candidates[i] = i;
for (i = 0; i < MAX-1; i++) {
int c = randrange(MAX-i);
int t = candidates[i];
candidates[i] = candidates[i+c];
candidates[i+c] = t;
}
for (i=0; i<10; i++)
vektor[i] = candidates[i] + 1;
for (i=0; i<10; i++)
printf("%i\n", vektor[i]);
return 0;
}
更多信息,请参见comp.lang.c FAQ列表问题13.19了解洗牌和问题13.16关于生成随机数。
int rand_array[100] = {0};
int vektor[10];
int i=0, rnd;
while(i<10) {
rnd = rand() % 100+ 1;
if ( rand_array[rnd-1] == 0 ) {
vektor[i++] = rnd;
rand_array[rnd-1] = 1;
}
}
分别生成第一位和第二位数字。 如果需要,稍后再进行洗牌。(语法来自记忆)
int vektor[10];
int i = 0;
while(i < 10) {
int j = rand() % 10;
if (vektor[j] == 0) { vektor[j] = rand() % 10 + j * 10; i ++;}
}
不过,这些数字之间将会有n的差距,其中0 < n < 10。
否则,你需要保持这些数字排序(O(n log n)
),以便新生成的数字可以快速地被检查是否存在(O(log n)
)。
这里有一个O(M)平均时间复杂度的方法。
方法:如果M ≤ N/2,则使用过程S(M,N)(如下)生成结果数组R,并返回R。如果M > N/2,则使用过程S(N-M,N)生成R,然后计算X = {1..M}\R
[在{1..M}中R的补集],用Fisher-Yates shuffle [在O(M)时间内]对X进行洗牌,然后返回X。
当M < N/2时,S(M,N)需要O(M)时间而不是O(N)时间的原因是,正如收集优惠券问题中所述,期望E(t_k)是kH_k,从中可以得出E(t_{k/2}) = k(H_k - H_{k/2})或大约为k*(ln(k)-ln(k/2)+O(1)) = k*(ln(k/(k/2))+O(1)) = k*(ln(2)+O(1)) = O(k)。
过程S(k,N):[此过程的主体是下面代码中“生成M个不同的随机数”注释后的十几行。] 分配并初始化三个M + 1元素整数数组H,L和V,其所有值均为-1。 对于i = 0到M-1:将随机值v放入V [i]和哨兵节点V [-1]中。 从H [v%M]中获取M个列表头之一,并跟随该列表,直到找到与v匹配的项。 如果匹配在V [-1]处,则v是一个新值; 因此更新列表头H [v%M]和列表链接L [i]。 如果匹配不在V [-1]处,则获取并测试另一个v,等等。 // randomMofN - jiw 8 Nov 2011
// Re: https://dev59.com/TXI-5IYBdhLWcg3w8NOB
#include <stdlib.h>
#include <stdio.h>
int main(int argc, char *argv[]) {
int h, i, j, tM, M, N, par=0, *H, *L, *V, cxc=0;
// Get M and N values
++par; M = 42; if (argc > par) M = atoi(argv[par]);
++par; N = 137; if (argc > par) N = atoi(argv[par]);
tM = 3*M+3;
H = malloc(tM*sizeof(int));
printf ("M = %d, N = %d %s\n", M, N, H?"":"\nmem error");
if (!H) exit(13);
for (i=0; i<tM; ++i) // Init arrays to -1's
H[i] = -1;
L = H+M; V = L+M;
// Gen M distinct random numbers
for (i=0; i<M; ++i) {
do {
++cxc; // complexity counter
V[-1] = V[i] = random()%N;
h = V[i]%M; // h = list-head index
j = H[h];
while (V[j] != V[i])
j = L[j];
} while (j>=0);
L[i] = H[h];
H[h] = i;
}
// Print results
for (j=i=0; i<M; ++i) {
j += printf ("%4d ", V[i]);
if (j>66) j = printf ("\n");
}
printf ("\ncxc %d\n", cxc);
return 0;
}
我喜欢弗洛伊德算法。
但我们可以从0
到M
中取所有的随机数(而不是in
):
#define M 10
#define N 100
unsigned char is_used[N] = { 0 }; /* flags */
int in, im;
im = 0;
for (in = N - M; in < N && im < M; ++in) {
int r = rand() % (N + 1); /* generate a random number 'r' */
while (is_used[r])
{
/* we already have 'r' */
r = rand() % (N + 1);
}
vektor[im++] = r + 1; /* +1 since your range begins from 1 */
is_used[r] = 1;
}
assert(im == M);