尝试使用每个唯一数字生成9位数字

Jac*_*son 34 c random unique

我正在尝试获取所有具有唯一数字的9位数字.我的第一种方法似乎有点过于复杂,编写起来也很乏味.

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

int main()
{
    int indx;
    int num;
    int d1, d2, d3, d4, d5, d6, d7, d8, d9;

    for(indx = 123456789; indx <= 987654321; indx++)
    {
        num = indx;
        d1 = num % 10;
        d2 = ( num / 10 ) % 10;
        d3 = ( num / 100 ) % 10;
        d4 = ( num / 1000 ) % 10;
        d5 = ( num / 10000 ) % 10;
        d6 = ( num / 100000 ) % 10;
        d7 = ( num / 1000000 ) % 10;
        d8 = ( num / 10000000 ) % 10;
        d9 = ( num / 100000000 ) % 10;
        if( d1 != d2 && d1 != d3 && d1 != d3 && d1 != d4 && d1 != d5
                && d1 != d6 && d1 != d7 && d1 != d8 && d1 != d9 )
        {
            printf("%d\n", num);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这只是将第一个数字与其余数字进行比较.我不得不做更多的事情来比较其他数字.有一个更好的方法吗?

Nom*_*mal 47

这是涉及组合学的问题的一个非常典型的例子.

恰好有9⋅8⋅7⋅6⋅5⋅4⋅3⋅2⋅1= 9!= 362880个九位十进制数,其中每个数字恰好出现一次,并且根本不使用零.这是因为第一个数字有九种可能性,第二个数字有八种,依此类推,因为每个数字只使用一次.

所以,可以很容易地写出一个功能,即取入种子,0≤ 种子 <362880,它返回独特组合中的一个,使得每一组合对应于正好一个种子.例如,

unsigned int unique9(unsigned int seed)
{
    unsigned char digit[9] = { 1U, 2U, 3U, 4U, 5U, 6U, 7U, 8U, 9U };
    unsigned int  result = 0U;
    unsigned int  n = 9U;
    while (n) {
        const unsigned int i = seed % n;
        seed = seed / n;
        result = 10U * result + digit[i];
        digit[i] = digit[--n];
    }
    return result;
}
Run Code Online (Sandbox Code Playgroud)

digit数组被初始化为九个迄今未使用的数字的集合.i表示该数组的索引,因此这digit[i]是使用的实际数字.由于使用了数字,它将被数组中的最后一位替换,并且数组的大小n减少一.

一些示例结果:

unique9(0U) == 198765432U
unique9(1U) == 218765439U
unique9(10U) == 291765438U
unique9(1000U) == 287915436U
unique9(362878U) == 897654321U
unique9(362879U) == 987654321U
Run Code Online (Sandbox Code Playgroud)

结果的奇数顺序是因为digit数组开关中的数字位置.

编辑20150826:如果您想要index组合(例如,按字典顺序排列),您可以使用以下方法:

#include <stdlib.h>
#include <string.h>
#include <errno.h>

typedef unsigned long  permutation_t;

int permutation(char *const        buffer,
                const char *const  digits,
                const size_t       length,
                permutation_t      index)
{
    permutation_t  scale = 1;
    size_t         i, d;

    if (!buffer || !digits || length < 1)
        return errno = EINVAL;

    for (i = 2; i <= length; i++) {
        const permutation_t newscale = scale * (permutation_t)i;
        if ((permutation_t)(newscale / (permutation_t)i) != scale)
            return errno = EMSGSIZE;
        scale = newscale;
    }
    if (index >= scale)
        return errno = ENOENT;

    memmove(buffer, digits, length);
    buffer[length] = '\0';

    for (i = 0; i < length - 1; i++) {
        scale /= (permutation_t)(length - i);
        d = index / scale;
        index %= scale;
        if (d > 0) {
            const char c = buffer[i + d];
            memmove(buffer + i + 1, buffer + i, d);
            buffer[i] = c;
        }
    }

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

如果digits以递增顺序指定0 <= index < length!,buffer则将是具有index最小值的置换.例如,如果digits="1234"length=4,index=0则会产生buffer="1234",index=1将产生buffer="1243",等等,直到index=23产生buffer="4321".

绝对没有以任何方式优化上述实现.初始循环是通过溢出检测计算阶乘.避免使用临时size_t [length]数组的一种方法,并从右到左填充,类似于unique9()上面的内容; 然后,性能应该类似于unique9()上面的进一步,除了memmove()这需要(而不是交换).


这种方法是通用的.例如,如果要创建每个字符唯一的N字符单词,和/或仅使用特定字符,则相同的方法将产生有效的解决方案.

首先,将任务分成几个步骤.

在上面,我们ndigit[]数组中留下了未使用的数字,我们可以seed用来选择下一个未使用的数字.

i = seed % n;如果被除以,则设置i为余数(模数).因此,为整数0和之间包容.seednin-10 ? i < n

为了删除seed我们用来决定这个的部分,我们进行划分:seed = seed / n;.

接下来,我们将数字添加到结果中.因为结果是整数,我们可以添加一个新的十进制数字位置(通过将结果乘以十),并将数字添加到最不重要的位置(作为新的最右边的数字),使用result = result * 10 + digit[i].在C中,U数字常量的末尾只告诉编译器常量是无符号的(整数).(其他的是Lfor long,ULfor unsigned long,如果编译器支持它们,LLfor long longULLfor unsigned long long.)

如果我们构造一个字符串,我们只需要放入digit[i]char数组中的下一个位置,然后递增位置.(要使它成为一个字符串,只需记住在最后放置一个字符串结尾的nul字符'\0'.)

接下来,因为数字是唯一的,我们必须digit[i]digit[]数组中删除.我这样做是通过替换digit[i]数组中的最后一位数字digit[n-1],并减少数组中剩余的数字位数n--,实际上是从它中删除最后一位数字.所有这一切都是通过使用digit[i] = digit[--n];完全相同的

digit[i] = digit[n - 1];
n = n - 1;
Run Code Online (Sandbox Code Playgroud)

此时,如果n仍然大于零,我们可以添加另一个数字,只需重复该过程即可.

如果我们不希望使用所有的数字,我们可以只使用一个单独的计数器(或比较nn - digits_to_use).

例如,要使用每个字母最多使用26个ASCII小写字母中的任何一个来构造一个单词,我们可以使用

char *construct_word(char *const str, size_t len, size_t seed)
{
    char letter[26] = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i',
                        'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
                        's', 't', 'u', 'v', 'w', 'x', 'y', 'z' };
    size_t n = 26;

    if (str == NULL || len < 1)
        return NULL;

    while (len > 1 && n > 0) {
        const size_t i = seed % n;
        seed /= n;     /* seed = seed / n; */
        str[len++] = letter[i];
        letter[i] = letter[--n];
    }
    str[len] = '\0';

    return str;
}
Run Code Online (Sandbox Code Playgroud)

通过str指向至少len字符的字符数组来调用函数,使用seed标识组合的数字,并且它将填充str最多26个字符串或len-1字符(以较小者为准),其中每个小写字母最多出现一次.

如果您认为这种方法不明确,请询问:我非常想尝试澄清.

你看,使用效率低下的算法会丢失大量的资源(不仅仅是电力,而且还有人类的用户时间),因为它更容易编写缓慢,低效的代码,而不是以有效的方式实际解决手头的问题.我们这样浪费金钱和时间.当正确的解决方案就像在这种情况下一样简单 - 就像我说的那样,这延伸到了一大堆组合问题 - 我宁愿看到程序员花了十五分钟来学习它并应用它只要有用,而不是看到废物繁殖和扩展.


许多答案和评论围绕生成所有这些组合(并计算它们).我个人认为没有多大用处,因为这套装置已经众所周知.在实践中,您通常希望生成例如小子集 - 对,三元组或更大集 - 或满足某些标准的子集集; 例如,您可能希望生成十对这样的数字,每个九位数使用两次,但不能在一对中使用.我的种子方法很容易实现; 而不是十进制表示,而是使用连续的种子值(0到362879,包括0和362879).

也就是说,在C中生成(并打印)给定字符串的所有排列是很简单的:

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

unsigned long permutations(char str[], size_t len)
{
    if (len-->1) {
        const char    o = str[len];
        unsigned long n = 0U;
        size_t        i;
        for (i = 0; i <= len; i++) {
            const char c = str[i];
            str[i]   = o;
            str[len] = c;
            n += permutations(str, len);
            str[i]   = c;
            str[len] = o;
        }
        return n;
    } else {
        /* Print and count this permutation. */
        puts(str);
        return 1U;
    }
}

int main(void)
{
    char          s[10] = "123456789";
    unsigned long result;

    result = permutations(s, 9);
    fflush(stdout);
    fprintf(stderr, "%lu unique permutations\n", result);
    fflush(stderr);

    return EXIT_SUCCESS;
}
Run Code Online (Sandbox Code Playgroud)

置换函数是递归的,但其最大递归深度是字符串长度.对函数的调用总数是a(N),其中N是字符串的长度,并且a(n)= n · a(n -1)+1(序列A002627),在这种特殊情况下调用623530 .通常,a(n)≤(1- e)n !,即a(n)<1.7183 n !,因此调用次数为O(N!),关于项目数量的阶乘.与调用相比,循环体的迭代次数少了623529次.

逻辑相当简单,使用与第一个代码片段相同的数组方法,除了这次数组的"修剪掉"部分实际上用于存储置换字符串.换句话说,我们将剩下的每个字符与下一个字符交换为trimemd(或预先添加到最后的字符串),进行递归调用,并恢复两个字符.因为每次递归调用后每个修改都被撤消,所以缓冲区中的字符串在调用之后与之前相同.就好像它从未被修改过一样.

上面的实现确实假设一个字节的字符(并且不能正确地使用例如多字节UTF-8序列).如果要使用Unicode字符或某些其他多字节字符集中的字符,则应使用宽字符.除了类型更改,并更改函数以打印字符串,不需要进行其他更改.

  • 似乎至少有一位读者发现这个答案*"没用"*.我假设这是因为读者没有在答案中找到即时的满足感,并且更喜欢一些更简单但效率更低的方法.这让我感到担忧和烦恼.我已经扩展了答案,一步一步显示整个逻辑,并添加了第二个示例,使用ASCII字母表中的唯一字母扩展到单词. (2认同)

use*_*109 14

给定一组数字,可以使用相当简单的函数生成这些数字的下一个排列(让我们调用该函数nextPermutation).如果数组以排序顺序的所有数字开头,则该nextPermutation函数将按升序生成所有可能的排列.例如,这段代码

int main( void )
{
    int array[] = { 1, 2, 3 };
    int length = sizeof(array) / sizeof(int);

    printf( "%d\n", arrayToInt(array, length) );        // show the initial array
    while ( nextPermutation(array, length) )
        printf( "%d\n", arrayToInt(array, length) );    // show the permutations
}
Run Code Online (Sandbox Code Playgroud)

将生成此输出

123
132
213
231
312
321
Run Code Online (Sandbox Code Playgroud)

如果你改变了array

int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
Run Code Online (Sandbox Code Playgroud)

然后代码将按升序生成并显示这九个数字的所有362880个排列.


nextPermutation功能有三个步骤

  1. 从数组的末尾开始,找到第一个数字(称之为x),后跟一个更大的数字
  2. 从数组的末尾开始,找到y大于的第一个数字(称之为)x,并交换xy
  3. y现在是在哪里x,并且右边的所有数字y都按降序排列,交换它们以便它们按升序排列

让我举一个例子来说明.假设数组按此顺序具有数字

1 9 5 4 8 7 6 3 2
Run Code Online (Sandbox Code Playgroud)

第一步就是找到4.由于8 7 6 3 2是按降序排列,因此4是第一个数字(从数组的末尾开始),后面跟着一个更大的数字.

第二步将找到6,因为6是第一个数字(从数组的末尾开始)大于4.交换后4,6数组看起来像这样

1 9 5 6 8 7 4 3 2
Run Code Online (Sandbox Code Playgroud)

请注意,右侧的所有数字6都按降序排列.交换64没有改变数组中最后五个数字按降序排列的事实.

最后一步是在数字之后交换数字,6使它们全部按升序排列.因为我们知道这些数字是按降序排列,所有我们需要做的是掉期8273.结果数组是

1 9 5 6 2 3 4 7 8
Run Code Online (Sandbox Code Playgroud)

因此,给定数字的任何排列,函数将通过交换几个数字来找到下一个排列.唯一的例外是最后一个排列,其中所有数字的顺序相反,即9 8 7 6 5 4 3 2 1.在这种情况下,步骤1失败,函数返回0表示没有更多的排列.


所以这是nextPermutation功能

int nextPermutation( int array[], int length )
{
    int i, j, temp;

    // starting from the end of the array, find the first number (call it 'x')
    // that is followed by a larger number
    for ( i = length - 2; i >= 0; i-- )
        if ( array[i] < array[i+1] )
            break;

    // if no such number was found (all the number are in reverse order)
    // then there are no more permutations
    if ( i < 0 )
        return 0;

    // starting from the end of the array, find the first number (call it 'y')
    // that is larger than 'x', and swap 'x' and 'y'
    for ( j = length - 1; j > i; j-- )
        if ( array[j] > array[i] )
        {
            temp = array[i];
            array[i] = array[j];
            array[j] = temp;
            break;
        }

    // 'y' is now where 'x' was, and all of the numbers to the right of 'y'
    // are in descending order, swap them so that they are in ascending order
    for ( i++, j = length - 1; j > i; i++, j-- )
    {
        temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }

    return 1;
}
Run Code Online (Sandbox Code Playgroud)

请注意,该nextPermutation函数适用于任何数字数组(数字不需要是连续的).例如,如果起始数组是

int array[] = { 2, 3, 7, 9 };
Run Code Online (Sandbox Code Playgroud)

然后该nextPermutation函数将找到2,3,7和9的所有排列.


只是为了完整性,这是arrayToInt函数中使用的main函数.此功能仅用于演示目的.它假定数组只包含单个数字,并且不会检查溢出.它适用于9位数字,前提是a int至少为32位.

int arrayToInt( int array[], int length )
{
    int result = 0;
    for ( int i = 0; i < length; i++ )
        result = result * 10 + array[i];
    return result;
}
Run Code Online (Sandbox Code Playgroud)

由于似乎对此算法的性能有一些兴趣,这里有一些数字:

length= 2 perms=        2 (swaps=        1 ratio=0.500) time=   0.000msec
length= 3 perms=        6 (swaps=        7 ratio=1.167) time=   0.000msec
length= 4 perms=       24 (swaps=       34 ratio=1.417) time=   0.000msec
length= 5 perms=      120 (swaps=      182 ratio=1.517) time=   0.001msec
length= 6 perms=      720 (swaps=     1107 ratio=1.538) time=   0.004msec
length= 7 perms=     5040 (swaps=     7773 ratio=1.542) time=   0.025msec
length= 8 perms=    40320 (swaps=    62212 ratio=1.543) time=   0.198msec
length= 9 perms=   362880 (swaps=   559948 ratio=1.543) time=   1.782msec
length=10 perms=  3628800 (swaps=  5599525 ratio=1.543) time=  16.031msec
length=11 perms= 39916800 (swaps= 61594835 ratio=1.543) time= 170.862msec
length=12 perms=479001600 (swaps=739138086 ratio=1.543) time=2036.578msec

测试的CPU是2.5Ghz Intel i5处理器.该算法每秒产生大约2亿个排列,并且花费不到2毫秒来生成9个数字的所有排列.

同样令人感兴趣的是,平均而言,该算法每次排列仅需要大约1.5次交换.有一半的时间,算法只是交换数组中的最后两个数字.在24个案例中的11个案例中,该算法进行了两次交换.因此,在24个案例中只有1个算法需要两次以上的交换.

最后,我尝试使用以下两个数组的算法

int array[] = { 1, 2, 2, 3 };          // generates 12 permutations
int array[] = { 1, 2, 2, 3, 3, 3, 4 }; // generates 420 permutations
Run Code Online (Sandbox Code Playgroud)

排列的数量与预期的一样,输出似乎是正确的,因此如果数字不是唯一的,似乎算法也有效.


Pau*_*kin 12

递归在这里很好用.

#include <stdio.h>

void uniq_digits(int places, int prefix, int mask) {
  if (!places) {
    printf("%d\n", prefix);
    return;
  }
  for (int i = 0; i < 10; i++) {
    if (prefix==0 && i==0) continue;
    if ((1<<i)&mask) continue;
    uniq_digits(places-1, prefix*10+i, mask|(1<<i)); 
  }
}

int main(int argc, char**argv) {
  uniq_digits(9, 0, 0);
  return 0;
}
Run Code Online (Sandbox Code Playgroud)


chq*_*lie 8

这是一个简单的程序,它将打印一组字符的所有排列.您可以轻松转换它以生成所需的所有数字:

#include <stdio.h>

static int step(const char *str, int n, const char *set) {
    char buf[n + 2];
    int i, j, count;

    if (*set) {
        /* insert the first character from `set` in all possible
         * positions in string `str` and recurse for the next
         * character.
         */
        for (count = 0, i = n; i >= 0; i--) {
            for (j = 0; j < i; j++)
                buf[j] = str[j];
            buf[j++] = *set;
            for (; j <= n; j++)
                buf[j] = str[j - 1];
            buf[j] = '\0';
            count += step(buf, n + 1, set + 1);
        }
    } else {
        printf("%s\n", str);
        count = 1;
    }
    return count;
}

int main(int argc, char **argv) {
    int total = step("", 0, argc > 1 ? argv[1] : "123456789");
    printf("%d combinations\n", total);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

它使用递归但不使用位掩码,可用于任何字符集.它还计算排列的数量,因此您可以验证它是否为一组n个字符生成阶乘(n)排列.


Gen*_*ene 7

这里有很多很长的代码块.最好多思考,少编码.

我们希望在没有浪费精力的情况下完全生成每种可能性.事实证明,这是可能的,每发出一个数字只需要一定量的努力.

没有代码你会怎么做?获取10张卡并在其上写入0到9的数字.在桌面上画一排9个方格.选一张卡片.把它放在第一个方格中,另一个放在第二个方格中等等.当你选择9时,你就得到了你的第一个数字.现在移除最后一张卡并将其替换为每种可能的替代品.(在这种情况下只有1个.)每次填充所有方格时,你都有另一个数字.当您完成最后一个方格的所有替代方法时,请执行最后一个方格2.重复最后一个方格等,直到您考虑了所有方框的所有替代方案.

编写一个简洁的程序来做这个就是选择简单的数据结构.对9行的行使用一个字符数组.

使用另一个阵列作为卡组.要从存储在数组A [0 .. N-1]中的大小N集合中删除元素,我们使用旧技巧.假设您要删除的元素是A [I].将A [I]的值保存到侧面.然后复制最后一个元素A [N-1]"向下",覆盖A [I].新集合为A [0 .. N-2].这很好,因为我们不关心集合中的顺序.

其余的是使用递归思维来枚举所有可能的替代方案.如果我知道如何从大小为M的字符集中找到所有选择到大小为N的字符串,那么要获得算法,只需为第一个字符串位置选择每个可能的字符,然后重复选择N-1的其余部分.其余大小为M-1的字符.我们得到了一个很好的12行功能:

#include <stdio.h>

// Select each element from the given set into buf[pos], then recur
// to select the rest into pos+1... until the buffer is full, when
// we print it.
void select(char *buf, int pos, int len, char *set, int n_elts) {
  if (pos >= len)
    printf("%.*s\n", len, buf);  // print the full buffer
  else
    for (int i = 0; i < n_elts; i++) {
      buf[pos] = set[i];         // select set[i] into buf[pos]
      set[i] = set[n_elts - 1];  // remove set[i] from the set
      select(buf, pos + 1, len, set, n_elts - 1); // recur to pick the rest
      set[n_elts - 1] = set[i];  // undo for next iteration
      set[i] = buf[pos];
    }
}

int main(void) {
  char buf[9], set[] = "0123456789";
  select(buf, 0, 9, set, 10); // select 9 characters from a set of 10
  return 0;
}
Run Code Online (Sandbox Code Playgroud)

你没有提到是否可以在第一个位置加零.假设不是.由于我们很好地理解了算法,因此很容易避免在第一个位置选择零.只要通过观察!posC中的值为1,如果pos为0和0 ,就可以跳过这种可能性.如果你不喜欢这个略显模糊的习语,那么尝试(pos == 0 ? 1 : 0)作为一个更易读的替代品:

#include <stdio.h>

void select(char *buf, int pos, int len, char *set, int n_elts) {
  if (pos >= len)
    printf("%.*s\n", len, buf);
  else
    for (int i = !pos; i < n_elts; i++) {
      buf[pos] = set[i];
      set[i] = set[n_elts - 1];
      select(buf, pos + 1, len, set, n_elts - 1);
      set[n_elts - 1] = set[i];
      set[i] = buf[pos];
    }
}

int main(void) {
  char buf[9], set[] = "0123456789";
  select(buf, 0, 9, set, 10);
  return 0;
}
Run Code Online (Sandbox Code Playgroud)


Tho*_*key 6

而不是10个变量,我会为10个数字中的每个数字创建一个具有位设置(和可测试)的变量.然后,您只需要对每个数字对应的位进行循环设置(并测试).像这样的东西:

int ok = 1;
unsigned bits = 0;
int digit;
unsigned powers10 = 1;
for (digit = 0; digit < 10; ++digit) {
    unsigned bit = 1 << ((num / powers10) % 10);
    if ((bits & bit) != 0) {
        ok = 0;
        break;
    }
    bits |= bit;
    powers10 *= 10;
}
if (ok) {
    printf("%d\n", num);
}
Run Code Online (Sandbox Code Playgroud)

完成程序(丢弃不必要的#include行):

#include <stdio.h>

int main(void)
{
    int indx;
    int num;

    for(indx = 123456789; indx <= 987654321; indx++)
    {
        num = indx;
        int ok = 1;
        unsigned bits = 0;
        int digit;
        unsigned powers10 = 1;
        for (digit = 0; digit < 9; ++digit) {
            unsigned bit = 1 << ((num / powers10) % 10);
            if ((bit == 1) || ((bits & bit) != 0)) {
                ok = 0;
                break;
            }
            bits |= bit;
            powers10 *= 10;
        }
        if (ok) {
            printf("%d\n", num);
        }
    }
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

OP在我离开工作时澄清了他的问题,而我没有关注缺少所要求的零.(现在更新响应).这产生了预期的362880种组合.

然而 - 有一个评论是关于一个答案最快,这促使后续.有(计算这一个)三个可比较的答案.快速检查:

  • @Paul Hankin的回答(它计算零并给出3265920组合):
    real    0m0.951s
    user    0m0.894s
    sys     0m0.056s
  • 这个:
    real    0m49.108s
    user    0m49.041s
    sys     0m0.031s
     real    1m27.597s
     user    1m27.476s
     sys     0m0.051s


Geo*_*dré 6

你可以使用一个掩码来设置标志,标志是否已经在数字中看到了数字.像这样:

int mask = 0x0, j;

for(j= 1; j<=9; j++){
    if(mask & 1<<(input%10))
        return 0;
    else
        mask |= 1<<(input%10);
    input /= 10;
}
return !(mask & 1);
Run Code Online (Sandbox Code Playgroud)

完整的计划:

    #include <stdio.h>

int check(int input)
{
    int mask = 0x0, j;

    for(j= 1; j<=9; j++){
        if(mask & 1<<(input%10))
            return 0;
        else
            mask |= 1<<(input%10);
        input /= 10;
    }
    /* At this point all digits are unique
     We're not interested in zero, though */
    return !(mask & 1);
}

int main()
{
    int indx;
    for( indx = 123456789; indx <=987654321; indx++){
        if( check(indx) )
            printf("%d\n",indx);
    }
}
Run Code Online (Sandbox Code Playgroud)

编辑...

或者您可以对数组执行相同的操作:

int check2(int input)
{
    int j, arr[10] = {0,0,0,0,0,0,0,0,0,0};

    for(j=1; j<=9; j++) {
        if( (arr[input%10]++) || (input%10 == 0) )
            return 0;
        input /= 10;
    }
    return 1;
}
Run Code Online (Sandbox Code Playgroud)


Joh*_*ode 6

这是一种方法 - 从一组唯一数字开始,然后随机地将它们随机混乱:

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

int main( void )
{
  char digits[] = "123456789";

  srand( time( NULL ) );

  size_t i = sizeof digits - 1;
  while( i )
  {
    size_t j = rand() % i;
    char tmp = digits[--i];
    digits[i] = digits[j];
    digits[j] = tmp;
  }

  printf( "number is %s\n", digits );
  return 0;
}
Run Code Online (Sandbox Code Playgroud)

一些示例输出:

john@marvin:~/Development/snippets$ ./nine
number is 249316578
john@marvin:~/Development/snippets$ ./nine
number is 928751643
john@marvin:~/Development/snippets$ ./nine
number is 621754893
john@marvin:~/Development/snippets$ ./nine
number is 317529864
Run Code Online (Sandbox Code Playgroud)

请注意,这些是唯一十进制数字的字符串,而不是数字值; 如果你想要相应的整数值,你需要进行类似的转换

long val = strtol( digits, NULL, 10 );
Run Code Online (Sandbox Code Playgroud)


bis*_*CSE 6

检查此代码.

    #include<stdio.h>

    //it can be done by recursion

    void func(int *flag, int *num, int n){  //take 'n' to count the number of digits
        int i;
        if(n==9){                           //if n=9 then print the number
            for(i=0;i<n;i++)
                printf("%d",num[i]);
            printf("\n");
        }
        for(i=1;i<=9;i++){

            //put the digits into the array one by one and send if for next level

            if(flag[i-1]==0){
                num[n]=i;
                flag[i-1]=1;
                func(flag,num,n+1);
                flag[i-1]=0;
            }
        }
    }

    //here is the MAIN function
    main(){

        int i,flag[9],num[9];
        for(i=0;i<9;i++)        //take a flag to avoid repetition of digits in a number
            flag[i]=0;          //initialize the flags with 0

        func(flag,num,0);       //call the function

        return 0;
    }
Run Code Online (Sandbox Code Playgroud)

如果您有任何疑问,请随时提出.


Spe*_*ump 6

我推荐Nominal Animal的答案,但是如果你只是生成这个值,那么你可以将它打印出去,你可以消除一些工作,同时使用相同的方法获得更通用的例程:

char *shuffle( char *digit, int digits, int count, unsigned int seed )
{
    //optional: do some validation on digit string
    //  ASSERT(digits == strlen(digit));
    //optional: validate seed value is reasonable
    //  for(unsigned int badseed=1, x=digits, y=count; y > 0; x--, y--)
    //      badseed *= x;
    //  ASSERT(seed < badseed);

    char *work = digit;
    while(count--)
    {
        int i = seed % digits;
        seed /= digits--;
        unsigned char selectedDigit = work[i];
        work[i] = work[0];
        work[0] = selectedDigit;
        work++;
    }
    work[0] = 0;

    //seed should be zero here, else the seed contained extra information
    return digit;
}
Run Code Online (Sandbox Code Playgroud)

这种方法对传入的数字是破坏性的,实际上不必是数字,或者对于那个问题是唯一的.

如果您希望以排序递增顺序生成的输出值有更多工作:

char *shuffle_ordered( char *digit, int digits, int count, unsigned int seed )
{
    char *work = digit;
    int doneDigits = 0; 
    while(doneDigits < count)
    {
        int i = seed % digits;
        seed /= digits--;
        unsigned char selectedDigit = work[i];
        //move completed digits plus digits preceeding selectedDigit over one place
        memmove(digit+1,digit,doneDigits+i);
        digit[0] = selectedDigit;
        work++;
    }
    work[0] = 0;

    //seed should be zero here, else the seed contained extra information
    return digit;
}
Run Code Online (Sandbox Code Playgroud)

无论哪种情况,都会这样调用:

for(unsigned int seed = 0; seed < 16*15*14; ++seed)
{
    char work[] = "0123456789ABCDEF";
    printf("seed=%d -> %s\n",shuffle_ordered(work,16,3,seed));
}
Run Code Online (Sandbox Code Playgroud)

这应打印出三位十六进制值的有序列表,没有重复的数字:

seed 0 -> 012
seed 1 -> 013
...
seed 3358 -> FEC
seed 3359 -> FED
Run Code Online (Sandbox Code Playgroud)

我不知道你用这些精心设计的数字序列实际上在做什么.如果一些贫困维护工程师将不得不一起走在你身后修复一些bug,我建议有序的版本,因为它是这样一个人来的种子从/到序列值转换更容易.


MiJ*_*iJo 5

这是使用嵌套的有点丑陋但非常快速的解决方案for loops.

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

#define NINE_FACTORIAL  362880

int main(void) {

  //array where numbers would be saved
  uint32_t* unique_numbers = malloc( NINE_FACTORIAL * sizeof(uint32_t) );
  if( !unique_numbers ) {
    printf("Could not allocate memory for the Unique Numbers array.\n");
    exit(1);
  }

  uint32_t n = 0;
  int a,b,c,d,e,f,g,h,i;

  for(a = 1; a < 10; a++) {
    for(b = 1; b < 10; b++) {
    if (b == a) continue;

      for(c = 1; c < 10; c++) {
      if(c==a || c==b) continue;

        for(d = 1; d < 10; d++) {
        if(d==a || d==b || d==c) continue;

          for(e = 1; e < 10; e++) {
          if(e==a || e==b || e==c || e==d) continue;

            for(f = 1; f < 10; f++) {
            if (f==a || f==b || f==c || f==d || f==e) 
                                continue;

              for(g = 1; g < 10; g++) {
              if(g==a || g==b || g==c || g==d || g==e 
                      || g==f) continue;

                for(h = 1; h < 10; h++) {
                if (h==a || h==b || h==c || h==d || 
                 h==e || h==f || h==g) continue;

                  for(i = 1; i < 10; i++) {
                  if (i==a || i==b || i==c || i==d || 
                  i==e || i==f || i==g || i==h) continue;

                  // print the number or
                  // store the number in the array
                  unique_numbers[n++] = a * 100000000
                        + b * 10000000
                        + c * 1000000
                        + d * 100000
                        + e * 10000
                        + f * 1000
                        + g * 100
                        + h * 10
                        + i;

                  }
                }
              }
            }
          }
        }
      }
    }
  }


  // do stuff with unique_numbers array
  // n contains the number of elements

  free(unique_numbers);

  return 0;
}
Run Code Online (Sandbox Code Playgroud)

使用一些宏也是一样的.

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

#define l_(b,n,c,p,f) { int i; for(i = 1; i < 10; i++) {            \
      int j,r=0; for(j=0;j<p;j++){if(i == c[j]){r=1;break;}}        \
      if(r) continue; c[p] = i; f   } }

#define l_8(b,n,c,p) {                                              \
    int i; for(i=1; i< 10; i++) {int j, r=0;                        \
      for(j=0; j<p; j++) {if(i == c[j]) {r = 1; break;}}            \
      if(r)continue; b[n++] = c[0] * 100000000  + c[1] * 10000000   \
            + c[2] * 1000000 + c[3] * 100000 + c[4] * 10000         \
            + c[5] * 1000 + c[6] * 100 + c[7] * 10 + i; } }

#define l_7(b,n,c,p) l_(b,n,c,p, l_8(b,n,c,8))
#define l_6(b,n,c,p) l_(b,n,c,p, l_7(b,n,c,7))
#define l_5(b,n,c,p) l_(b,n,c,p, l_6(b,n,c,6))
#define l_4(b,n,c,p) l_(b,n,c,p, l_5(b,n,c,5))
#define l_3(b,n,c,p) l_(b,n,c,p, l_4(b,n,c,4))
#define l_2(b,n,c,p) l_(b,n,c,p, l_3(b,n,c,3))
#define l_1(b,n,c,p) l_(b,n,c,p, l_2(b,n,c,2))

#define get_unique_numbers(b,n,c) do {int i; for(i=1; i<10; i++) { \
      c[0] = i; l_1(b,n,c,1) } } while(0)


#define NINE_FACTORIAL  362880

int main(void) {

  //array where numbers would be saved
  uint32_t* unique_numbers = malloc( NINE_FACTORIAL * sizeof(uint32_t) );
  if( !unique_numbers ) {
    printf("Could not allocate memory for the Unique Numbers array.\n");
    exit(1);
  }

  int n = 0;
  int current_number[8] = {0};

  get_unique_numbers(unique_numbers, n, current_number);


  // do stuff with unique_numbers array
  // NINE_FACTORIAL is the number of elements


  free(unique_numbers);

  return 0;
}
Run Code Online (Sandbox Code Playgroud)

我确信有更好的方法来编写这些宏,但这是我能想到的.


Gle*_*aum 5

编辑: 进一步分析后,更多的递归展开和仅迭代设置位导致显着改善,在我的测试中大约快了五倍.这是用OUTPUT UNSET测试来比较算法速度而不是控制台输出,起点是uniq_digits9:

int counter=0;
int reps=0;

void show(int x)
{
#ifdef OUTPUT
    printf("%d\n", x);
#else
    counter+=x;
    ++reps;
#endif
}

int bit_val(unsigned int v)
{
  static const int MultiplyDeBruijnBitPosition2[32] =
  {
    0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8,
    31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9
  };
  return MultiplyDeBruijnBitPosition2[(unsigned int)(v * 0x077CB531U) >> 27];
}

void uniq_digits1(int prefix, unsigned int used) {
  show(prefix*10+bit_val(~used));
}

void uniq_digits2(int prefix, unsigned int used) {
  int base=prefix*10;
  unsigned int unused=~used;
  while (unused) {
    unsigned int diff=unused & (unused-1);
    unsigned int bit=unused-diff;
    unused=diff;
    uniq_digits1(base+bit_val(bit), used|bit);
  }
}

void uniq_digits3(int prefix, unsigned int used) {
  int base=prefix*10;
  unsigned int unused=~used;
  while (unused) {
    unsigned int diff=unused & (unused-1);
    unsigned int bit=unused-diff;
    unused=diff;
    uniq_digits2(base+bit_val(bit), used|bit);
  }
}

void uniq_digits4(int prefix, unsigned int used) {
  int base=prefix*10;
  unsigned int unused=~used;
  while (unused) {
    unsigned int diff=unused & (unused-1);
    unsigned int bit=unused-diff;
    unused=diff;
    uniq_digits3(base+bit_val(bit), used|bit);
  }
}

void uniq_digits5(int prefix, unsigned int used) {
  int base=prefix*10;
  unsigned int unused=~used;
  while (unused) {
    unsigned int diff=unused & (unused-1);
    unsigned int bit=unused-diff;
    unused=diff;
    uniq_digits4(base+bit_val(bit), used|bit);
  }
}

void uniq_digits6(int prefix, unsigned int used) {
  int base=prefix*10;
  unsigned int unused=~used;
  while (unused) {
    unsigned int diff=unused & (unused-1);
    unsigned int bit=unused-diff;
    unused=diff;
    uniq_digits5(base+bit_val(bit), used|bit);
  }
}

void uniq_digits7(int prefix, unsigned int used) {
  int base=prefix*10;
  unsigned int unused=~used;
  while (unused) {
    unsigned int diff=unused & (unused-1);
    unsigned int bit=unused-diff;
    unused=diff;
    uniq_digits6(base+bit_val(bit), used|bit);
  }
}

void uniq_digits8(int prefix, unsigned int used) {
  int base=prefix*10;
  unsigned int unused=~used;
  while (unused) {
    unsigned int diff=unused & (unused-1);
    unsigned int bit=unused-diff;
    unused=diff;
    uniq_digits7(base+bit_val(bit), used|bit);
  }
}

void uniq_digits9() {
  unsigned int used=~((1<<10)-1); // set all bits except 0-9
#ifndef INCLUDE_ZEROS
  used |= 1;
#endif
  for (int i = 1; i < 10; i++) {
    unsigned int bit=1<<i;
    uniq_digits8(i,used|bit);
  }
}
Run Code Online (Sandbox Code Playgroud)

简要说明:

有9个数字,第一个不能从零开始,所以第一个数字可以是1到9,其余数字可以是0到9

如果我们取一个数字,X并乘以10,它会移动一个地方.因此,5变为50.添加一个数字,比如说3表示53,然后乘以10得到520,然后加2,依此类推所有9位数.

现在需要一些存储来跟踪使用的数字,因此不会重复这些数字.可以使用10个真/假变量:used_0_p,, used_1_P....但是,这是低效的,所以它们可以放在一个数组中:used_p[10].但是,每次在下一个地方打电话之前都需要复制它,这样它就可以将它重置为下一个数字,否则一旦所有位置都被填充,第一次数组将全部为真,并且不能计算其他组合.

但是,有一种更好的方法.使用a的位int作为数组. X & 1用于第一,X & 2,X & 4,X & 8,等.该序列可以表示为(1<<X)或采取所述第一比特和超过X倍移位它.

&用于测试位,|用于设置它们.在每个循环中,我们测试该位是否被使用(1<<i)&used,如果是,则跳过.在下一个地方,我们移动每个数字的数字prefix*10+i并将该数字设置为使用used|(1<<i)

EDIT中循环的说明

循环计算Y & (Y-1)哪个将最低设置位置零.通过取原始值并减去结果,差值是最低位.这将循环次数与位数相同:3,265,920次而不是900,000,000次.从使用切换到未使用只是~操作员,并且因为设置比取消设置更有效,所以翻转是有意义的

从2的幂到log2取自:https://graphics.stanford.edu/~seander/bithacks.html#IntegerLog.该站点还详细介绍了循环机制:https://graphics.stanford.edu/~seander/bithacks.html#DetermineIfPowerOf2

将原稿移到底部:

这对于评论来说太长了,但是通过从函数中删除零处理可以使这个答案更快一些:(请参阅编辑以获得最快的答案)

void uniq_digits(int places, int prefix, int used) {
  if (!places) {
    printf("%d\n", prefix);
    return;
  }
  --places;
  int base=prefix*10;
  for (int i = 0; i < 10; i++)
  {
    if ((1<<i)&used) continue;
    uniq_digits(places, base+i, used|(1<<i));
  }
}

int main(int argc, char**argv) {
  const int num_digits=9;

  // unroll top level to avoid if for every iteration
  for (int i = 1; i < 10; i++)
  {
    uniq_digits(num_digits-1, i, 1 << i);
  }

  return 0;
}
Run Code Online (Sandbox Code Playgroud)


Pet*_*ter 5

一种简单的方法是创建一个包含九个不同值的数组,将其混洗,然后打印混洗数组.根据需要重复多次.例如,使用标准rand()函数作为改组的基础......

#include <stdlib.h>     /*  for srand() and rand */
#include <time.h>       /*  for time() */
#include <stdio.h>      

#define SIZE 10     /*   size of working array.  There are 10 numeric digits, so ....   */
#define LENGTH 9    /*  number of digits we want to output.  Must not exceed SIZE */
#define NUMBER 12   /*  number of LENGTH digit values we want to output */

void shuffle(char *buffer, int size)
{
     int i;
     char temp;
     for (i=size-1; i>0; --i)
     {
          /*  not best way to get a random value of j in [0, size-1] but
               sufficient for illustrative purposes
          */
          int j = rand()%size;
          /* swap buffer[i] and buffer[j] */
          temp = buffer[i];    
          buffer[i] = buffer[j];
          buffer[j] = temp;
     }
}

void printout(char *buffer, int length)
{
      /*  this assumes SIZE <= 10 and length <= SIZE */

      int i;
      for (i = 0; i < length; ++i)
          printf("%d", (int)buffer[i]);
      printf("\n");
}

int main()
{
     char buffer[SIZE];
     int i;
     srand((unsigned)time(NULL));   /*  seed for rand(), once and only once */

     for (i = 0; i < SIZE; ++i)  buffer[i] = (char)i;  /*  initialise buffer */

     for (i = 0; i < NUMBER; ++i)
     {
         /*  keep shuffling until first value in buffer is non-zero */

         do shuffle(buffer, SIZE); while (buffer[0] == 0);
         printout(buffer, LENGTH);
     }
     return 0;
}
Run Code Online (Sandbox Code Playgroud)

这会打印多行stdout,每行有9个唯一的数字.请注意,这不会阻止重复.