递归算法将所有组合分为两组
Recursive algorithm getting all combinations into two groups
我需要一种算法,在给定偶数个元素的情况下,对分为两组的元素的所有组合进行评估。组内的顺序无关紧要,因此不应重复组内的排列。一个 N=4 元素的例子是 evaluations
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 也失败了,草率的测试。
// 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
的前半部分,您需要 post 处理才能获得第二组。
通过适量的额外逻辑,您可以在 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.
}
}
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);
}
要递归列出 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]}
我需要一种算法,在给定偶数个元素的情况下,对分为两组的元素的所有组合进行评估。组内的顺序无关紧要,因此不应重复组内的排列。一个 N=4 元素的例子是 evaluations
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 也失败了,草率的测试。
// 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
的前半部分,您需要 post 处理才能获得第二组。
通过适量的额外逻辑,您可以在 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.
}
}
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);
}
要递归列出 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]}