如何让我的c程序随机选择

时间:2012-01-07 02:34:35

标签: c random

我想制作一个C程序,该程序从范围1 - 37的数字中随机选择6个数字,而不重复任何先前的排列。例如,假设程序随机选择1,2,3,4,5,6。如果下一个排列被随机选为2,1,3,4,5,6,那就没问题。但是,如果再次选择1,2,3,4,5,6,则表示不正常。我希望这种情况继续下去,直到没有更多可用的设置。我将如何编写此C程序?

2 个答案:

答案 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);
} 

参考:http://rosettacode.org/wiki/Knuth_shuffle#C

答案 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还有其他讨论。也许数据库是有保证的。去搜索这些解决方案并将其应用于此处。