簡體   English   中英

遞歸算法將所有組合分為兩組

[英]Recursive algorithm getting all combinations into two groups

我需要一種算法,給定偶數個元素,對分為兩組的所有元素組合執行評估。 組內的順序無關緊要,因此組內的排列不應重復。 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,草率的測試。

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);
}

現場演示

// 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.
    }
}

要遞歸列出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]}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM