【问题标题】:Trying to generate 9 digit numbers with each unique digits尝试使用每个唯一数字生成 9 位数字
【发布时间】:2015-08-05 08:04:00
【问题描述】:

我正在尝试获取 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);
        }
    }
}

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

【问题讨论】:

  • 使用数组.....
  • 当你有很多变量命名为d1d2等等时,这应该是把它们变成数组的一个重要提示。
  • 我什至不认为您当前的方法是正确的,因为第 2 位和第 3 位可以相同,第 2 位和第 4 位、第 2 位和第 5 位也可以相同。第 8 位和第 9 位。跨度>
  • 如何将其变成字符串“123456789”的排列,然后将字符串转换为数字。
  • 你是包括0还是只是1-9

标签: c random unique


【解决方案1】:

这是涉及combinatorics 的一个非常典型的问题示例。

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

因此,您可以轻松编写一个函数,接收 seed, 0 ≤ seed

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;
}

digit 数组被初始化为一组九个迄今未使用的数字。 i 表示该数组的索引,因此 digit[i] 是实际使用的数字。由于使用了数字,所以替换为数组中的最后一个数字,数组n的大小减一。

一些示例结果:

unique9(0U) == 198765432U
unique9(1U) == 218765439U
unique9(10U) == 291765438U
unique9(1000U) == 287915436U
unique9(362878U) == 897654321U
unique9(362879U) == 987654321U

结果的奇数顺序是因为digit 数组中的数字交换了位置。

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

#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;
}

如果您按升序指定digits0 &lt;= index &lt; length!,则buffer 将是具有indexth 最小值的排列。例如,如果digits="1234"length=4,那么index=0 将产生buffer="1234"index=1 将产生buffer="1243",依此类推,直到index=23 将产生buffer="4321"

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


这种方法是通用的。例如,如果您想创建 N 个字符的单词,其中每个字符都是唯一的,并且/或者只使用特定字符,那么相同的方法将产生一个有效的解决方案。

首先,将任务拆分为多个步骤。

上面,digit[] 数组中还有 n 未使用的数字,我们可以使用 seed 选择下一个未使用的数字。

如果seed 除以n

i = seed % n;i 设置为余数(模数)。因此,i 是介于 0 和 n-1 之间的整数,0 ≤ i &lt; n

要删除我们用来决定这一点的seed 的部分,我们进行除法:seed = seed / n;

接下来,我们将数字添加到结果中。因为结果是一个整数,所以我们可以只添加一个新的小数位位置(将结果乘以十),然后使用result = result * 10 + digit[i] 将该数字添加到最低有效位(作为新的最右边的数字)。在 C 中,数字常量末尾的 U 只是告诉编译器该常量是无符号的(整数)。 (其他的是L 对应longUL 对应unsigned long,如果编译器支持它们,LL 对应long longULL 对应unsigned long long。)

如果我们正在构造一个字符串,我们只需将digit[i] 放在 char 数组中的下一个位置,并增加该位置。 (要将其变成字符串,请记住在最后放置一个字符串结尾的 nul 字符 '\0'。)

接下来,由于数字是唯一的,我们必须从 digit[] 数组中删除 digit[i]。为此,我将digit[i] 替换为数组中的最后一位数字digit[n-1],并减少数组中剩余的数字位数n--,实际上是从其中删除最后一位数字。这一切都是通过使用digit[i] = digit[--n]; 完成的,它完全等同于

digit[i] = digit[n - 1];
n = n - 1;

此时,如果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;
}

str 指向至少有len 个字符的字符数组调用函数,seed 是标识组合的数字,它将用最多26 个字符串填充strlen-1 字符(以较少者为准),其中每个小写字母最多出现一次。

如果您觉得该方法不清楚,请询问:我非常想尝试澄清一下。

您会发现,使用低效的算法会损失大量资源(不仅是电力,还包括人类用户的时间),这只是因为编写缓慢、低效的代码更容易,而不是真正有效地解决手头的问题。我们这样浪费金钱和时间。当 正确 解决方案像这种情况一样简单时——就像我说的那样,这会扩展到大量的组合问题——我宁愿看到程序员花 15 分钟学习它,并在有用的时候应用它,而不是看到浪费的传播和扩展。


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

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

