我想生成一个介于0到1000之间的永不重复的唯一随机数(即6不会出现两次),但是这并不能像对先前值进行O(N)搜索那样进行。这可能吗?
O(n)
在时间或内存上),则以下许多答案是错误的,包括已接受的答案。
我想生成一个介于0到1000之间的永不重复的唯一随机数(即6不会出现两次),但是这并不能像对先前值进行O(N)搜索那样进行。这可能吗?
O(n)
在时间或内存上),则以下许多答案是错误的,包括已接受的答案。
Answers:
使用0-1000值初始化一个1001个整数的数组,并将变量max设置为数组的当前max索引(从1000开始)。选择一个介于0到max之间的随机数r,将r处的数字与max处的数字交换,然后立即返回max处的数字。最大减1,然后继续。当max为0时,将max设置回数组的大小-1,然后重新启动,而无需重新初始化数组。
更新: 尽管我回答问题时是自己提出了这种方法,但经过一些研究,我意识到这是费舍尔·耶茨的改良版,称为Durstenfeld-Fisher-Yates或Knuth-Fisher-Yates。由于描述可能会有些困难,因此我在下面提供了一个示例(使用11个元素而不是1001):
数组以初始化为array [n] = n的11个元素开始,最大值从10开始:
+--+--+--+--+--+--+--+--+--+--+--+
| 0| 1| 2| 3| 4| 5| 6| 7| 8| 9|10|
+--+--+--+--+--+--+--+--+--+--+--+
^
max
每次迭代时,都会在0到max之间选择一个随机数r,将array [r]和array [max]交换,返回新的array [max],并将max减1:
max = 10, r = 3
+--------------------+
v v
+--+--+--+--+--+--+--+--+--+--+--+
| 0| 1| 2|10| 4| 5| 6| 7| 8| 9| 3|
+--+--+--+--+--+--+--+--+--+--+--+
max = 9, r = 7
+-----+
v v
+--+--+--+--+--+--+--+--+--+--+--+
| 0| 1| 2|10| 4| 5| 6| 9| 8| 7: 3|
+--+--+--+--+--+--+--+--+--+--+--+
max = 8, r = 1
+--------------------+
v v
+--+--+--+--+--+--+--+--+--+--+--+
| 0| 8| 2|10| 4| 5| 6| 9| 1: 7| 3|
+--+--+--+--+--+--+--+--+--+--+--+
max = 7, r = 5
+-----+
v v
+--+--+--+--+--+--+--+--+--+--+--+
| 0| 8| 2|10| 4| 9| 6| 5: 1| 7| 3|
+--+--+--+--+--+--+--+--+--+--+--+
...
经过11次迭代后,已选择数组中的所有数字,max == 0,并且对数组元素进行了混洗:
+--+--+--+--+--+--+--+--+--+--+--+
| 4|10| 8| 6| 2| 0| 9| 5| 1| 7| 3|
+--+--+--+--+--+--+--+--+--+--+--+
此时,最大可以重置为10,并且该过程可以继续。
N
迭代(在本示例中为11),才能每次都得到期望的结果O(n)
?因为您需要进行N
迭代才能N!
从相同的初始状态获得组合,否则输出将仅为N个状态之一。
你可以这样做:
因此,这不需要每次都搜索旧值,但仍需要O(N)来进行初始混洗。但是正如Nils在评论中指出的那样,这是摊销O(1)。
使用最大线性反馈移位寄存器。
它可以在几行C语言中实现,并且在运行时所做的只是几个测试/分支,一点点添加和移位。这不是随机的,但它愚弄了大多数人。
您可以使用线性同余生成器。其中m
(模数)是大于1000的最接近素数。当您获得的数字超出范围时,只需获得下一个即可。仅当所有元素都发生后,序列才会重复,而您不必使用表。请注意此生成器的缺点(包括缺乏随机性)。
k
序列中间隔更远的数字永远不会一起出现)。
您可以使用格式保留加密来加密计数器。您的计数器仅从0开始向上移动,并且加密使用您选择的密钥将其转换为您想要的任意基数和宽度的看似随机值。例如,此问题的示例:基数10,宽度3。
块密码通常具有固定的块大小,例如64或128位。但是,格式保留加密使您可以采用像AES这样的标准密码,并使用仍具有加密功能的算法,根据需要的基数和宽度,制作较小宽度的密码。
保证永远不会发生冲突(因为密码算法会创建1:1映射)。它也是可逆的(2向映射),因此您可以获取结果数字并返回到开始时使用的计数器值。
该技术不需要内存来存储经过改组的数组等,这在内存有限的系统上可能是一个优势。
AES-FFX是一种建议的实现此目的的标准方法。我已经尝试了一些基于AES-FFX想法的基本Python代码,尽管并不完全一致-请在此处查看Python代码。例如,它可以将计数器加密为看起来很随机的7位十进制数字或16位数字。这是一个基数为10,宽度为3(给出一个介于0到999之间的数字)的示例,如下所示:
000 733
001 374
002 882
003 684
004 593
005 578
006 233
007 811
008 072
009 337
010 119
011 103
012 797
013 257
014 932
015 433
... ...
要获得不同的非重复伪随机序列,请更改加密密钥。每个加密密钥产生一个不同的非重复伪随机序列。
k
,序列中大于相除的值永远不会一起出现)。
k
吗?什么是“所有相关的纽结” ?什么是?
1,2,...,N
其他但仍然恒定的顺序,用相同编号的序列替换该序列。然后将数字从该序列中一个接一个地拉出。k
是选取的值的数量(OP没有为其指定字母,因此我不得不介绍一个字母)。
对于像0 ... 1000这样的低数字,创建一个包含所有数字的列表并将其改组是很简单的。但是,如果要提取的数字集非常大,则还有另一种优雅的方式:您可以使用密钥和加密哈希函数构建伪随机排列。请参见下面的C ++样例伪代码:
unsigned randperm(string key, unsigned bits, unsigned index) {
unsigned half1 = bits / 2;
unsigned half2 = (bits+1) / 2;
unsigned mask1 = (1 << half1) - 1;
unsigned mask2 = (1 << half2) - 1;
for (int round=0; round<5; ++round) {
unsigned temp = (index >> half1);
temp = (temp << 4) + round;
index ^= hash( key + "/" + int2str(temp) ) & mask1;
index = ((index & mask2) << half1) | ((index >> half2) & mask1);
}
return index;
}
在这里,hash
只是一些任意的伪随机函数,它将字符串映射到可能是巨大的无符号整数。该函数randperm
是假定键为固定值的所有数字在0 ... pow(2,bits)-1之内的排列。这是从构造中得出的,因为更改变量的每个步骤index
都是可逆的。这是受Feistel密码启发的。
hash()
上面的代码中使用的是安全的伪随机函数,则该构造将被证明是有效的(Luby&Rackoff,1988)(伪随机排列),该伪随机排列无法与真正的随机混洗区分开来,其耗费的工作量远小于穷举。搜索整个密钥空间,密钥空间是指数长度。即使对于大小合理的密钥(例如128位),这也超出了地球上可用的总计算能力。
hash( key + "/" + int2str(temp) )
用HMAC代替上面的临时结构,其安全性又可以证明可降低到底层哈希压缩函数的安全性。此外,使用HMAC可能会使不太可能有人错误地尝试将这种结构与不安全的非加密哈希函数一起使用。)
您可以使用此处描述的我的Xincrol算法:
http://openpatent.blogspot.co.il/2013/04/xincrol-unique-and-random-number.html
这是一种纯粹的算法方法,可以生成随机但唯一的数字,而无需数组,列表,排列或大量的CPU负载。
最新版本还允许设置数字范围,例如,如果我想要0-1073741821范围内的唯一随机数。
我已经用了
它是免费的。试试看...
您甚至不需要数组来解决这个问题。
您需要一个位掩码和一个计数器。
将计数器初始化为零,并在后续调用中将其递增。将计数器与位掩码(在启动时随机选择或固定)进行异或,以生成伪随机数。如果您的数字不能超过1000,请不要使用大于9位的位掩码。(换句话说,位掩码是不大于511的整数。)
确保当计数器超过1000时,将其重置为零。此时,您可以根据需要选择另一个随机位掩码,以不同的顺序生成同一组数字。
我认为线性同余生成器将是最简单的解决方案。
并且a,c和m值只有3个限制
PS该方法已被提及,但帖子对常量值有错误的假设。以下常量应适合您的情况
在你的情况,你可以使用a = 1002
,c = 757
,m = 1001
X = (1002 * X + 757) mod 1001
这是我使用第一个解决方案的逻辑输入的一些代码。我知道这是“不可知的语言”,但只是想以C#举例说明,以防万一有人在寻求快速实用的解决方案。
// Initialize variables
Random RandomClass = new Random();
int RandArrayNum;
int MaxNumber = 10;
int LastNumInArray;
int PickedNumInArray;
int[] OrderedArray = new int[MaxNumber]; // Ordered Array - set
int[] ShuffledArray = new int[MaxNumber]; // Shuffled Array - not set
// Populate the Ordered Array
for (int i = 0; i < MaxNumber; i++)
{
OrderedArray[i] = i;
listBox1.Items.Add(OrderedArray[i]);
}
// Execute the Shuffle
for (int i = MaxNumber - 1; i > 0; i--)
{
RandArrayNum = RandomClass.Next(i + 1); // Save random #
ShuffledArray[i] = OrderedArray[RandArrayNum]; // Populting the array in reverse
LastNumInArray = OrderedArray[i]; // Save Last Number in Test array
PickedNumInArray = OrderedArray[RandArrayNum]; // Save Picked Random #
OrderedArray[i] = PickedNumInArray; // The number is now moved to the back end
OrderedArray[RandArrayNum] = LastNumInArray; // The picked number is moved into position
}
for (int i = 0; i < MaxNumber; i++)
{
listBox2.Items.Add(ShuffledArray[i]);
}
当限制较高且您只想生成几个随机数时,此方法会很合适。
#!/usr/bin/perl
($top, $n) = @ARGV; # generate $n integer numbers in [0, $top)
$last = -1;
for $i (0 .. $n-1) {
$range = $top - $n + $i - $last;
$r = 1 - rand(1.0)**(1 / ($n - $i));
$last += int($r * $range + 1);
print "$last ($r)\n";
}
请注意,数字是按升序生成的,但是您可以在此之后进行随机排序。
(top,n)=(100,10)
是:(0.01047705, 0.01044825, 0.01041225, ..., 0.0088324, 0.008723, 0.00863635)
。我使用Python进行了测试,因此数学上的细微差异可能会在这里起作用(我确实确保所有用于计算的运算r
都是浮点数)。
public static int[] randN(int n, int min, int max)
{
if (max <= min)
throw new ArgumentException("Max need to be greater than Min");
if (max - min < n)
throw new ArgumentException("Range needs to be longer than N");
var r = new Random();
HashSet<int> set = new HashSet<int>();
while (set.Count < n)
{
var i = r.Next(max - min) + min;
if (!set.Contains(i))
set.Add(i);
}
return set.ToArray();
}
根据需要,N个非重复随机数的复杂度为O(n)。
注意:在应用线程安全的情况下,random应该是静态的。
假设您要一遍又一遍地浏览混洗的列表,而O(n)
每次重新开始都没有延迟,请再次进行混洗,在这种情况下,我们可以这样做:
创建2个列表A和B(0到1000)占用2n
空间。
使用Fisher-Yates的随机播放列表A需要花费n
时间。
绘制数字时,在另一个列表上进行1步Fisher-Yates随机播放。
当光标在列表末尾时,切换到另一个列表。
预处理
cursor = 0
selector = A
other = B
shuffle(A)
画
temp = selector[cursor]
swap(other[cursor], other[random])
if cursor == N
then swap(selector, other); cursor = 0
else cursor = cursor + 1
return temp
[1,3,4,5,2]
会产生相同的结果洗牌[1,2,3,4,5]
。
问题如何有效地生成0到上限N之间的K个非重复整数的列表将其作为重复链接-并且是否希望每个生成的随机数为O(1)(没有O(n)启动成本)),您可以对接受的答案进行简单的调整。
从整数到整数创建一个空的无序映射(一个空的有序映射将占用每个元素O(log k))-而不是使用初始化的数组。如果最大值,则将max设置为1000,
与使用初始化数组相比,唯一的区别是元素的初始化被推迟/跳过了-但是它将从同一PRNG中生成完全相同的数字。
另一个可能性:
您可以使用标志数组。并选择下一个。
但是,要提防1000次调用后,该函数将永远不会结束,因此您必须采取保护措施。
这是一些示例COBOL代码,您可以使用。
我可以将RANDGEN.exe文件发送给您,以便您可以使用它来查看它是否确实需要。
IDENTIFICATION DIVISION.
PROGRAM-ID. RANDGEN as "ConsoleApplication2.RANDGEN".
AUTHOR. Myron D Denson.
DATE-COMPILED.
* **************************************************************
* SUBROUTINE TO GENERATE RANDOM NUMBERS THAT ARE GREATER THAN
* ZERO AND LESS OR EQUAL TO THE RANDOM NUMBERS NEEDED WITH NO
* DUPLICATIONS. (CALL "RANDGEN" USING RANDGEN-AREA.)
*
* CALLING PROGRAM MUST HAVE A COMPARABLE LINKAGE SECTION
* AND SET 3 VARIABLES PRIOR TO THE FIRST CALL IN RANDGEN-AREA
*
* FORMULA CYCLES THROUGH EVERY NUMBER OF 2X2 ONLY ONCE.
* RANDOM-NUMBERS FROM 1 TO RANDOM-NUMBERS-NEEDED ARE CREATED
* AND PASSED BACK TO YOU.
*
* RULES TO USE RANDGEN:
*
* RANDOM-NUMBERS-NEEDED > ZERO
*
* COUNT-OF-ACCESSES MUST = ZERO FIRST TIME CALLED.
*
* RANDOM-NUMBER = ZERO, WILL BUILD A SEED FOR YOU
* WHEN COUNT-OF-ACCESSES IS ALSO = 0
*
* RANDOM-NUMBER NOT = ZERO, WILL BE NEXT SEED FOR RANDGEN
* (RANDOM-NUMBER MUST BE <= RANDOM-NUMBERS-NEEDED)
*
* YOU CAN PASS RANDGEN YOUR OWN RANDOM-NUMBER SEED
* THE FIRST TIME YOU USE RANDGEN.
*
* BY PLACING A NUMBER IN RANDOM-NUMBER FIELD
* THAT FOLLOWES THESE SIMPLE RULES:
* IF COUNT-OF-ACCESSES = ZERO AND
* RANDOM-NUMBER > ZERO AND
* RANDOM-NUMBER <= RANDOM-NUMBERS-NEEDED
*
* YOU CAN LET RANDGEN BUILD A SEED FOR YOU
*
* THAT FOLLOWES THESE SIMPLE RULES:
* IF COUNT-OF-ACCESSES = ZERO AND
* RANDOM-NUMBER = ZERO AND
* RANDOM-NUMBER-NEEDED > ZERO
*
* TO INSURING A DIFFERENT PATTERN OF RANDOM NUMBERS
* A LOW-RANGE AND HIGH-RANGE IS USED TO BUILD
* RANDOM NUMBERS.
* COMPUTE LOW-RANGE =
* ((SECONDS * HOURS * MINUTES * MS) / 3).
* A HIGH-RANGE = RANDOM-NUMBERS-NEEDED + LOW-RANGE
* AFTER RANDOM-NUMBER-BUILT IS CREATED
* AND IS BETWEEN LOW AND HIGH RANGE
* RANDUM-NUMBER = RANDOM-NUMBER-BUILT - LOW-RANGE
*
* **************************************************************
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
DATA DIVISION.
FILE SECTION.
WORKING-STORAGE SECTION.
01 WORK-AREA.
05 X2-POWER PIC 9 VALUE 2.
05 2X2 PIC 9(12) VALUE 2 COMP-3.
05 RANDOM-NUMBER-BUILT PIC 9(12) COMP.
05 FIRST-PART PIC 9(12) COMP.
05 WORKING-NUMBER PIC 9(12) COMP.
05 LOW-RANGE PIC 9(12) VALUE ZERO.
05 HIGH-RANGE PIC 9(12) VALUE ZERO.
05 YOU-PROVIDE-SEED PIC X VALUE SPACE.
05 RUN-AGAIN PIC X VALUE SPACE.
05 PAUSE-FOR-A-SECOND PIC X VALUE SPACE.
01 SEED-TIME.
05 HOURS PIC 99.
05 MINUTES PIC 99.
05 SECONDS PIC 99.
05 MS PIC 99.
*
* LINKAGE SECTION.
* Not used during testing
01 RANDGEN-AREA.
05 COUNT-OF-ACCESSES PIC 9(12) VALUE ZERO.
05 RANDOM-NUMBERS-NEEDED PIC 9(12) VALUE ZERO.
05 RANDOM-NUMBER PIC 9(12) VALUE ZERO.
05 RANDOM-MSG PIC X(60) VALUE SPACE.
*
* PROCEDURE DIVISION USING RANDGEN-AREA.
* Not used during testing
*
PROCEDURE DIVISION.
100-RANDGEN-EDIT-HOUSEKEEPING.
MOVE SPACE TO RANDOM-MSG.
IF RANDOM-NUMBERS-NEEDED = ZERO
DISPLAY 'RANDOM-NUMBERS-NEEDED ' NO ADVANCING
ACCEPT RANDOM-NUMBERS-NEEDED.
IF RANDOM-NUMBERS-NEEDED NOT NUMERIC
MOVE 'RANDOM-NUMBERS-NEEDED NOT NUMERIC' TO RANDOM-MSG
GO TO 900-EXIT-RANDGEN.
IF RANDOM-NUMBERS-NEEDED = ZERO
MOVE 'RANDOM-NUMBERS-NEEDED = ZERO' TO RANDOM-MSG
GO TO 900-EXIT-RANDGEN.
IF COUNT-OF-ACCESSES NOT NUMERIC
MOVE 'COUNT-OF-ACCESSES NOT NUMERIC' TO RANDOM-MSG
GO TO 900-EXIT-RANDGEN.
IF COUNT-OF-ACCESSES GREATER THAN RANDOM-NUMBERS-NEEDED
MOVE 'COUNT-OF-ACCESSES > THAT RANDOM-NUMBERS-NEEDED'
TO RANDOM-MSG
GO TO 900-EXIT-RANDGEN.
IF YOU-PROVIDE-SEED = SPACE AND RANDOM-NUMBER = ZERO
DISPLAY 'DO YOU WANT TO PROVIDE SEED Y OR N: '
NO ADVANCING
ACCEPT YOU-PROVIDE-SEED.
IF RANDOM-NUMBER = ZERO AND
(YOU-PROVIDE-SEED = 'Y' OR 'y')
DISPLAY 'ENTER SEED ' NO ADVANCING
ACCEPT RANDOM-NUMBER.
IF RANDOM-NUMBER NOT NUMERIC
MOVE 'RANDOM-NUMBER NOT NUMERIC' TO RANDOM-MSG
GO TO 900-EXIT-RANDGEN.
200-RANDGEN-DATA-HOUSEKEEPING.
MOVE FUNCTION CURRENT-DATE (9:8) TO SEED-TIME.
IF COUNT-OF-ACCESSES = ZERO
COMPUTE LOW-RANGE =
((SECONDS * HOURS * MINUTES * MS) / 3).
COMPUTE RANDOM-NUMBER-BUILT = RANDOM-NUMBER + LOW-RANGE.
COMPUTE HIGH-RANGE = RANDOM-NUMBERS-NEEDED + LOW-RANGE.
MOVE X2-POWER TO 2X2.
300-SET-2X2-DIVISOR.
IF 2X2 < (HIGH-RANGE + 1)
COMPUTE 2X2 = 2X2 * X2-POWER
GO TO 300-SET-2X2-DIVISOR.
* *********************************************************
* IF FIRST TIME THROUGH AND YOU WANT TO BUILD A SEED. *
* *********************************************************
IF COUNT-OF-ACCESSES = ZERO AND RANDOM-NUMBER = ZERO
COMPUTE RANDOM-NUMBER-BUILT =
((SECONDS * HOURS * MINUTES * MS) + HIGH-RANGE).
IF COUNT-OF-ACCESSES = ZERO
DISPLAY 'SEED TIME ' SEED-TIME
' RANDOM-NUMBER-BUILT ' RANDOM-NUMBER-BUILT
' LOW-RANGE ' LOW-RANGE.
* *********************************************
* END OF BUILDING A SEED IF YOU WANTED TO *
* *********************************************
* ***************************************************
* THIS PROCESS IS WHERE THE RANDOM-NUMBER IS BUILT *
* ***************************************************
400-RANDGEN-FORMULA.
COMPUTE FIRST-PART = (5 * RANDOM-NUMBER-BUILT) + 7.
DIVIDE FIRST-PART BY 2X2 GIVING WORKING-NUMBER
REMAINDER RANDOM-NUMBER-BUILT.
IF RANDOM-NUMBER-BUILT > LOW-RANGE AND
RANDOM-NUMBER-BUILT < (HIGH-RANGE + 1)
GO TO 600-RANDGEN-CLEANUP.
GO TO 400-RANDGEN-FORMULA.
* *********************************************
* GOOD RANDOM NUMBER HAS BEEN BUILT *
* *********************************************
600-RANDGEN-CLEANUP.
ADD 1 TO COUNT-OF-ACCESSES.
COMPUTE RANDOM-NUMBER =
RANDOM-NUMBER-BUILT - LOW-RANGE.
* *******************************************************
* THE NEXT 3 LINE OF CODE ARE FOR TESTING ON CONSOLE *
* *******************************************************
DISPLAY RANDOM-NUMBER.
IF COUNT-OF-ACCESSES < RANDOM-NUMBERS-NEEDED
GO TO 100-RANDGEN-EDIT-HOUSEKEEPING.
900-EXIT-RANDGEN.
IF RANDOM-MSG NOT = SPACE
DISPLAY 'RANDOM-MSG: ' RANDOM-MSG.
MOVE ZERO TO COUNT-OF-ACCESSES RANDOM-NUMBERS-NEEDED RANDOM-NUMBER.
MOVE SPACE TO YOU-PROVIDE-SEED RUN-AGAIN.
DISPLAY 'RUN AGAIN Y OR N '
NO ADVANCING.
ACCEPT RUN-AGAIN.
IF (RUN-AGAIN = 'Y' OR 'y')
GO TO 100-RANDGEN-EDIT-HOUSEKEEPING.
ACCEPT PAUSE-FOR-A-SECOND.
GOBACK.
这里的大多数答案都不能保证它们不会两次返回相同的数字。这是一个正确的解决方案:
int nrrand(void) {
static int s = 1;
static int start = -1;
do {
s = (s * 1103515245 + 12345) & 1023;
} while (s >= 1001);
if (start < 0) start = s;
else if (s == start) abort();
return s;
}
我不确定约束是否指定正确。有人假设在其他1000个输出之后,允许重复一个值,但是天真地允许0在0之后紧随其后,只要它们都出现在1000个集合的末尾即可。相反,可以保持一定距离重复之间还有1000个其他值,这样做会导致序列每次都以完全相同的方式重播自身的情况,因为没有其他值发生在该限制范围之外。
这是一种始终可以保证至少有500个其他值的方法,然后才能重复该值:
int nrrand(void) {
static int h[1001];
static int n = -1;
if (n < 0) {
int s = 1;
for (int i = 0; i < 1001; i++) {
do {
s = (s * 1103515245 + 12345) & 1023;
} while (s >= 1001);
/* If we used `i` rather than `s` then our early results would be poorly distributed. */
h[i] = s;
}
n = 0;
}
int i = rand(500);
if (i != 0) {
i = (n + i) % 1001;
int t = h[i];
h[i] = h[n];
h[n] = t;
}
i = h[n];
n = (n + 1) % 1001;
return i;
}
当N大于1000且需要绘制K个随机样本时,可以使用包含到目前为止样本的集合。对于每个抽奖,您都使用拒绝抽样,这将是“几乎” O(1)的操作,因此使用O(N)存储时,总运行时间接近O(K)。
当K“接近” N时,该算法会发生冲突。这意味着运行时间将比O(K)差很多。一个简单的解决方法是颠倒逻辑,以便对于K> N / 2,保留所有尚未绘制的样本的记录。每次抽奖都会从拒绝集中删除一个样本。
拒绝采样的另一个明显问题是它是O(N)存储,如果N在数十亿或更多,这是个坏消息。但是,有一种算法可以解决该问题。该算法发明人之后被称为Vitter算法。该算法在此处描述。维特算法的要点是,每次抽奖后,您都使用一定的分布来计算随机跳跃,以保证均匀采样。
for i from n−1 downto 1 do
j ← random integer such that 0 ≤ j ≤ i
exchange a[j] and a[i]
实际上是O(n-1),因为您只需要为最后两个交换一次即可。
这是C#
public static List<int> FisherYates(int n)
{
List<int> list = new List<int>(Enumerable.Range(0, n));
Random rand = new Random();
int swap;
int temp;
for (int i = n - 1; i > 0; i--)
{
swap = rand.Next(i + 1); //.net rand is not inclusive
if(swap != i) // it can stay in place - if you force a move it is not a uniform shuffle
{
temp = list[i];
list[i] = list[swap];
list[swap] = temp;
}
}
return list;
}
请在以下位置查看我的答案 https://stackoverflow.com/a/46807110/8794687
它是最简单的算法之一,平均时间复杂度为O(s log s),s表示样本大小。还有一些指向哈希表算法的链接,这些哈希表的算法的复杂度被称为O(s)。
有人张贴“在excel中创建随机数”。我正在使用这个理想。创建一个由str.index和str.ran两部分组成的结构;对于10个随机数,创建10个结构的数组。将str.index设置为0到9,并将str.ran设置为其他随机数。
for(i=0;i<10; ++i) {
arr[i].index = i;
arr[i].ran = rand();
}
根据arr [i] .ran中的值对数组排序。现在,str.index处于随机顺序。下面是c代码:
#include <stdio.h>
#include <stdlib.h>
struct RanStr { int index; int ran;};
struct RanStr arr[10];
int sort_function(const void *a, const void *b);
int main(int argc, char *argv[])
{
int cnt, i;
//seed(125);
for(i=0;i<10; ++i)
{
arr[i].ran = rand();
arr[i].index = i;
printf("arr[%d] Initial Order=%2d, random=%d\n", i, arr[i].index, arr[i].ran);
}
qsort( (void *)arr, 10, sizeof(arr[0]), sort_function);
printf("\n===================\n");
for(i=0;i<10; ++i)
{
printf("arr[%d] Random Order=%2d, random=%d\n", i, arr[i].index, arr[i].ran);
}
return 0;
}
int sort_function(const void *a, const void *b)
{
struct RanStr *a1, *b1;
a1=(struct RanStr *) a;
b1=(struct RanStr *) b;
return( a1->ran - b1->ran );
}