【问题标题】:Creating every possible value of a fixed size array创建固定大小数组的每个可能值
【发布时间】:2010-01-05 00:03:36
【问题描述】:

我正在尝试制作一些非常基本的东西,它将循环遍历数组的所有可能排列。

确实这是在汇编中完成的,但我会用 C 来解释。

基本上,假设我们有一个数组uint8_t *data=malloc(10);

我想创建一个算法来打印数组data 中所有可能的字节组合。

是的,我知道它会很慢(并且有很多值),而且我并不是要真正复杂的优化版本。我只是在寻找可以在我的计算机上运行的东西一种蛮力类型的东西来找到符合某些条件的某些值..

(注意,我说排列是​​因为 [0,1,2] 不应该和 [2,1,0] 一样计算)

编辑: 另外,尽量不要使用太多的 libc 函数,因为我会将其转换为只有 512 字节的独立引导加载程序。

我知道我知道如何做到这一点,但对于我的一生,我就是无法让算法在我的脑海中运行!

【问题讨论】:

  • 您的示例有 1208925819614629174706176 (~1e24) 种可能的组合,其中有 10 个 uint8_t 值。你真的需要这个吗?
  • 是吗?我知道如何计算它以及它有多大。也许我的电脑可以在几个月内完成它。
  • 没有。即使“你的电脑”被理解为“当今世界上最快的电脑(捷豹)”,你也无法在几个月内完成它。 Jaguar 有四分之一的一百万个内核,每个内核的运行频率为 2.6 Ghz。即使您可以在每个内核上的每个周期都打印一个位模式,这显然是荒谬的,但要花 58 年才能打通整个数据空间。由于您的计算机充其量只有 Jaguar 的处理能力的十分之一,因此您将看到 50 万年。
  • @earlz,正如斯蒂芬所说,计算出许多排列不会很快发生。我们需要现实的限制:-)
  • 任何需要 50 万年计算的东西都有相同的答案:42

标签: c algorithm permutation brute-force


【解决方案1】:

好吧,整个事情都是徒劳的(请参阅我对问题的评论),但无论如何都可以(x86_64 AT&T 风格的程序集,假设 AMD 系统 V 调用约定)。我只是在这里写这个没有测试,所以它完全有可能有错误。尽管如此,代码的基本操作应该是完全清楚的。

我没有在内存中的 80 位缓冲区上操作,而是简单地运行了跨两个 64 位寄存器拆分的 80 位字段的所有可能性。检查条件的例程可以将它们存储到内存中,如果您真的愿意,可以以uint8_t 访问该内存。

    push r12
    push r13
    xor  r12, r12 // zero out low 64 bits of our "buffer" in register
    xor  r13, r13 // zero out high 16 bits of our "buffer"

loop:
    // Copy the current array value into rsi:rdi and call whatever routine you're
    // using to check for magic conditions.  This routine's copy (in r13:r12)
    // should be unaffected if you're obeying the System V calling conventions.
    mov  r12, rdi
    mov  r13, rsi
    call _doSomethingWithValue

    // Increment r13:r12 to get the next value.  We only need to worry about r13
    // if the increment of r12 wraps around to zero.
    inc  r12
    jnz  loop
    inc  r13

    // Check for the termination condition, though you'll never hit it =)
    cmp  $0x10000, r13
    jne  loop

    // We don't actually need to clean up; the apocalypse will come and there
    // won't be electricity to run the computer before it reaches this point of
    // the program.  Nonetheless, let's be exhaustively correct.
    pop  r13 
    pop  r12

【讨论】:

  • 公平地说,计算机很可能会在世界末日剥夺电力之前很久就停止运行 =)
  • 令人惊讶的是,有些人会为声望点数所追求的深度 :-) :-)
  • 不,如果我真的是代表嫖娼,我会在 ARM 程序集中编写它并重新标记问题“iPhone”。 =)
  • 我想在Iphone上生成图片?你会怎么做?
【解决方案2】:

您的问题有一个奇怪的术语混淆。根据您的描述,您似乎想要生成所有可能的 10 元组的无符号 8 位值。这些不是“排列”,所有这些都与生成排列无关。

生成所有可能的 10 元组 uint8_t 值的代码很容易想出。例如下面的简单代码就可以了

#define N 10u

uint8_t data[N] = { 0 };
unsigned i;

do {

  /* Process the current 10-typle in `data` array */
  /* in any way you want do */

  /* Generate next tuple */
  for (i = 0; i < N && ++data[i] == 0; ++i);

} while (i < N);

这只不过是一个 80 位 little-endian 数字的循环增量。

当然,正如其他人已经指出的那样,从任何实际角度来看,这将花费大量时间使整个事情变得毫无用处。

【讨论】:

  • 哇。好吧,它是“无用的”(例如,无法很快计算东西)但它完全符合我的要求并且可以工作:)
  • @斯蒂芬:哦。是的,是 80,而不是 800 :)
  • @Alok:是的,它应该重新循环。它确实如此。那么,您究竟在哪里看到了问题?
  • @AndreyT:抱歉,我没想到。
  • 它确实有一些小错误,即第一个数字不循环,但没关系,我已经解决了......不过将它转换为汇编让我有些适应......
【解决方案3】:

我建议你阅读,

唐纳德·高德纳。计算机编程艺术,第 4 卷,第 2 卷:生成所有元组和排列。