#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;
}

置换函数是递归的,但它的最大递归深度是字符串长度。该函数的总调用次数为a(N),其中N是字符串的长度,a(n)=na(n-1)+1(序列A002627 ),在这种特殊情况下调用 623530。一般来说,a(n)≤(1-e)n!,即a(n)n!,所以调用次数为O(N!) ,关于置换的项目数的阶乘。与调用相比,循环体的迭代次数减少了 623529 次。

逻辑相当简单,使用与第一个代码 sn-p 中相同的数组方法,只是这次数组的“修剪掉”部分实际上用于存储置换后的字符串。换句话说,我们将每个剩余的字符与下一个要修剪的字符交换(或附加到最终字符串),进行递归调用,并恢复这两个字符。因为每次递归调用后都会撤消每次修改,所以缓冲区中的字符串在调用后与之前相同。就好像它从一开始就没有被修改过一样。

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

【讨论】:

  • 哇,真不错。你甚至可以改进它以获取任何种子:对输入进行哈希处理,然后取模 362880。
  • @this:实际上,它已经接受任何种子,因为它实际上只使用(seed % 362880)。也就是说,unique9(seed) == unique9(seed % 362880) 和每个可能的无符号输入 seed 都会产生有效结果。 (只有 362880 个唯一的,仅此而已。)
  • 对,我没注意到。
  • 似乎至少有一位读者发现了这个答案“没用”。我假设这是因为读者没有在答案中找到即时满足感,而是更喜欢更简单但效率更低的方法。这让我感到担心和烦恼。我已经扩展了答案以逐步显示整个逻辑,并添加了第二个示例,该示例扩展到使用 ASCII 字母表中唯一字母的单词。
  • 单循环,高效且确定性。非常好。
【解决方案2】:

给定一个数字数组,可以使用一个相当简单的函数生成这些数字的下一个排列(我们称该函数为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
}

将生成此输出

123
132
213
231
312
321

如果您将 array 更改为

int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };

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


nextPermutation 函数分为三个步骤

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

让我用一个例子来说明。假设数组有这个顺序的数字

1 9 5 4 8 7 6 3 2

第一步将找到4。由于8 7 6 3 2 是降序排列,所以4 是第一个数字(从数组末尾开始),后跟一个更大的数字。

第二步将找到6,因为6 是第一个大于4 的数字(从数组末尾开始)。交换 46 后,数组如下所示

1 9 5 6 8 7 4 3 2

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

最后一步是交换6 之后的数字,使它们都按升序排列。因为我们知道数字是按降序排列的,所以我们需要做的就是将82 交换,并将73 交换。结果数组是

1 9 5 6 2 3 4 7 8

因此,给定数字的任何排列,该函数只需交换几个数字即可找到下一个排列。唯一的例外是所有数字都以相反顺序排列的最后一个排列,即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;
}

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

int array[] = { 2, 3, 7, 9 };

然后nextPermutation 函数将找到 2、3、7 和 9 的所有排列。


为了完整起见,这里是 arrayToInt 函数中使用的 main 函数。此功能仅用于演示目的。它假定数组只包含一位数字,并且不费心检查溢出。如果int 至少为 32 位,它将适用于 9 位数字。

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

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

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

用于测试的 CPU 是 2.5Ghz Intel i5 处理器。该算法每秒生成大约 2 亿个排列,生成 9 个数字的所有排列只需要不到 2 毫秒的时间。

同样有趣的是,平均而言,该算法每个排列只需要大约 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

排列的数量与预期的一样,并且输出似乎是正确的,因此如果数字不唯一,该算法似乎也可以工作。

