我想制作一个C
程序,该程序从范围1 - 37
的数字中随机选择6个数字,而不重复任何先前的排列。例如,假设程序随机选择1,2,3,4,5,6
。如果下一个排列被随机选为2,1,3,4,5,6
,那就没问题。但是,如果再次选择1,2,3,4,5,6
,则表示不正常。我希望这种情况继续下去,直到没有更多可用的设置。我将如何编写此C
程序?
答案 0 :(得分:3)
使用Knuth Shuffle。给你O(n)渐近复杂度。
#include <stdlib.h>
#include <string.h>
int rrand(int m)
{
return (int)((double)m * ( rand() / (RAND_MAX+1.0) ));
}
#define BYTE(X) ((unsigned char *)(X))
void shuffle(void *obj, size_t nmemb, size_t size)
{
void *temp = malloc(size);
size_t n = nmemb;
while ( n > 1 ) {
size_t k = rrand(n--);
memcpy(temp, BYTE(obj) + n*size, size);
memcpy(BYTE(obj) + n*size, BYTE(obj) + k*size, size);
memcpy(BYTE(obj) + k*size, temp, size);
}
free(temp);
}
答案 1 :(得分:2)
现在,下面发布的KNUTH shuffle答案相当优雅。但它不符合OP在其质询中提出的特定要求。
OP说他希望能够以随机顺序选择所有集合,直到他的程序全部消耗掉它们。所以这里。出于演示的目的,我们将“选择”理解为“打印”。
“1-37范围内6个唯一数字”的可能组合总数可表示为:
TOTAL_NUMBER_OF_SETS = 37*36*35*34*33*32 = 1673844480
1673844480(16亿)非常适合签名的32位数字。并且可以为每个唯一集合分配唯一的整数id。
所以...如果你能在[0,1673844479]之间生成一个随机数,我们可以将它映射到一组非常特殊的6个唯一整数。
要构造集合,我们需要一个辅助函数,它允许我们跟踪在构造集合的迭代过程中已经使用了1-37之间的值。然后用一个模数运算来帮助我们将ID号映射到它的6位数集:
#include <stdio.h>
#include <stdlib.h
#include <stdint.h>
const uint32_t TOTAL_NUMBER_OF_SETS = 37*36*35*34*33*32; // = 1673844480
// returns the Nth value value from the ordered set {1,range},
// skipping over elements previous selected
int GetAvailableElementFromSet(int n, int range, int inuse[])
{
int i = 0, x;
for (x = 0; x < range; x++)
{
if (inuse[x] == 0)
{
if (i == n)
{
inuse[x] = 1;
return x + 1; // +1 since the loop variable has a zero-based index
}
i++;
}
}
return -1; // error
}
void GetSpecificSet(uint32_t setindex, int output[])
{
int index;
int inuse[37] = {}; // boolean array of elements already picked for the output set. zero-init to all false
int j,k;
if (setindex >= TOTAL_NUMBER_OF_SETS)
return; // error!
for (j = 0; j < 6; j++)
{
index = setindex % (37-j);
output[j] = GetAvailableElementFromSet(index, 37, inuse);
setindex = setindex / (37-j) ;
}
}
为了证明这是有效的,我们可以在所有集合上迭代另一个函数:
void PrintSet(uint32_t setindex)
{
int output[6];
GetSpecificSet(setindex, output);
printf("%d, %d, %d, %d, %d, %d\n", output[0], output[1], output[2], output[3], output[4], output[5]);
}
void PrintAllSetsInOrder()
{
uint32_t index;
for (index = 0; index < TOTAL_NUMBER_OF_SETS; index++)
{
PrintSet(index);
}
}
现在上面的程序将打印出所有集合:
{1,2,3,4,5,6} // first set
{2,1,3,4,5,6} // second set
{3,1,2,4,5,6} // third set
以
结尾{36, 37, 35, 34, 33, 32} // next to last set
{37, 36, 35, 34, 33, 32} // last set
然后显然要打印一个随机集:
void PrintRandomSet()
{
PrintSet(rand() % TOTAL_NUMBER_OF_SETS);
}
但OP希望所有套装以随机顺序打印而不重复。这很棘手,因为我们必须跟踪先前生成的随机数值。我可以想到几种方法来做到这一点。最常见的候选解决方案是保持由TOTAL_NUMBER_OF_SETS位组成的位掩码。那就是:
#define IS_BIT_SET(bmask, bitindex) (bmask[bitindex/8] & (0x01<<(bitindex%8)))
#define SET_BIT(bmask, bitindex) {bmask[bitindex/8] |= (0x01<<(bitindex%8));}
uint8_t* bitmask = calloc(TOTAL_NUMBER_OF_SETS/8 + 1);
分配的内存大约为200MB。大,但可行。然后我们继续从[0-TOTAL_NUMBER_OF_SETS]范围中选择随机数,检查位掩码是否已被使用,然后在设置其位掩码位置后用随机数调用PrintSet。重复,直到打印完所有TOTAL_NUMBER_OF_SETS。
用于工作但有问题的解决方案的伪代码
for (x = 0; x < TOTAL_NUMBER_OF_SETS; x++)
{
index = rand()%TOTAL_NUMBER_OF_SETS;
while (IS_BIT_SET(bitmask, index))
{
index = (index + 1) % TOTAL_NUMBER_OF_SETS;
}
SET_BIT(bitmask, index);
PrintSet(index);
}
现在这应该工作得很好。但随着bitmask阵列开始被填满,它会慢慢变得迟钝。后来的迭代将花费大部分时间来扫描寻找未设置索引值的位数组。关于如何对大型集合进行高效且统一的排列,StackOverflow还有其他讨论。也许数据库是有保证的。去搜索这些解决方案并将其应用于此处。