生成具有特定约束的大量组合列表的计算效率高的方法是什么?

What is a computationally efficient way to generate a list of a large number of combinations with certain restraints?

我们有一组由整数索引的对象,需要生成这些对象的可能组合对列表(任意长度,不超过对象的数量)并带有约束。约束是如果一对中的一个组合包含一个对象,则该对中的另一个组合不能也包含该对象。

例如,如果我们只有 3 个对象 { 0, 1, 2},则列表应该如下所示

{ {0}, {1} }
{ {0}, {2} }
{ {1}, {2} }
{ {0,1}, {2} }
{ {0}, {1,2} }
{ {0,2}, {1} }

在 C++ 中为多达 20 个对象生成此列表的计算效率高的方法是什么?

在每一对中,每个对象要么未被使用,要么在左边的集合中,要么在右边的集合中。

如果您有 N 个对象,您可以轻松地遍历 3^N 种可能性,跳过那些导致空集的可能性:

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

int main() {
    unsigned N = 5; //number of objects
    vector<unsigned> left, right;

    for (unsigned index=0 ;; ++index) {
        left.clear();
        right.clear();

        //treat the index as a number in base 3
        //each digit determines the fate of one object
        unsigned digits=index;
        for (int obj=1; obj<=N; ++obj) {
            unsigned pos=digits%3;
            digits /= 3;
            if (pos == 1)
                left.push_back(obj);
            else if (pos == 2)
                right.push_back(obj);
        }

        if (digits) {
            //done all possibilities
            break;
        }

        if (left.empty() || right.empty()) {
            // don't want empty left or right
            continue;
        }

        //got one -- print it
        cout << "{ {" <<left[0];
        for (size_t i=1; i<left.size(); ++i)
            cout << "," << left[i];
        cout << "}, {" <<right[0];
        for (size_t i=1; i<right.size(); ++i)
            cout << "," << right[i];
        cout << "} }" << endl;
    }
    return 0;
}

如果 unsigned 是 32 位,这将适用于最多 20 个对象。请注意,在这种情况下,它将打印大约 3.5 亿 对。

在这里试试:https://ideone.com/KIeas7

首先我们可以决定哪个元素将成为我们的对。 比如元素个数为3,考虑二进制表示从0到2^3.

0=000
1=001
2=010
3=011
4=100
5=101
6=110
7=111

现在,我们将从 0 到 2^n 的每个数字组成对,方法是保留数字位置为 1 的元素。像 3=011,它的第一个和第二个位置都有 1,所以我们将第一个和第二个 element.For 6=110 的对,我们将用第二个和第三个元素组成对。 因此,我们可以通过 2^n 的复杂度来决定我们将在每一对中取哪个元素,其中 n 是元素的数量。

现在我们知道每对中将包含哪个元素了。 例如,对于一对,我们选择了 m 个元素。现在我们需要在每一边划分它们。我们可以通过考虑来自 m 个元素的所有数字的二进制表示来以类似的方式做到这一点。 如果m=3,

    0=000
    1=001
    2=010
    3=011
    4=100
    5=101
    6=110
    7=111

所以从 0 到 2^m 的每个数字,我们可以组成一对。为了从数字中生成一对,我们将保留第一组中索引在该数字中具有 0 的元素,并将保留在第二组中索引在该数字中具有 1 的元素。 C++代码:

#include<bits/stdc++.h>
using namespace std;


