【问题标题】:Generate all combinations of arbitrary alphabet up to arbitrary length生成任意长度的任意字母的所有组合
【发布时间】:2023-03-18 11:16:01
【问题描述】:

假设我有一个包含单个字符的任意大小的数组。我想计算这些字符的所有可能组合,不超过任意长度。

假设我的数组是 [1, 2, 3]。用户指定长度为2。那么可能的组合是[11,22,33,12,13,23,21,31,32]。

我很难找到一个允许任意长度而不仅仅是排列数组的合适算法。哦,虽然速度不是绝对关键,但它也应该相当快。

【问题讨论】:

  • 家庭作业? C++ 对此有何特别之处?
  • 这绝对不是功课;)。我将从标签中删除 C++,因为这确实是一个算法问题。
  • 您确定 11、22 和 33 是​​有效的组合吗?它们不会在传统的组合定义中。
  • 当然有,一共有3^2种组合,等于9;)
  • “组合”是一个数学术语,如 N 选择 k。 3选2显然不是9。

标签: algorithm permutation combinations


【解决方案1】:

只需使用进位进行加法即可。

假设您的数组包含 4 个符号,并且您想要长度为 3 的符号。

以 000 开头(即单词上的每个符号 = 字母表[0])

然后加起来:

000 001 002 003 010 011 ...

算法(给定这些索引)只是增加最小的数字。如果它达到字母表中的符号数,则增加前一个数字(遵循相同的规则)并将当前设置为 0。

C++ 代码:

int N_LETTERS = 4;
char alphabet[] = {'a', 'b', 'c', 'd'};

std::vector<std::string> get_all_words(int length)
{
  std::vector<int> index(length, 0);
  std::vector<std::string> words;

  while(true)
  {
    std::string word(length);
    for (int i = 0; i < length; ++i)
      word[i] = alphabet[index[i]];
    words.push_back(word);

    for (int i = length-1; ; --i)
    { 
      if (i < 0) return words;
      index[i]++;
      if (index[i] == N_LETTERS)
        index[i] = 0;
      else
        break;
    }
  }
}

代码未经测试,但应该可以解决问题。

【讨论】:

  • 听起来很有趣,似乎在我的脑海中起作用。我会测试一下。感谢您的回答!
  • 在我开始工作时接受了您的回答,这确实是一个不错的方法。再次感谢您!
  • 直观实用。谢谢!
【解决方案2】:

Knuth 在The Art of Computer Programming,第 1 卷中深入介绍了组合和排列。这是我几年前写的他的一种算法的实现(不要讨厌这种风格,它的古老代码):

#include <algorithm>
#include <vector>
#include <functional>
#include <iostream>
using namespace std;

template<class BidirectionalIterator, class Function, class Size>
Function _permute(BidirectionalIterator first, BidirectionalIterator last, Size k, Function f, Size n, Size level)
{
    // This algorithm is adapted from Donald Knuth, 
    //      "The Art of Computer Programming, vol. 1, p. 45, Method 1"
    // Thanks, Donald.
    for( Size x = 0; x < (n-level); ++x )   // rotate every possible value in to this level's slot
    {
        if( (level+1) < k ) 
            // if not at max level, recurse down to twirl higher levels first
            f = _permute(first,last,k,f,n,level+1);
        else
        {
            // we are at highest level, this is a unique permutation
            BidirectionalIterator permEnd = first;
            advance(permEnd, k);
            f(first,permEnd);
        }
        // rotate next element in to this level's position & continue
        BidirectionalIterator rotbegin(first);
        advance(rotbegin,level);
        BidirectionalIterator rotmid(rotbegin);
        rotmid++;
        rotate(rotbegin,rotmid,last);
    }
    return f;
}

template<class BidirectionalIterator, class Function, class Size>
Function for_each_permutation(BidirectionalIterator first, BidirectionalIterator last, Size k, Function fn)
{
    return _permute<BidirectionalIterator,Function,Size>(first, last, k, fn, distance(first,last), 0);
}   





template<class Elem>
struct DumpPermutation : public std::binary_function<bool, Elem* , Elem*>
{
    bool operator()(Elem* begin, Elem* end) const
    {
        cout << "[";
        copy(begin, end, ostream_iterator<Elem>(cout, " "));
        cout << "]" << endl;
        return true;
    }
};

