【问题标题】:Generating combinations in c++在 C++ 中生成组合
【发布时间】:2012-03-14 21:25:05
【问题描述】:

我一直在搜索使用 c++ 生成组合的源代码。我为此找到了一些高级代码,但这仅适用于特定数量的预定义数据。任何人都可以给我一些提示,或者可能是产生组合的一些想法。举个例子,假设集合 S = { 1, 2, 3, ...., n},我们从中挑选出 r=2。输入将是nr。在这种情况下,程序将生成长度为 2 的数组,例如 5 2 输出 1 2、1 3 等。我在构建算法时遇到了困难。我花了一个月的时间思考这个问题。

【问题讨论】:

  • 我不太明白你想要什么。给定集合 S 和输入 2,您是否希望在数组长度为 2 的数组中包含 2 和 S 的每个项目的所有组合?
  • 您需要更具体地说明您想要什么样的组合。例如,如果 S = {1, 2} 和​​ r=2,你想要 {1,2} 和 {2,1},还是想要 {1,1} 和 {2,2},甚至只想要 {1 ,2}?
  • 我想他想要这个:en.wikipedia.org/wiki/Combination。 {1,2} {2,1} 是一样的,{1,1} 和 {2,2} 是不可能的。
  • 对于可读算法,您可以查看 Python 文档:docs.python.org/library/itertools.html
  • answer 只需 google 一次搜索即可

标签: c++ algorithm combinations


【解决方案1】:

使用std::next_permutation的简单方法:

#include <iostream>
#include <algorithm>
#include <vector>

int main() {
    int n, r;
    std::cin >> n;
    std::cin >> r;

    std::vector<bool> v(n);
    std::fill(v.end() - r, v.end(), true);

    do {
        for (int i = 0; i < n; ++i) {
            if (v[i]) {
                std::cout << (i + 1) << " ";
            }
        }
        std::cout << "\n";
    } while (std::next_permutation(v.begin(), v.end()));
    return 0;
}

或以更易于遵循的顺序输出结果的轻微变化:

#include <iostream>
#include <algorithm>
#include <vector>

int main() {
   int n, r;
   std::cin >> n;
   std::cin >> r;

   std::vector<bool> v(n);
   std::fill(v.begin(), v.begin() + r, true);

   do {
       for (int i = 0; i < n; ++i) {
           if (v[i]) {
               std::cout << (i + 1) << " ";
           }
       }
       std::cout << "\n";
   } while (std::prev_permutation(v.begin(), v.end()));
   return 0;
}

一点解释:

它的工作原理是创建一个“选择数组”(v),我们在其中放置r选择器,然后我们创建这些选择器的所有排列,如果在当前排列中被选中,则打印相应的集合成员的v。希望这会有所帮助。

【讨论】:

  • 它将输出排列而不是问题中所述的组合。您可能会发现 this link 很有帮助
  • 嗯。要么我想念一些东西,要么你想念一些东西。看看这个:ideone.com/tfAGp
  • @kids_fox 这个代码是正确的,它确实产生了组合。它工作的原因是因为它打印了所有 sorted 排列。
  • 我用通用形式重写了这段代码:coliru.stacked-crooked.com/…
  • 您可以获得“更容易遵循订单”,而无需反转 if(v[i]) 检查是否将 vv.begin() 填充到 v.end()-n+r 而不是 v.begin()+n-rv.end()。跨度>
【解决方案2】:

如果您注意到对于每个级别 r 您选择一个从 1 到 n 的数字,您就可以实现它。

在 C++ 中,我们需要在产生结果的调用之间“手动”保持状态(组合):因此,我们构建了一个在构造时初始化状态的类,并有一个成员在每次调用时返回组合,而有解决方案:例如

#include <iostream>
#include <iterator>
#include <vector>
#include <cstdlib>

using namespace std;

struct combinations
{
    typedef vector<int> combination_t;

    // initialize status
   combinations(int N, int R) :
       completed(N < 1 || R > N),
       generated(0),
       N(N), R(R)
   {
       for (int c = 1; c <= R; ++c)
           curr.push_back(c);
   }

   // true while there are more solutions
   bool completed;

   // count how many generated
   int generated;