int main()
{
    long long  cnt=0;
    int n;
    cin>>n;
    int object[n];
    for(int i=0; i<n; i++)cin>>object[i];

    for(int i=0; i<(1<<n); i++){
        // From each i, we will decide which element will be in our set.
        int c=0;
        int nu=0;
        int one_pair[n];
        // Now we will calculate , how many elements will be in current pair.
        for(int j=0; j<n; j++)if((i&(1<<j)))one_pair[c++]=object[j];
        if(c>=2){
            // So we have c element in each pair 
            for(int k=1; k<(1<<(c-1)); k++){
                //Now we will divide each of the c element within two sides.
                cout<<"{ {";
                bool fl=0;
                for(int divider=0;divider<c;divider++){
                   if((k&(1<<divider))){
                        if(fl)cout<<",";
                        fl=1;
                        cout<<one_pair[divider];
                   }
                }

                cout<<"}, ";
                cout<<"{";
                fl=0;
                for(int divider=0;divider<c;divider++){
                   if((k&(1<<divider))==0){
                        if(fl)cout<<",";
                        fl=1;
                        cout<<one_pair[divider];
                   }
                }
                cout<<"} }"<<endl;
            }
        }
    }
    return 0;
}

输出:

  3
0 1 2
{ {0}, {1} }
{ {0}, {2} }
{ {1}, {2} }
{ {0}, {1,2} }
{ {1}, {0,2} }
{ {0,1}, {2} }

这是一个递归版本,它采用包含多个元素的幂集中的每个组合并运行 "put in one bag or the other" 例程。 (这几乎是我第一次尝试用 C++ 编写比琐碎的代码更多的代码,所以我想可能还有改进的余地。)

Powerset:
{{0, 1, 2}, {0, 1}, {0, 2}, {0}, {1, 2}, {1}, {2}, {}}

{{0, 1}, {2}}
{{0, 2}, {1}}
{{0}, {1, 2}}

{{0}, {1}}

{{0}, {2}}

{{1}, {2}}

代码(也here):

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

void print(std::vector<int> const &input){
      std::cout << '{';
      for (int i = 0; i < input.size(); i++) {
        std::cout << input.at(i);
        if (i < input.size() - 1)
          std::cout << ", ";
      }
      std::cout << '}';
}
void printMany(std::vector< std::vector<int> > const &input)
{
    std::cout << '{';
    for (int i = 0; i < input.size(); i++) {
      print(input.at(i));
      if (i < input.size() - 1)
        std::cout << ", ";
    }
    std::cout << '}';
    std::cout << '\n';
}
void printPairs(std::vector< std::vector<int> > const &input)
{
    for (int i = 0; i < input.size(); i+=2) {
      cout << '{';
      print(input.at(i));
      cout << ", ";
      print(input.at(i + 1));
      cout << "}\n";
    }
}

std::vector< std::vector<int> > f(std::vector<int> const &A, int i, const std::vector<int> &left, const std::vector<int> &right) {
  if (i == A.size() - 1 && right.empty())
    return std::vector< std::vector<int> >{left, std::vector<int> {A[i]}};
  if (i == A.size())
    return std::vector< std::vector<int> > {left, right};

  std::vector<int> _left{ left };
  _left.emplace_back(A[i]);
  std::vector< std::vector<int> > result = f(A, i + 1, _left, right);
  std::vector<int> _right{ right };
  _right.emplace_back(A[i]);
  std::vector< std::vector<int> > result1 = f(A, i + 1, left, _right);
  result.insert( result.end(), result1.begin(), result1.end() );
  return result;
}

std::vector< std::vector<int> > powerset(std::vector<int> const &A, const vector<int>& prefix = vector<int>(), int i = 0) {
  if (i == A.size())
    return std::vector< std::vector<int> > {prefix};

  std::vector<int> _prefix(prefix);
  _prefix.emplace_back(A[i]);
  std::vector< std::vector<int> > result = powerset(A, _prefix, i + 1);
  std::vector< std::vector<int> > result1 = powerset(A, prefix, i + 1);
  result.insert( result.end(), result1.begin(), result1.end() );
  return result;
}

int main() {
  std::vector<int> A{0, 1, 2};
  std::vector< std::vector<int> > ps = powerset(A);
  cout << "Powerset:\n";
  printMany(ps);
  cout << "\nResult:\n";
  for (int i=0; i<ps.size(); i++){
    if (ps.at(i).size() > 1){
      std::vector<int> left{ps.at(i)[0]};
      std::vector<int> right;
      printPairs(f(ps.at(i), 1, left, right));
    }
  }
  return 0;
}