【问题标题】:Recursive algorithm getting all combinations into two groups递归算法将所有组合分为两组
【发布时间】:2015-12-25 12:44:32
【问题描述】:

我需要一个算法,给定偶数个元素,对分为两组的元素的所有组合执行评估。组内的顺序无关紧要,因此不应重复组内的排列。具有 N=4 个元素的示例是评估

e(12,34), e(13,24), e(14,32), e(32,14), e(34,12), e(24,13)

我以为我有它,递归算法可以在 N=6 下工作,但结果证明它在 N=8 时失败。这是算法(这个版本只是打印出两组;在我的实际实现中它会执行一个计算):

// Class for testing algoritm
class sym {

    private:
    int N, Nhalf, combs;
    VI order;
    void evaluate();
    void flip(int, int);
    void combinations(int, int);

    public:
    void combinations();

    sym(int N_) : N(N_) {
        if(N%2) {
           cout "Number of particles must divide the 2 groups; requested N = " << N << endl;
           throw exception();
        }
        Nhalf=N/2;
        order.resize(N);
        for(int i=0;i<N;i++) order[i]=i+1;  
    }

    ~sym() {
        cout << endl << combs << " combinations" << endl << endl;
    }
};

// Swaps element n in group 1 and i in group 2
void sym::flip(int n, int i) {
    int tmp=order[n];
    order[n]=order[i+Nhalf];
    order[i+Nhalf]=tmp;
}

// Evaluation (just prints the two groups)
void sym::evaluate() {
    for(int i=0;i<Nhalf;i++) cout << order[i] << " ";
    cout << endl;
    for(int i=Nhalf;i<N;i++) cout << order[i] << " ";
    cout << endl << "--------------------" << endl;
    combs++;
}

// Starts the algorithm
void sym::combinations() {
    cout << "--------------------" << endl;
    combinations(0, 0);
} 

// Recursive algorithm for the combinations
void sym::combinations(int n, int k) {
    if(n==Nhalf-1) {
        evaluate();
        for(int i=k;i<Nhalf;i++) {
            flip(n, i);
            evaluate();
            flip(n, i);
        }
        return;
    }
    combinations(n+1, k);
    for(int i=k;i<Nhalf;i++) {
        flip(n, i);
        combinations(n+1, k+i+1);
        flip(n, i);
    }
}

如果我以 N=2 为例,我得到正确的结果

--------------------
1 2 
3 4 
--------------------
1 3 
2 4 
--------------------
1 4 
3 2 
--------------------
3 2 
1 4 
--------------------
3 4 
1 2 
--------------------
4 2 
3 1 
--------------------

6 combinations

但似乎 N>6 不起作用。是否有一个简单的改变可以解决这个问题,还是我必须重新考虑整个事情?

编辑:最好每次更改只涉及交换两个元素(如上面失败的尝试);因为我认为这最终会使代码更快。

编辑:刚刚意识到它对于 N=6 也失败了,草率的测试。

【问题讨论】:

  • 将其视为两个小组只会产生额外的努力。如果您将目标视为创建一个大小正好是原始集合一半大小的子集(那么另一组就是不在该子集中的所有内容),则要简单得多。
  • 确实如此。我设法做到这一点的唯一方法不仅仅是元素之间的交换(参见最后的编辑)。但我敢肯定有办法..

标签: c++ algorithm recursion combinations permutation


【解决方案1】:

std::next_permutation 可能会有所帮助(无需递归):

#include <iostream>
#include <algorithm>

template<typename T>
void do_job(const std::vector<T>& v, const std::vector<std::size_t>& groups)
{
    std::cout << " e(";
    for (std::size_t i = 0; i != v.size(); ++i) {
        if (groups[i] == 0) {
            std::cout << " " << v[i];
        }
    }
    std::cout << ",";
    for (std::size_t i = 0; i != v.size(); ++i) {
        if (groups[i] == 1) {
            std::cout << " " << v[i];
        }
    }
    std::cout << ")\n";
}

template<typename T>
void print_combinations(const std::vector<T>& v)
{
    std::vector<std::size_t> groups(v.size() / 2, 0);
    groups.resize(v.size(), 1); // groups is now {0, .., 0, 1, .., 1}

    do {
        do_job(v, groups);
    } while (std::next_permutation(groups.begin(), groups.end()));
}

int main()
{
    std::vector<int> numbers = {1, 2, 3, 4};
    print_combinations(numbers);
}

Live Demo

【讨论】:

    【解决方案2】:
    // generate all combination that use n of the numbers 1..k
    void sym::combinations(int n, int k) {
       if (n>k) return;  // oops
       if (n==0) { evaluate(); return; }
       combinations(n, k-1);
       order[n-1] = k;
       combinations(n-1,k-1);
    }
    

    combinations(N/2,N) 开始,无需预先初始化order。但按照编码,它只用第一组填充order 的前半部分,您需要发布过程才能获得第二组。

    使用适量的额外逻辑,您可以改为在combinations 期间填写后半部分。我认为这样做:

    void sym::combinations(int n, int k) {
       if (k==0) { evaluate(); return; }
       if (n>0) {
           order[n-1] = k;
           combinations(n-1,k-1); }
       if (n<k) {
           order[Nhalf+k-n-1] = k;
           combinations(n, k-1); }
    }
    

    我认为基于翻转的设计更丑陋。但仔细想想,其实并不难。因此,改回从combinations(0,0) 开始的设计,您可以使用:

    // Generate all combinations subject to having already filled the first n
    // of the first group and having already filled the last k of the second.
    void sym::combinations(int n, int k) {
        if(n==Nhalf) {
            // Once the first group is full, the rest must be the second group
            evaluate();
            return; 
        }
        // Since the first group isn't full, recursively get all combinations
        // That make the current order[n] part of the first group
        combinations(n+1,k);
    
        if (k<Nhalf) {
          // Next try all combinations that make the current order[n] part of
          // the second group
          std::swap(order[n], order[N-k-1]);
          combinations(n,k+1);
          // Since no one cares about the sequence of the items not yet chosen
          // there is no benefit to swapping back.
        }
    }
    

    【讨论】:

      【解决方案3】:

      要递归列出n choose n/2 组合,您可以使用一种算法,将每个值添加到任一组:

      f(n,k,A,B):
        if k == 0:
          output A,B with {n,n-1..1}
        else if n == k:
          output A with {n,n-1..1},B
        else if k > 0:
          f(n-1,k-1,A with n,B)
          f(n-1,k,A,B with n)
      

      示例如下。对于累积堆栈的一半,可以跳过两个第一个递归调用中的一个,并在评估期间反转对的顺序。

      f(4,2,[],[])
        f(3,1,[4],[])
          f(2,0,[4,3],[]) => {[4,3],[2,1]}
          f(2,1,[4],[3])
            f(1,0,[4,2],[3]) => {[4,2],[3,1]}
            f(1,1,[4],[3,2]) => {[4,1],[3,2]}
        f(3,2,[],[4])
          f(2,1,[3],[4])
            f(1,0,[3,2],[4]) => {[3,2],[4,1]}
            f(1,1,[3],[4,2]) => {[3,1],[4,2]}
          f(2,2,[],[4,3]) => {[2,1],[4,3]}
      

      【讨论】:

        猜你喜欢
        • 2014-08-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-03-15
        • 1970-01-01
        • 1970-01-01
        • 2012-10-24
        • 1970-01-01
        相关资源
        最近更新 更多