int main()
{

    int ary[] = {1, 2, 3};
    const size_t arySize = sizeof(ary)/sizeof(ary[0]);

    for_each_permutation(&ary[0], &ary[arySize], 2, DumpPermutation<int>());

    return 0;
}

这个程序的输出是:

[1 2 ]
[1 3 ]
[2 3 ]
[2 1 ]
[3 1 ]
[3 2 ]

如果您希望您的组合包含重复的元素,如 [11] [22] 和 [33],您可以使用上述算法生成组合列表,然后通过执行类似的操作将新元素附加到生成的列表中这个:

for( size_t i = 0; i < arySize; ++i )
{
    cout << "[";
    for( int j = 0; j < k; ++j )
        cout << ary[i] << " ";
    cout << "]" << endl;
}

...程序输出现在变为:

[1 2 ]
[1 3 ]
[2 3 ]
[2 1 ]
[3 1 ]
[3 2 ]
[1 1 ]
[2 2 ]
[3 3 ]

【讨论】:

  • 这不适用于除了 2-length 单词之外的任何内容。排列中缺失的元素不仅包含仅包含一个符号的元素,还包含包含多个相同符号的元素。例如121 不是 {1,2,3} 的排列,也不是统一的,所以你的算法不会生成它。
  • 提问者似乎喜欢对事情投反对票。我不知道,我也想问一下我的解决方案。我投了你的票。
  • @Larry:嗯,我想我明白为什么它被否决了。 Poita_ 说我的代码不处理任意长度的字符串是错误的,但说重复的元素没有得到正确处理是正确的。不过,提问者正在寻找一种算法,我将他们指向 TAoCP,它有很多。在我看来,这使我的回答完全有效。
  • >提问者似乎喜欢对事情投反对票。
【解决方案3】:

一种方法是使用一个简单的计数器,您在内部将其解释为基数 N,其中 N 是数组中的项目数。然后,您从基数 N 计数器中提取每个数字,并将其用作数组的索引。所以如果你的数组是 [1,2] 并且用户指定的长度是 2,你有

Counter = 0, indexes are 0, 0
Counter = 1, indexes are 0, 1
Counter = 2, indexes are 1, 0
Counter = 3, indexes are 1, 1

这里的诀窍是你的 base-10 到 base-N 的转换代码,这并不难。

