【问题标题】:Generating all n-letter permutations生成所有 n 字母排列
【发布时间】:2013-07-30 14:36:17
【问题描述】:

我正在尝试使用 26 个字母(仅相当于 26*25*24=15,600)来计算所有可能的 3 个字母排列。字母的顺序很重要,我不想重复字母。 (我希望按字典顺序生成排列,但这不是必需的)

到目前为止,我尝试嵌套for 循环,但最终我迭代了所有可能的组合。所以有重复的字母,这是我不想要的,如果我想要超过 3 个字母,for 循环可能会变得难以管理。

我可以翻阅这些字母,直到我得到一个没有使用过的字母,但它不是按字典顺序排列的,而且它比使用 next_permutation很多(我不能使用这个std 方法,因为我要计算 26 个字母的所有子集)。

有没有更有效的方法来做到这一点? 从效率低下的角度来看,next_permutation 会立即迭代前 6 位数字。但是,使用这种方法获得所有三个字母的排列需要几秒钟的时间,而且next_permutation 在我必须计算 2^n 个子集时仍然很快变得低效。

这是嵌套的for 循环:

char key[] = {'a','b','c','d','e','f','g','h','i','j','k',
'l','m','n','o','p','r','s','t','u','v','w','x','y','z'};
bool used[25];
ZeroMemory( used, sizeof(bool)*25 );

for( int i = 0; i < 25; i++ )
{
     while( used[i] == true )
          i++;
     if( i >= 25 )
          break;
     used[i] = true;
     for( int j = 0; j < 25; j++ )
     {
          while( used[j] == true )
               j++;
          if( j >= 25 )
               break;
          used[j] = true;
          for( int k = 0; k < 25; k++ )
          {
               while( used[k] == true )
                    k++;
               if( k >= 25 )
                    break;
               used[k] = true;

               cout << key[i] << key[j] << key[k] << endl;

               used[k] = false;
          }
          used[j] = false;
     }
     used[i] = false;
}