   // get current and compute next combination
   combination_t next()
   {
       combination_t ret = curr;

       // find what to increment
       completed = true;
       for (int i = R - 1; i >= 0; --i)
           if (curr[i] < N - R + i + 1)
           {
               int j = curr[i] + 1;
               while (i <= R-1)
                   curr[i++] = j++;
               completed = false;
               ++generated;
               break;
           }

       return ret;
   }

private:

   int N, R;
   combination_t curr;
};

int main(int argc, char **argv)
{
    int N = argc >= 2 ? atoi(argv[1]) : 5;
    int R = argc >= 3 ? atoi(argv[2]) : 2;
    combinations cs(N, R);
    while (!cs.completed)
    {
        combinations::combination_t c = cs.next();
        copy(c.begin(), c.end(), ostream_iterator<int>(cout, ","));
        cout << endl;
    }
    return cs.generated;
}

测试输出:

1,2,
1,3,
1,4,
1,5,
2,3,
2,4,
2,5,
3,4,
3,5,
4,5,

【讨论】:

    【解决方案3】:

    我基于algorithms from Prof. Nathan Wodarz的简单高效的解决方案:

    // n choose r combination
    #include <vector>
    #include <iostream>
    #include <algorithm>
    
    struct c_unique {
      int current;
      c_unique() {current=0;}
      int operator()() {return ++current;}
    } UniqueNumber;
    
    void myfunction (int i) {
      std::cout << i << ' ';
    }
    
    int main()
    {
        int n=5;
        int r=3;
    
        std::vector<int> myints(r);
        std::vector<int>::iterator first = myints.begin(), last = myints.end();
    
        std::generate(first, last, UniqueNumber);
    
        std::for_each(first, last, myfunction);
        std::cout << std::endl;
    
        while((*first) != n-r+1){
            std::vector<int>::iterator mt = last;
    
            while (*(--mt) == n-(last-mt)+1);
            (*mt)++;
            while (++mt != last) *mt = *(mt-1)+1;
    
            std::for_each(first, last, myfunction);
            std::cout << std::endl;
        }
    }
    

    然后输出是:
    1 2 3
    1 2 4
    1 2 5
    1 3 4
    1 3 5
    1 4 5
    2 3 4
    2 3 5
    2 4 5
    3 4 5

    【讨论】:

    • 这是最快、最简单、最干净的非递归算法。递归不会在这里增加清晰度,并且可能会更慢。
    • 它是干净的,因为它是硬编码的,可以处理从 1 到 N 的值。否则与更通用的 CapelliC 完全相同。
    【解决方案4】:
              #include<iostream>
              using namespace std;
    
              for(int i=1;i<=5;i++)
                 for (int j=2;j<=5;j++) 
                    if (i!=j)
                      cout<<i<<","<<j<<","<<endl;
    
               //or instead of cout... you can put them in a matrix n x 2 and use the solution
    

    【讨论】:

    • 这包括同一组合的不同排列,尝试修改第二个循环for (int j=i+1;j&lt;=5;j++)
    【解决方案5】:

    代码类似于生成二进制数字。保留一个额外的数据结构,一个数组 perm[],它在索引 i 处的值将告诉是否包含第 i 个数组元素。并且还保留一个计数变量。每当 count == 组合长度时,根据 perm[] 打印元素。

    #include<stdio.h>
    
    // a[] : given array of chars 
    // perm[] : perm[i] is 1 if a[i] is considered, else 0
    // index : subscript of perm which is to be 0ed and 1ed
    // n     : length of the given input array
    // k     : length of the permuted string
    void combinate(char a[], int perm[],int index, int n, int k)
    {
       static int count = 0;
    
       if( count == k )
       { 
          for(int i=0; i<n; i++)
            if( perm[i]==1)
              printf("%c",a[i]);
          printf("\n");
    
        } else if( (n-index)>= (k-count) ){
    
             perm[index]=1;
             count++;
             combinate(a,perm,index+1,n,k);
    
             perm[index]=0;
             count--;
             combinate(a,perm,index+1,n,k);
    
       }
    }
    int main()
    {
       char a[] ={'a','b','c','d'};
       int perm[4] = {0};
       combinate(a,perm,0,4,3);
    
       return 0;
    }
    

    【讨论】:

      【解决方案6】:

      这是一种递归方法,可以用于任何类型。您可以迭代 Combinations 类的实例(例如,或 get() 具有所有组合的向量,每个组合都是对象的向量。这是用 C++11 编写的。

      //combinations.hpp
      #include <vector>
      
      template<typename T> class Combinations {
      // Combinations(std::vector<T> s, int m) iterate all Combinations without repetition
      // from set s of size m s = {0,1,2,3,4,5} all permuations are: {0, 1, 2}, {0, 1,3}, 
      // {0, 1, 4}, {0, 1, 5}, {0, 2, 3}, {0, 2, 4}, {0, 2, 5}, {0, 3, 4}, {0, 3, 5},
      // {0, 4, 5}, {1, 2, 3}, {1, 2, 4}, {1, 2, 5}, {1, 3, 4}, {1, 3, 5}, {1, 4, 5}, 
      // {2, 3, 4}, {2, 3, 5}, {2, 4, 5}, {3, 4, 5}
      
      public:
          Combinations(std::vector<T> s, int m) : M(m), set(s), partial(std::vector<T>(M))
          {
              N = s.size(); // unsigned long can't be casted to int in initialization
      
              out = std::vector<std::vector<T>>(comb(N,M), std::vector<T>(M)); // allocate space
      
              generate(0, N-1, M-1);
          };
      
          typedef typename std::vector<std::vector<T>>::const_iterator const_iterator;
          typedef typename std::vector<std::vector<T>>::iterator iterator;
          iterator begin() { return out.begin(); }
          iterator end() { return out.end(); }    
          std::vector<std::vector<T>> get() { return out; }
      
      private:
          void generate(int i, int j, int m);
          unsigned long long comb(unsigned long long n, unsigned long long k); // C(n, k) = n! / (n-k)!
      
          int N;
          int M;
          std::vector<T> set;
          std::vector<T> partial;
          std::vector<std::vector<T>> out;   
      
          int count (0); 
      };
      
      template<typename T> 
      void Combinations<T>::generate(int i, int j, int m) {  
          // combination of size m (number of slots) out of set[i..j]
          if (m > 0) { 
              for (int z=i; z<j-m+1; z++) { 
                  partial[M-m-1]=set[z]; // add element to permutation
                  generate(z+1, j, m-1);
              }
          } else {
              // last position
              for (int z=i; z<j-m+1; z++) { 
                  partial[M-m-1] = set[z];
                  out[count++] = std::vector<T>(partial); // add to output vector
              }
          }
      }
      
      template<typename T> 
      unsigned long long
      Combinations<T>::comb(unsigned long long n, unsigned long long k) {
          // this is from Knuth vol 3
      
          if (k > n) {
              return 0;
          }
          unsigned long long r = 1;
          for (unsigned long long d = 1; d <= k; ++d) {
              r *= n--;
              r /= d;
          }
          return r;
      }
      

      测试文件:

      // test.cpp
      // compile with: gcc -O3 -Wall -std=c++11 -lstdc++ -o test test.cpp
      #include <iostream>
      #include "combinations.hpp"
      
      struct Bla{
          float x, y, z;
      };
      
      int main() {
      
          std::vector<int> s{0,1,2,3,4,5};
          std::vector<Bla> ss{{1, .4, 5.0},{2, .7, 5.0},{3, .1, 2.0},{4, .66, 99.0}};
      
          Combinations<int> c(s,3);
          // iterate over all combinations
          for (auto x : c) { for (auto ii : x) std::cout << ii << ", "; std::cout << "\n"; }
      
          // or get a vector back
          std::vector<std::vector<int>> z = c.get();  
      
          std::cout << "\n\n";
      
          Combinations<Bla> cc(ss, 2);
          // combinations of arbitrary objects
          for (auto x : cc) { for (auto b : x) std::cout << "(" << b.x << ", " << b.y << ", " << b.z << "), "; std::cout << "\n"; }    
      
      }
      

      输出是:

      0, 1, 2, 0, 1, 3, 0, 1, 4, 0, 1, 5, 0, 2, 3, 0, 2, 4, 0, 2, 5, 0, 3, 4, 0, 3, 5, 0, 4, 5, 1, 2, 3, 1, 2, 4, 1, 2, 5, 1, 3, 4, 1, 3, 5, 1, 4, 5, 2, 3, 4, 2, 3, 5, 2, 4, 5, 3, 4, 5,

      (1, 0.4, 5), (2, 0.7, 5), (1, 0.4, 5), (3, 0.1, 2), (1, 0.4, 5), (4, 0.66, 99), (2, 0.7, 5), (3, 0.1, 2), (2, 0.7, 5), (4, 0.66, 99), (3, 0.1, 2), (4, 0.66, 99),

      【讨论】:

        【解决方案7】:

        以下是 C++ 中的一种迭代算法,它不使用 STL、递归或条件嵌套循环。这种方式速度更快,它不执行任何元素交换,也不会给堆栈带来递归负担,还可以通过将mallloc()free()printf() 替换为new 来轻松移植到ANSI C, deletestd::cout

        如果您希望显示的元素从 1 开始,请更改 OutputArray() 函数。
        即:cout &lt;&lt; ka[i]+1... 而不是 cout &lt;&lt; ka[i]...

        请注意,我使用 K 而不是 r

        void OutputArray(unsigned int* ka, size_t n) {
            for (int i = 0; i < n; i++)
                std::cout << ka[i] << ",";
            std::cout << endl;
        }
        
        
        void GenCombinations(const unsigned int N, const unsigned int K) {
            unsigned int *ka = new unsigned int [K];  //dynamically allocate an array of UINTs
            unsigned int ki = K-1;                    //Point ki to the last elemet of the array
            ka[ki] = N-1;                             //Prime the last elemet of the array.
        
            while (true) {
                unsigned int tmp = ka[ki];  //Optimization to prevent reading ka[ki] repeatedly
        
                while (ki)                  //Fill to the left with consecutive descending values (blue squares)
                    ka[--ki] = --tmp;
                OutputArray(ka, K);
        
                while (--ka[ki] == ki) {    //Decrement and check if the resulting value equals the index (bright green squares)
                    OutputArray(ka, K);
                    if (++ki == K) {      //Exit condition (all of the values in the array are flush to the left)
                        delete[] ka;
                        return;
                    }
                }
            }
        }
        
        
        int main(int argc, char *argv[])
        {
            GenCombinations(7, 4);
            return 0;
        }
        

        组合:“7选4”。

        【讨论】:

        • 从堆中分配内存是耗时的操作。使用模板,您可以将其放在堆栈上。
        • @DejanM:栈内存比堆内存少。无论如何,请注意内存分配只发生一次。
        【解决方案8】:

        我建议您自己弄清楚如何在纸上执行此操作并从中推断出伪代码。之后,您只需要决定对操作数据进行编码和存储的方式即可。

        例如:

        For each result item in result array // 0, 1, ... r
            For each item possible // 0, 1, 2, ... n
                if current item does not exist in the result array
                    place item in result array
                    exit the inner for
                end if
            end for
        end for
        

        【讨论】:

          【解决方案9】:

          您可以使用递归来选择 N+1 个组合,然后选择 N 个组合,然后添加 1。您添加的 1 必须始终位于 N 的最后一个之后,因此如果您的 N 包含最后一个元素,则没有与之关联的 N+1 组合。

          也许不是最有效的解决方案,但它应该可以工作。

          基本情况会选择 0 或 1。您可以选择 0 并得到一个空集。从一个空集合中,您可以假设迭代器在元素之间而不是在它们之间工作。

          【讨论】:

            【解决方案10】:

            这是我的尝试:

            没有任何依赖的函数(准备复制/粘贴)

             template<class _Tnumber, class _Titerator >
                  bool next_combination
                   (
                        _Titerator const& _First
                      , _Titerator const& _Last
                      , _Tnumber const& _Max //!< Upper bound. Not reachable
                   )
                   {
                    _Titerator _Current = _First;
                     if( _Current  == _Last )
                      {
                       return false;
                      }
                     *_Current += 1;
                     if( *_Current < _Max )
                      {
                       return true;
                      }
                    _Titerator _Next = _Current + 1;
                     if( _Next == _Last )
                      {
                       return false;
                      }
                     if( false == next_combination( _Next, _Last, _Max - 1 ) )
                      {
                       return false;
                      }
                     *_Current = *_Next + 1; 
                     return *_Current < _Max;
                    }
            

            测试:

            vector<int> vec({3,2,1}); // In descending order and different
            do
             {
              copy( vec.begin(), vec.end(), ostream_iterator<int>(cout, ", " ) ); cout << endl;
             }while( ::math::algorithm::next_combination( vec.begin(), vec.end(), 6 ) );
            

            然后输出:

            3, 2, 1,
            4, 2, 1,
            5, 2, 1,
            4, 3, 1,
            5, 3, 1,
            5, 4, 1,
            4, 3, 2,
            5, 3, 2,
            5, 4, 2,
            5, 4, 3,
            

            【讨论】:

            • 信不信由你,但您的所有第一个代码块都违反了规范。名称开头的下划线后没有大写字母;保留实施。
            【解决方案11】:
            void print(int *a, int* s, int ls)
            {
                for(int i = 0; i < ls; i++)
                {
                    cout << a[s[i]] << " ";
                }
                cout << endl;
            }    
            void PrintCombinations(int *a, int l, int k, int *s, int ls, int sp)
            {
               if(k == 0)
               {
                   print(a,s,ls);
                   return;
               }
               for(int i = sp; i < l; i++)
               {
            
                  s[k-1] = i;
                  PrintCombinations(a,l,k-1,s,ls,i+1);
                  s[k-1] = -1;
            
               }
            }
            
            int main()
            {
             int e[] = {1,2,3,4,5,6,7,8,9};
             int s[] = {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1};
             PrintCombinations(e,9,6,s,6,0);
            }
            

            【讨论】:

              【解决方案12】:

              对于(n选择r)的特殊情况,其中r是一个固定常数,我们可以编写r个嵌套循环来达到这种情况。有时当 r 不固定时,我们可能会有另一种特殊情况 (n 选择 n-r),其中 r 又是一个固定常数。这个想法是每个这样的组合都是(n选择r)组合的逆。所以我们可以再次使用 r 嵌套循环,但反转解决方案:

              // example 1: choose each 2 from given vector and apply 'doSomething'
              void doOnCombinationsOfTwo(const std::vector<T> vector) {
                 for (int i1 = 0; i1 < vector.size() - 1; i1++) {
                    for (int i2 = i1 + 1; i2 < vector.size(); i2++) {
                       doSomething( { vector[i1], vector[i2] });
                    }
                 }
              }
              
              
              // example 2: choose each n-2 from given vector and apply 'doSomethingElse'
              void doOnCombinationsOfNMinusTwo(const std::vector<T> vector) {
                 std::vector<T> combination(vector.size() - 2); // let's reuse our combination vector 
                 for (int i1 = 0; i1 < vector.size() - 1; i1++) {
                    for (int i2 = i1 + 1; i2 < vector.size(); i2++) {
                       auto combinationEntry = combination.begin(); // use iterator to fill combination
                       for (int i = 0; i < vector.size(); i++) {
                          if (i != i1 && i != i2) {
                             *combinationEntry++ = i;
                          }
                       }
                       doSomethingElse(combinationVector);
                    }
                 }
              }
              

              【讨论】:

                【解决方案13】:

                这似乎是可读的,它也适用于std::vectorstd::liststd::deque,甚至是静态声明的int intArray[]

                #include <iostream>
                #include <vector>
                #include <algorithm>
                #include <numeric>
                #include <list>
                #include <set>
                #include <iterator>
                
                template<typename InputIt, typename T>
                bool nextCombination(InputIt begin,
                                     InputIt end,
                                     T toElement) {
                    /*
                        Given sequence: 1 2 3 4 5
                        Final sequence: 6 7 8 9 10
                
                        -- Formally --
                        Given sequence: 1 2 ... k-1 k
                        Final sequence: (n-k+1) (n-k+2) ... (n-1) n
                
                        lengthOfSubsequence = positionOf(5) - positionOf(1) = 5
                        
                        We look for an element that satisfies:
                            seqeunce[pos] < n - k + pos
                
                    */
                
                    const auto lengthOfSubsequence = std::distance(begin, end);
                
                    auto viewed_element_it = std::make_reverse_iterator(end);
                    auto reversed_begin = std::make_reverse_iterator(begin);
                
                    /*Looking for this element here*/
                
                    while ((viewed_element_it != reversed_begin) && 
                           (*viewed_element_it >= toElement -
                                                  lengthOfSubsequence + 
                                                  std::distance(viewed_element_it, reversed_begin))) {
                        //std::distance shows position of element in subsequence here
                        viewed_element_it++;
                    }
                
                    if (viewed_element_it == reversed_begin)
                        return false;
                
                    auto it = std::prev(viewed_element_it.base());
                
                    /*
                        Increment the found element. 
                    The rest following elements we set as seqeunce[pos] = seqeunce[pos-1] + 1
                    */
                
                    std::iota(it, end, *it + 1);
                
                    return true;
                }
                
                int main()
                {
                    std::list<int> vec = { 1, 2, 3 };
                
                    do {
                        std::copy(vec.begin(), vec.end(), std::ostream_iterator<int>(std::cout, " "));
                        std::cout << std::endl;
                    } while (nextCombination(vec.begin(), vec.end(), 10));
                }
                
                

                【讨论】:

                  【解决方案14】:

                  可以直接按字典顺序计算所有组合索引,就像我在下面的代码中所做的那样。

                  这些索引可用于直接输出或作为指向任何组合项的指针,如 main() 函数的第二个示例中的 "abcde" 字符串,请参阅代码后的输出示例。

                  Try it online!

                  #include <vector>
                  #include <iostream>
                  
                  template <typename F>
                  void Combinations(size_t n, size_t k, F && out) {
                      if (k > n)
                          return;
                      std::vector<size_t> a(k);
                      for (size_t i = 0; i < k; ++i)
                          a[i] = i;
                      while (true) {
                          out(a);
                  
                          int i = int(k) - 1;
                          while (i >= 0 && a[i] >= n - 1 - (k - 1 - i))
                              --i;
                          if (i < 0)
                              break;
                          for (size_t j = a[i] + 1; i < k; ++j, ++i)
                              a[i] = j;
                      }
                  }
                  
                  int main() {
                      Combinations(5, 3, [](auto const & a){
                          for (auto i: a)
                              std::cout << i << " ";
                          std::cout << std::endl;
                      });
                  
                      std::string s = "abcde";
                      Combinations(5, 3, [&](auto const & a){
                          for (auto i: a)
                              std::cout << s[i] << " ";
                          std::cout << std::endl;
                      });
                  }
                  

                  输出:

                  0 1 2 
                  0 1 3 
                  0 1 4 
                  0 2 3 
                  0 2 4 
                  0 3 4 
                  1 2 3 
                  1 2 4 
                  1 3 4 
                  2 3 4 
                  
                  a b c 
                  a b d 
                  a b e 
                  a c d 
                  a c e 
                  a d e 
                  b c d 
                  b c e 
                  b d e 
                  c d e 
                  

                  【讨论】:

                    【解决方案15】:
                    vector<list<int>> generate(int N, int K, int& count) {
                    
                        vector<list<int>> output;
                    
                        if(K == 1) {
                            count = N;
                            for(int i = 1; i <= N; i++) {
                                list<int> l = {i};
                                output.push_back(l);
                            }
                        } else {
                            count = 0;
                            int n;
                            vector<list<int>> l = generate(N, K - 1, n);
                            for(auto iter = l.begin(); iter != l.end(); iter++) {
                                int last = iter->back();
                                for (int i = last + 1; i <= N; ++i) {
                                    list<int> value = *iter;
                                    value.push_back(i);
                                    output.push_back(value);
                                    count++;
                                }
                            }
                        }
                    
                        return output;
                    }
                    

                    【讨论】:

                      【解决方案16】:

                      如果 r 很小,你可以只使用 for 循环,这里 r = 2,所以两个 for 循环:

                      unsigned int i, j, max=0;
                      for(i=1; i<=n; i++){
                          for(j=i+1; j<=n; j++){
                                  int ans = (i & j);
                                  cout << i << " " << j << endl;     
                           }
                      }
                      

                      【讨论】:

                      • 应该使用递归
                      猜你喜欢
                      • 1970-01-01
                      • 1970-01-01
                      • 2011-03-11
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      相关资源
                      最近更新 更多