【讨论】:

  • 干得好。您的代码/解决方案遥遥领先于竞争对手。
  • 使用简单的数组来获得最快和最易读的答案,现在将其与其他解决方案进行比较。我想知道,你自己弄清楚程序了吗?
  • @this 是的,我想要一种无需递归即可生成排列的方法。所以我首先打印短数组的排列(使用递归),然后分析输出以找到模式。事实证明,该模式非常简单,我能够将其转换为代码。
  • @GlennTeitelbaum 您没有非递归解决方案。我很想测试它。请写一个带有公共原型的版本:( int array[], int length )
  • @GlennTeitelbaum:该序列允许对任何字符串进行恒定时间排列,而无需递归。您只需要按该序列的顺序执行成对交换。 (因此,每个排列恰好有一个交换,因此是恒定的时间。生成所有排列当然是 O(N!),正好进行 N!-1 个交换。)我不知道这个序列是否有名字。
【解决方案3】:

递归在这里工作得很好。

#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;
}

【讨论】:

【解决方案4】:

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

#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;
}

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

【讨论】:

    【解决方案5】:

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

    我们希望每个可能性都只生成一次,而不会浪费任何精力。事实证明,只要每发射一个数字付出一定的努力,这是可能的。

    如果没有代码,您将如何做到这一点?拿 10 张卡片,在上面写上 0 到 9 的数字。在桌面上画一行 9 个正方形。选择一张卡片。把它放在第一个格子里,另一个放在第二个格子里,等等。当你选择了 9 时,你就有了你的第一个数字。现在取出最后一张卡片并用每个可能的替代品替换它。 (在这种情况下只有 1。)每次填充所有方格时,您都会得到另一个数字。当你完成了最后一个方格的所有备选方案后,再对最后 2 个进行。对最后 3 个重复,以此类推,直到你考虑了所有方框的所有备选方案。

    为此编写一个简洁的程序就是选择简单的数据结构。对 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;
    }
    

    你没有提到是否可以在第一个位置放一个零。假设不是。由于我们很好地理解了算法,因此很容易避免在第一个位置选择零。如果 pos 为 0 和 0,则通过观察 C 中的 !pos 的值为 1 来跳过这种可能性。如果您不喜欢这个稍微晦涩的习语,请尝试将 (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;
    }
    

    【讨论】:

      【解决方案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);
      

      完整的程序:

          #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);
          }
      }
      

      已编辑...

      或者你可以对数组做同样的事情:

      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;
      }
      

      【讨论】:

      • 我真的不知道如何处理比特,因为我从未学过它们。还有其他方法可以解决这个问题吗?
      • @RichardFlores 对不起,这是最快的并且需要更少的代码。这是关于与、或和位操作的很好的一课。当您确实了解这些领域时,我建议您回到这个示例。理论上,您可以用整数数组替换掩码,结果相同。
      • @RichardFlores 我已经按照您的要求用数组示例更新了我的答案。 :)
      • @RichardFlores 原来数组版本稍微快一点... :)
      • 谢谢。这背后的逻辑究竟是什么?
      【解决方案7】:

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

      #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;
      }
      

      一些示例输出:

      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
      

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

      long val = strtol( digits, NULL, 10 );
      

      【讨论】:

        【解决方案8】:

        而不是 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);
        }
        

        完整的程序(丢弃不必要的#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;
        }
        

        OP 在我上班时澄清了他的问题,而我并没有关注所要求的缺少零。 (回复已更新)。这会产生预期的 362880 个组合。

        但是 - 有评论说一个答案是最快的,这会提示后续操作。有(算上这个)三个可比较的答案。快速检查:

        • @Paul Hankin 的答案(计算零并给出 3265920 种组合):
        实际0m0.951s 用户 0m0.894s 系统 0m0.056s
        • 这个:
        真正的 0m49.108s 用户 0m49.041s 系统 0m0.031s 实际 1m27.597s 用户 1m27.476s 系统 0m0.051s

        【讨论】:

        • bit的赋值将num的值映射为1、2、4、8等,根据digit的值(0,1,2,3,等等)。有人可能会举一个更简洁的例子,但这种方法在使用 C 的系统编程中很常见。
        • 请注意我的第二个解决方案的计时(在 EDIT 之后,完全展开的一个)只是好奇您的计时结果是否与我的相符,而且进行独立验证是有意义的:stackoverflow.com/a/31928246/2963099
        • @ThomasDickey 不错!你测试了我的哪个答案?掩码一,check1() 还是数组一,check2()?你能测试一下吗?我有一种感觉,哪个会更快,但很高兴确认。
        • @ThomasDickey :您能否将我的解决方案添加到名人堂?我认为它相当快。
        【解决方案9】:

        检查此代码。

            #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;
            }
        

        如果您有任何问题,请随时提出。

        【讨论】:

          【解决方案10】:

          我推荐 Nominal Animal 的 answer,但如果您只是生成此值以便将其打印出来,您可以省去一些工作,同时使用更通用的例程同样的方法:

          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;
          }
          

          此方法对传入的数字具有破坏性,实际上不必是数字或唯一的。

          如果您希望按升序排序生成的输出值,则需要做更多的工作:

          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;
          }
          

          在任何一种情况下,它都是这样调用的:

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

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

          seed 0 -> 012
          seed 1 -> 013
          ...
          seed 3358 -> FEC
          seed 3359 -> FED
          

          我不知道你实际上在用这些精心制作的数字序列在做什么。如果一些糟糕的维护工程师必须在你身后修复一些错误,我推荐订购版本,因为人类将种子从/到序列值转换更容易方式

          【讨论】:

            【解决方案11】:

            这是使用嵌套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;
            }
            

            使用一些宏也一样。

            #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;
            }
            

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

            【讨论】:

              【解决方案12】:

              一种简单的方法是创建一个具有九个不同值的数组,将其打乱,然后打印打乱的数组。根据需要重复多次。例如,使用标准的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;
              }
              

              这会将多行打印到stdout,每行都有 9 个唯一数字。请注意,这并不能防止重复。

              【讨论】:

                【解决方案13】:

                编辑:经过进一步分析,更多的递归展开和仅迭代设置位导致显着改进,在我的测试中大约快五倍。这是用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);
                  }
                }
                

                简要说明

                有9位数字,第一位不能以0开头,所以第一位可以是1到9,其余可以是0到9

                如果我们取一个数字 X 并乘以 10,它会移动一位。所以,5 变成 50。加一个数字,比如 3 得到 53,然后乘以 10 得到 520,然后加 2,以此类推所有 9 个数字。

                现在需要一些存储来跟踪使用了哪些数字,以免重复。可以使用 10 个真/假变量:used_0_pused_1_P、...。但是,这是低效的,因此可以将它们放在一个数组中:used_p[10]。但是每次在调用下一个位置之前都需要复制它,以便它可以将其重置为下一个数字,否则一旦所有位置都被第一次填充,数组将全部为真,并且无法计算其他组合。

                但是,还有更好的方法。使用int 的位作为数组。 X &amp; 1 为第一个,X &amp; 2X &amp; 4X &amp; 8 等。此序列可以表示为(1&lt;&lt;X) 或取第一位并将其移位 X 次。

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

                EDIT中循环的解释

                循环计算Y &amp; (Y-1),它将最低设置位归零。通过取原始值并减去结果,差值是最低位。这将仅循环与位数一样多的次数:3,265,920 次而不是 900,000,000 次。从 used 切换到 Used 只是 ~ 运算符,由于设置比取消设置更有效,所以翻转是有意义的

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

                将原件移至底部:

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

                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;
                }
                

                【讨论】:

                • 问题下的 cmets 表明零不是结果中的有效数字。只需要 1-9 个。
                • 添加了 ifdef 以包含 0s
                【解决方案14】:

                聚会有点晚了,但很快(这里是 30 毫秒)...

                #include <stdio.h>
                #define COUNT 9
                
                   /* this buffer is global. intentionally.
                   ** It occupies (part of) one cache slot,
                   ** and any reference to it is a constant
                   */
                char ten[COUNT+1] ;
                
                unsigned rec(unsigned pos, unsigned mask);
                int main(void)
                {
                unsigned res;
                
                ten[COUNT] = 0;
                
                res = rec(0, (1u << COUNT)-1);
                fprintf(stderr, "Res=%u\n", res);
                
                return 0;
                }
                
                /* recursive function: consume the mask of available numbers
                ** until none is left.
                ** return value is the number of generated permutations.
                */
                unsigned rec(unsigned pos, unsigned mask)
                {
                unsigned bit, res = 0;
                
                if (!mask) { puts(ten); return 1; }
                
                for (bit=0; bit < COUNT; bit++) {
                        if (! (mask & (1u <<bit)) ) continue;
                        ten[pos] = '1' + bit;
                        res += rec(pos+1, mask & ~(1u <<bit));
                        }
                return res;
                }
                

                【讨论】:

                  【解决方案15】:

                  广泛使用位的迭代版本

                  注意array可以更改为任何类型,并以任何顺序设置 这将按给定顺序“计算”数字

                  更多解释请查看我的第一个答案(不太灵活但速度更快)https://stackoverflow.com/a/31928246/2963099

                  为了使其迭代,需要数组来保持每个级别的状态

                  这也对优化器无法找出的地方进行了相当多的优化

                  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_digits(const int array[], const int length) {
                    unsigned int unused[length-1];                    // unused prior
                    unsigned int combos[length-1];                    // digits untried
                    int digit[length];                                // printable digit
                    int mult[length];                                 // faster calcs
                    mult[length-1]=1;                                 // start at 1
                    for (int i = length-2; i >= 0; --i)
                       mult[i]=mult[i+1]*10;                          // store multiplier
                    unused[0]=combos[0]=((1<<(length))-1);            // set all bits 0-length
                    int depth=0;                                      // start at top
                    digit[0]=0;                                       // start at 0
                    while(1) {
                      if (combos[depth]) {                            // if bits left
                        unsigned int avail=combos[depth];             // save old
                        combos[depth]=avail & (avail-1);              // remove lowest bit
                        unsigned int bit=avail-combos[depth];         // get lowest bit
                        digit[depth+1]=digit[depth]+mult[depth]*array[bit_val(bit)]; // get associated digit
                        unsigned int rest=unused[depth]&(~bit);       // all remaining
                        depth++;                                      // go to next digit
                        if (depth!=length-1) {                        // not at bottom
                          unused[depth]=combos[depth]=rest;           // try remaining
                        } else {
                          show(digit[depth]+array[bit_val(rest)]);    // print it
                          depth--;                                    // stay on same level
                        }
                      } else {
                        depth--;                                      // go back up a level
                        if (depth < 0)
                          break;                                      // all done
                      }
                    }
                  }
                  

                  有些计时只使用 1 到 9 次,重复 1000 次:

                  【讨论】:

                  • @this 这是通用的迭代版本,它的速度是我的另一个版本的两倍,但是你去
                  • 您将苹果与橙子进行比较。您的函数在一次调用中输出每个可能的值,而其他所有解决方案都输出一个值。你怎么没注意到。即使忽略我严重怀疑您的测量方法是否正确。
                  • 写一个像 nextPermutation 函数一样接受数组及其长度的函数,将下一个值写入数组并返回。 然后尝试比较。
                  • @this OP 生成所有,递归生成所有,交换有代码生成所有,nextPermutation 有一个 main 生成所有。如果您怀疑我的方法,请运行它。不需要生成下一个,虽然很聪明。我的展开递归在解决 OP 的要求方面仍然是最快的,在 C++ 中使用模板和std::bitset 我可以走得更快,但这是 C
                  • 根据您的测量,您的函数可能是最快的,但是您会看到,如果您定义了 OUTPUT,则该函数不会在任何地方返回或写入任何值。结果只是,输了。嗯,其他函数似乎每次调用时都会返回一个结果。这一点以及可以修改所有其他函数的事实,以便它在内部计算每个值,而不是在循环中调用会产生额外开销,这使您输入无效。再见。
                  【解决方案16】:

                  制作一个包含 10 个值 0-9 的元素的列表。通过 rand() /w 当前列表长度拉出随机元素,直到获得所需的位数。

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多