【问题讨论】:

    标签: c++ performance permutation


    【解决方案1】:
    1. 创建一个代表组合开始的根,因此它没有任何价值。

    2. 计算所有可能的孩子(26 个字母,26 个孩子...)

    3. 为每个根孩子计算可能的孩子(所以:剩余的字母)

    4. 使用递归有限深度搜索来查找您的组合。

    【讨论】:

      【解决方案2】:

      如果我只是想要一个“简单”的解决方案,我会尝试这个解决方案。我不确定这需要多少资源,所以我建议你从一小部分字母开始尝试。

      a = {a...z}
      b = {a...z}
      c = {a...z}
      
      for each(a)
      {
        for each(b)
        {
          for each(c)
          {
           echo a + b + c;
          }
        }
      }
      

      【讨论】:

      • 嗯,我不确定是否有更好的答案。只是想说它可能(我应该说'mabey')不是最好的答案。
      • 感谢您的反馈,英语不是我的母语,所以我愿意接受建议!
      • 您实际上并不需要三个数组,您可以将其缩减为 1 并执行基于索引的循环而不是 for each
      • 我不能有重复的字母,但这可以通过让每个 for 循环在嵌套的 for 循环之前包含一个 while 循环来工作,该循环将继续递增它,直到找到一个未使用的字母。我会看看它是否有效。
      • @MooingDuck 我喜欢使用 continue 而不是 while 循环,这解决了覆盖堆栈的问题,我只是通过让它检查它是否太高然后打破循环来修补这个问题。这比我的要快得多,谢谢。 :)
      【解决方案3】:

      对于一个特定且小的 n,像您这样的手动循环是最简单的方法。但是,您的代码可以高度简化:

      for(char a='a'; a<='z'; ++a) {
          for(char b='a'; b<='z'; ++b) {
              if (b==a) continue;
              for(char c='a'; c<='z'; ++c) {
                  if (c==a) continue;
                  if (c==b) continue;
                  std::cout << a << b << c << '\n';
              }
          }
      }
      

      对于变量 N,显然我们需要不同的策略。而且,事实证明,它需要一个令人难以置信不同的策略。这是基于 DaMachk 的回答,即使用递归生成后续字母

      template<class func_type> 
      void generate(std::string& word, int length, const func_type& func) {
          for(char i='a'; i<='z'; ++i) {
              bool used = false;
              for(char c : word) {
                  if (c==i) {
                      used = true;
                      break;
                  }
              }
              if (used) continue;
              word.push_back(i);
              if (length==1) func(word);
              else generate(word, length-1, func);
              word.pop_back();
          }
      }
      template<class func_type> 
      void generate(int length, const func_type& func) {
          std::string word;
          generate(word, length, func);
      }
      

      You can see it here

      我还制作了一个展开的版本,结果证明它非常复杂,但速度明显更快。我有两个辅助函数:我有一个“查找下一个字母”(称为next_unused)的函数,它将索引处的字母增加到下一个未使用的字母,如果不能,则返回false。第三个函数reset_range“重置”从给定索引到字符串末尾的字母范围,使其可以使用第一个未使用的字母。首先我们使用reset_range 来查找第一个字符串。为了找到后续的字符串,我们在最后一个字母上调用next_unused,如果失败,则调用倒数第二个字母,如果失败则调用倒数第三个字母,依此类推。当我们找到一个字母时,我们可以适当增加,然后将其右侧的所有字母“重置”为最小的未使用值。如果我们一直到第一个字母并且它无法增加,那么我们已经到了结尾,我们停止了。代码很吓人,但这是我能想到的最好的了。

      bool next_unused(char& dest, char begin, bool* used) {
          used[dest] = false;
          dest = 0;
          if (begin > 'Z') return false;
          while(used[begin]) {
              if (++begin > 'Z')
                  return false;
          }
          dest = begin;
          used[begin] = true;
          return true;
      }
      void reset_range(std::string& word, int begin, bool* used) {
          int count = word.size()-begin;
          for(int i=0; i<count; ++i)
              assert(next_unused(word[i+begin], 'A'+i, used));
      }
      template<class func_type>
      void doit(int n, func_type func) {
          bool used['Z'+1] = {};
          std::string word(n, '\0');
          reset_range(word, 0, used);
          for(;;) {
              func(word);
              //find next word
              int index = word.size()-1;
              while(next_unused(word[index], word[index]+1, used) == false) {
                  if (--index < 0)
                      return; //no more permutations
              }
              reset_range(word, index+1, used);
         }
      }
      

      Here it is at work.
      And here it is running in a quarter of the time as the simple one

      【讨论】:

        【解决方案4】:

        我在 powershell 中做了类似的事情。生成 9 个符号的所有可能组合。经过一番反复试验,这就是我想出的。

        $S1=New-Object System.Collections.ArrayList
        $S1.Add("a")
        $S1.Add("b")
        $S1.Add("c")
        $S1.Add("d")
        $S1.Add("e")
        $S1.Add("f")
        $S1.Add("g")
        $S1.Add("h")
        $S1.Add("i")
        $S1 | % {$a = $_
            $S2 = $S1.Clone()
            $S2.Remove($_)
            $S2 | % {$b = $_
                $S3 = $S2.Clone()
                $S3.Remove($_)
                $S3 | % {$c = $_
                    $S4 = $S2.Clone()
                    $S4.Remove($_)
                    $S4 | % {$d = $_
                        $S5 = $S4.Clone()
                        $S5.Remove($_)
                        $S5 | % {$e = $_
                            $S6 = $S5.Clone()
                            $S6.Remove($_)
                            $S6 | % {$f = $_
                                $S7 = $S6.Clone()
                                $S7.Remove($_)
                                $S7 | % {$g = $_
                                    $S8 = $S7.Clone()
                                    $S8.Remove($_)
                                    $S8 | % {$h = $_
                                        $S9 = $S8.Clone()
                                        $S9.Remove($_)
                                        $S9 | % {$i = $_
                                            ($a+$b+$c+$d+$e+$f+$g+$h+$i)
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        

        【讨论】:

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