【讨论】:

    【解决方案4】:

    有一个经典的递归方法来解决这个问题,类似于以下内容:

    #include <stdio.h>
    
    
    void print(const uint8_t *v, const int size)
    {
      if (v != 0) {
        for (int i = 0; i < size; i++) {
          printf("%4d", v[i] );
        }
        printf("\n");
      }
    } // print
    
    
    void visit(uint8_t *Value, int N, int k)
    {
      static level = -1;
      level = level+1; Value[k] = level;
    
      if (level == N)
        print(Value, N);
      else
        for (int i = 0; i < N; i++)
          if (Value[i] == 0)
            visit(Value, N, i);
    
      level = level-1; Value[k] = 0;
    }
    
    
    main()
    {
      const int N = 4;
      uint8_t Value[N];
      for (int i = 0; i < N; i++) {
        Value[i] = 0;
      }
      visit(Value, N, 0);
    }
    

    示例取自link,其中还有其他方法。它背后的理论很简单。如果你需要我可以进一步解释算法,但它是不言自明的。

    【讨论】:

    • 我在这里处理一个相当大的N 数字.. 所以我认为即使使用 64k 大堆栈也不能使用递归(并且该链接中的所有算法都是递归的)
    • 展开递归并使用您自己的堆栈! :P
    • 如果 N 相当大,那么无论如何你都是在没有桨的情况下上岸的。
    【解决方案5】:

    查看this algorithm 以生成 M 项中的 N 项的组合。对于 N 的组合选择 N,只需使用 inittwiddle(N, N, p);

    int twiddle(x, y, z, p)
    int *x, *y, *z, *p;
      {
      register int i, j, k;
      j = 1;
      while(p[j] <= 0)
        j++;
      if(p[j-1] == 0)
        {
        for(i = j-1; i != 1; i--)
          p[i] = -1;
        p[j] = 0;
        *x = *z = 0;
        p[1] = 1;
        *y = j-1;
        }
      else
        {
        if(j > 1)
          p[j-1] = 0;
        do
          j++;
        while(p[j] > 0);
        k = j-1;
        i = j;
        while(p[i] == 0)
          p[i++] = -1;
        if(p[i] == -1)
          {
          p[i] = p[k];
          *z = p[k]-1;
          *x = i-1;
          *y = k-1;
          p[k] = -1;
          }
        else
          {
          if(i == p[0])
        return(1);
          else
        {
        p[j] = p[i];
        *z = p[i]-1;
        p[i] = 0;
        *x = j-1;
        *y = i-1;
        }
          }
        }
      return(0);
      }
    
    void inittwiddle(m, n, p)
    int m, n, *p;
      {
      int i;
      p[0] = n+1;
      for(i = 1; i != n-m+1; i++)
        p[i] = 0;
      while(i != n+1)
        {
        p[i] = i+m-n;
        i++;
        }
      p[n+1] = -2;
      if(m == 0)
        p[1] = 1;
      }
    
    /************************
      Here is a sample use of twiddle() and inittwiddle():
    #define N 5
    #define M 2
    #include <stdio.h>
    void main()
      {
      int i, x, y, z, p[N+2], b[N];
      inittwiddle(M, N, p);
      for(i = 0; i != N-M; i++)
        {
        b[i] = 0;
        putchar('0');
        }
      while(i != N)
        {
        b[i++] = 1;
        putchar('1');
        }
      putchar('\n');
      while(!twiddle(&x, &y, &z, p))
        {
        b[x] = 1;
        b[y] = 0;
        for(i = 0; i != N; i++)
          putchar(b[i]? '1': '0');
        putchar('\n');
        }
      }
    ************************/
    

    本帖的答案也可能对你有所帮助Algorithm to return all combinations of k elements from n

    【讨论】:

      【解决方案6】:

      如果您使用 C++,

      #include <algorithm>
      #include <iterator>
      #include <iostream>
      #include <numeric>
      
      int main() {
          int N;
          std::cin >> N;
          std::vector<int> data(N);
          std::fill(data.begin(), data.end(), 1);
          std::partial_sum(data.begin(), data.end(), data.begin());
      
          do {
              std::copy(data.begin(), data.end(),
                      std::ostream_iterator<int>(std::cout, " "));
              std::cout << std::endl;
          } while (std::next_permutation(data.begin(), data.end()));
      
          return 0;
      }
      

      如果你输入3,它会输出

      1 2 3 1 3 2 2 1 3 2 3 1 3 1 2 3 2 1

      请参阅 Next permutation: When C++ gets it right 了解 std::next_permutation 的工作原理。


      将其翻译成纯 C,

      #include <stdio.h>
      #include <stdlib.h>
      
      int main() {
          int i, N, *data;
      
          scanf("%d", &N);
          data = malloc(N);
          for (i = 0; i < N; i++) data[i] = i + 1;
      
          while (1) {
              int j, temp;
      
              for (i = 0; i < N; i++) printf("%d ", data[i]);
              printf("\n");
      
              for (i = N - 1; i > 0 && data[i] < data[i - 1]; i--);
              if (i <= 0) break;
              for (j = N; data[i - 1] >= data[--j];);
              temp = data[i - 1], data[i - 1] = data[j], data[j] = temp;
              for (j = N - 1; i < j; i++, j--)
                  temp = data[i], data[i] = data[j], data[j] = temp;
          }
      
          return 0;
      }
      

      如果问题不是询问现有数组的排列,而是生成所有可能的数组内容,那么这要容易得多。 (还有更多的组合。)

      memset(data, 0, N);
      do {
          for (i = 0; i < N; i++) printf("%d ", data[i]);
          printf("\n");
          for (i = 0; i < N && !++data[i++];);
      } while (i < N);
      

      【讨论】:

        猜你喜欢
        • 2018-05-01
        • 1970-01-01
        • 1970-01-01
        • 2014-11-06
        • 1970-01-01
        • 2015-05-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多