【讨论】:

    【解决方案4】:

    如果您事先知道长度,那么您只需要一些 for 循环。比如说,长度 = 3:

    for ( i = 0; i < N; i++ )
       for ( j = 0; j < N; j++ )
          for ( k = 0; k < N; k++ )
             you now have ( i, j, k ), or a_i, a_j, a_k
    

    现在概括一下,只需递归执行,递归的每一步都使用 for 循环之一:

    recurse( int[] a, int[] result, int index)
        if ( index == N ) base case, process result
        else
            for ( i = 0; i < N; i++ ) {
               result[index] = a[i]
               recurse( a, result, index + 1 )
            }
    

    当然,如果您只是想要所有组合,您可以将每个步骤视为基于N 的数字,从1k^N - 1,其中k 是长度。

    基本上你会得到,以N为基数(k = 4):

    0000 // take the first element four times
    0001 // take the first element three times, then the second element
    0002 
    ...
    000(N-1) // take the first element three times, then take the N-th element
    1000 // take the second element, then the first element three times
    1001 
    ..
    (N-1)(N-1)(N-1)(N-1) // take the last element four times
    

    【讨论】:

    • 我知道第一个 - 由于任意长度,它不适用于我的情况。递归也不是一种选择,仅仅是因为性能原因和可能的 SO。不过谢谢你的回答...
    • 如果你需要列出所有的东西并且你担心堆栈溢出,你也应该担心在宇宙结束之前完成算法。 ;) 除非你有一个微不足道的 N = 1 案例,否则即使是一个普通的 2^100 也可能比你想象的要多。如果您有具体案例,请随时告诉我!
    • 同意拉里的观点。堆栈溢出是您最少的问题。这里有 n^k 个单词。如果 k 变成任何合理的大小,生成所有单词将花费不可思议的时间。
    【解决方案5】:

    使用彼得的算法效果很好;但是,如果您的字母集太大或字符串太长,则尝试将所有排列放入一个数组并返回该数组是行不通的。数组的大小将是字母表的大小提升到字符串的长度。

    我在 perl 中创建了这个来解决这个问题:

    package Combiner;
    #package used to grab all possible combinations of a set of letters. Gets one every call, allowing reduced memory usage and faster processing.
    use strict;
    use warnings;
    
    #initiate to use nextWord
    #arguments are an array reference for the list of letters and the number of characters to be in the generated strings.
    sub new {
        my ($class, $phoneList,$length) = @_;
        my $self = bless {
            phoneList => $phoneList,
            length => $length,
            N_LETTERS => scalar @$phoneList,
        }, $class;
        $self->init;
        $self;
    }
    
    sub init {
        my ($self) = shift;
        $self->{lindex} = [(0) x $self->{length}];
        $self->{end} = 0;
        $self;
    }
    
    #returns all possible combinations of N phonemes, one at a time. 
    sub nextWord {
        my $self = shift;
        return 0 if $self->{end} == 1;
        my $word = [('-') x $self->{length}];
    
        $$word[$_] = ${$self->{phoneList}}[${$self->{lindex}}[$_]]
            for(0..$self->{length}-1);
    
        #treat the string like addition; loop through 000, 001, 002, 010, 020, etc.
        for(my $i = $self->{length}-1;;$i--){
             if($i < 0){
                $self->{end} = 1;
                return $word;
             }
             ${$self->{lindex}}[$i]++;
             if (${$self->{lindex}}[$i] == $self->{N_LETTERS}){
                ${$self->{lindex}}[$i] = 0;
             }
             else{
                return $word;
             }
        }
    }
    

    这样称呼它:my $c = Combiner-&gt;new(['a','b','c','d'],20);。然后拨打nextWord抢下一个字;如果nextWord返回0,则表示已经完成。

    【讨论】:

      【解决方案6】:

      这是 Haskell 中的 my implementation

      g :: [a] -> [[a]] -> [[a]]
      g alphabet = concat . map (\xs -> [ xs ++ [s] | s <- alphabet])
      
      allwords :: [a] -> [[a]]
      allwords alphabet = concat $ iterate (g alphabet) [[]]
      

      将此脚本加载到GHCi。假设我们想在字母表 {'a','b','c'} 上找到所有长度小于或等于 2 的字符串。以下GHCi 会话执行此操作:

      *Main> take 13 $ allwords ['a','b','c']
      ["","a","b","c","aa","ab","ac","ba","bb","bc","ca","cb","cc"]
      

      或者,如果您只想要长度等于 2 的字符串:

      *Main> filter (\xs -> length xs == 2) $ take 13 $ allwords ['a','b','c']
      ["aa","ab","ac","ba","bb","bc","ca","cb","cc"]
      

      小心allwords ['a','b','c'],因为它是一个无限列表!

      【讨论】:

        【解决方案7】:

        这是我写的。可能对你有帮助...

        #include<stdio.h>
        #include <unistd.h>
        void main()
        {
        FILE *file;
        int i=0,f,l1,l2,l3=0;
        char set[]="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890!@#$%&*.!@#$%^&*()";
        int size=sizeof(set)-1;
        char per[]="000";
        //check urs all entered details here//
        printf("Setlength=%d Comination are genrating\n",size);
        
        // writing permutation here for length of 3//
        for(l1=0;l1<size;l1++)
        //first for loop which control left most char printed in file//
        { 
        per[0]=set[l1];
        // second for loop which control all intermediate char printed in file//
        for(l2=0;l2<size;l2++)
        {
        per[1]=set[l2];
        //third for loop which control right most char printed in file//
        for(l3=0;l3<size;l3++)
        {
        per[2]=set[l3];
        //apend file (add text to a file or create a file if it does not exist.//
        file = fopen("file.txt","a+");
        //writes array per to file named file.txt// 
        fprintf(file,"%s\n",per); 
        ///Writing to file is completed//
        fclose(file); 
        i++;
        printf("Genrating Combination  %d\r",i);
        fflush(stdout);``
        usleep(1);
        }
        }
        }
        printf("\n%d combination has been genrate out of entered data of length %d \n",i,size);
        puts("No combination is left :) ");
        puts("Press any butoon to exit");
        getchar();
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2023-03-26
          • 2013-10-06
          • 2022-01-07
          • 1970-01-01
          • 2011-09-01
          • 2010-10-03
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多