我知道如何使用递归来生成所有可能的组合,即N选择K.但是如何创建K的所有可能的N / K组? N当然总是可以被K整除。为了澄清:例如,如果N是20而K是4,那么我想生成所有可能的五组四。如果说,N包含1,2,3 ... 20且K为4,那么一个这样的分组是{1,2,3,4},{5,6,7,8},{9,10 ,11,12},{13,14,15,16},{17,18,19,20}。我们假设N相对较小,因此递归是可行的
我觉得这是一个递归内递归问题,因为生成所有可能的单组四(又名N选K)需要递归,然后生成下一组四个变为N - 4选择K ,下一个N - 8选择K等。但是我在实施这个问题时遇到了麻烦...任何帮助?
答案 0 :(得分:1)
术语:你想要的是一组N个元素中的partitions个,每个元素大小为K.有n!/((k!)^(n / k)(n / k)!)这样的分区(见answer on math.SE)。
为了避免过度生成,让我们决定每个的“规范形式” 分区:假设它是按递增顺序写入每个部分,然后 按照最小元素的递增顺序自己编写这些部分。
现在,您可以将分区的生成可视化为丢球的过程 编号为1到N,一个接一个地进入N / K箱。每个bin的约束 (部分)必须按递增顺序然后自动满足(当然 你应该按插入元素的顺序“读取”每个bin,和 如果我们确保不跳过空箱,则满足对零件的约束 在填充下一个之前。
现在将其转换为代码。让我们将我们的分区(或一堆箱子)建模为矢量矢量。
#include <iostream>
#include <vector>
#include <cassert>
std::vector< std::vector<int> > partition;
int N, K;
int num_partitions_found = 0;
void OutputPartition();
通常,在递归生成结构时(或编码任何递归 算法),你的递归函数被赋予一个特定的“状态”,并尝试 扩展该状态的每种增量方式。在不同的选择之间,只是 确保将州恢复到你所得到的状态。
所以让我们把我们的递归函数写成一个状态,其中一个直到n-1
的球被放置在箱中,并通过将球n
放在所有可能的位置来扩展状态。 / p>
void GeneratePartitions(int n) {
// When we've placed all N elements, output and stop.
if (n == N + 1) {
OutputPartition();
return;
}
// Place ball n into each allowed bin.
for (int i = 0; i < N / K; ++i) {
// Cannot place into a full bin
if (partition[i].size() == K) continue;
// Cannot skip an empty bin
if (i > 0 && partition[i-1].empty()) break;
// Place the ball here: extending the state
partition[i].push_back(n);
// How to extend the new state is left to the recursive call
GeneratePartitions(n + 1);
// Make sure you restore state after each recursive call!
partition[i].pop_back();
}
}
这是递归的核心。该计划的其余部分是脚手架。
void OutputPartition() {
assert(partition.size() == N/K);
++num_partitions_found;
for (int i = 0; i < N/K; ++i) {
assert(partition[i].size() == K);
std::cout << "{";
for (int j = 0; j < K; ++j) {
std::cout << partition[i][j] << (j == K - 1 ? "}" : ", ");
}
if (i < N/K - 1) std::cout << ", ";
}
std::cout << std::endl;
}
int main() {
std::cin >> N >> K;
assert(N % K == 0);
partition.resize(N / K);
GeneratePartitions(1);
std::cout << num_partitions_found << " found" << std::endl;
}
注意:这篇文章是literate program:如果你把代码片段放在一起,你就有了一个有效的工作程序。
或者,如果你想尝试该程序(看它运行的速度等),不同的版本是here on github:它不使用全局变量,使用普通数组(更快) )对于分区,并仅为小N打印所有找到的分区(编辑代码以更改它)。
回到你的问题,我们看到通过仔细考虑问题一段时间,你可以避免有不同类型的递归。即使你这样做,编写递归程序的一般方法仍然是:编写一个递归函数,它接受一个状态,以所有可能的方式将它扩展一步(将这些状态的进一步扩展转移到递归调用),然后清除达到其原始状态。 (有时,当状态很小时,你不需要进行任何清理 - 你可以通过函数调用传递状态 - 但有时候,递归调用可以使状态更加清晰。)
答案 1 :(得分:0)
该解决方案使用两个级别的递归:
为避免以不同的顺序重复组:每组中的第一个元素必须小于或等于位置。 结果是第一组总是从1开始,例如。
一些结果:
我想知道进一步验证结果的计算公式。
#include <vector>
#include <set>
#include <iostream>
#include <cassert>
#include <fstream>
namespace lan
{
struct Solution
{
virtual bool Next(const std::vector<unsigned>&) = 0;
};
void SetCombination(const size_t, const size_t, Solution*);
struct ToPrint: public Solution
{
ToPrint(): m_cnt(0) {};
void Print(const std::vector<unsigned>& solution, std::ostream& out, size_t grouped = 0)
{
assert(grouped <= solution.size());
if (grouped)
out<<"{";
for (size_t i = 0; i < solution.size(); ++i)
{
if (i)
{
if (grouped && !(i%grouped))
out<<"} {";
else
out<<" ";
}
out<<solution[i];
}
if (grouped)
out<<"}";
out<<std::endl;
}
bool Next(const std::vector<unsigned>& solution)
{
Print(solution, std::cout);
++m_cnt;
return true;
}
size_t Count() const {return m_cnt;};
protected:
size_t m_cnt;
};
struct GroupOf: public ToPrint
{
typedef ToPrint Super;
GroupOf(int length, int group, bool print): m_length(length), m_group(group), m_print(print)
{
assert(!(length%group));
//m_out.open("out.txt");
//assert(m_out);
}
bool Next(const std::vector<unsigned>& partial)
{
assert(m_group == partial.size());
size_t i;
for (i = 0; i < m_group-1; ++i)
assert(partial[i] < partial[i+1]);
// first element in each partial solution must be <= position (to avoid groups recombination)
if (partial[0] > 0)
return false;
// add the next group to the solution
assert(m_sum <= m_length-m_group);
size_t j, k;
for (i = j = k = 0; (i < m_length) && (j < m_group); ++i)
{
if (!m_solution[i] && (k++ == partial[j]))
{
j++;
m_solution[i] = j + m_sum;
}
}
assert(j == m_group);
// process a solution or go after the next group, recursively
m_sum += m_group;
if (m_sum == m_length)
{
std::vector<unsigned> print;
Translate(m_solution, print);
if (m_print)
//Print(print, m_out, m_group);
Print(print, std::cout, m_group);
assert(Validate(print));
++m_cnt;
}
else
SetCombination(m_length-m_sum, m_group, this);
// restore "stack" of recursive calls
m_sum -= m_group;
for (i = 0; i < m_length; ++i)
if (m_solution[i] > m_sum)
m_solution[i] = 0;
return true;
};
void Start()
{
m_sum = 0;
m_solution.assign(m_length, 0);
SetCombination(m_length, m_group, this);
};
protected:
// transform positions to "real" solution
static
void Translate(const std::vector<unsigned>& a, std::vector<unsigned>& b)
{
b.resize(a.size());
for (size_t i = 0; i < a.size(); ++i)
{
assert(a[i]);
b[a[i]-1] = 1+i;
}
}
#ifdef _DEBUG
static
bool Validate(const std::vector<unsigned>& a)
{
std::set<unsigned> elements;
for (size_t i = 0; i < a.size(); ++i)
elements.insert(a[i]);
if (elements.size() != a.size())
return false;
return true;
}
#else
static
bool Validate(const std::vector<unsigned>&)
{
return true;
}
#endif
private:
GroupOf& operator= (const GroupOf&);
protected:
const size_t m_length;
const size_t m_group;
const bool m_print;
size_t m_sum;
std::vector<unsigned> m_solution;
//std::ofstream m_out;
};
// get a 'length' by 'group' combination; since it's strictly ordered ( = ... the order doesn't matter), the first index is "next"
bool SetCombination(const size_t length, const size_t group, size_t next, std::vector<unsigned>& solution, Solution* cbk)
{
assert(length);
assert(cbk);
assert(group <= length);
assert(next < length);
for (size_t i = next; i < length; ++i)
{
solution.push_back(i);
next = i+1;
if (solution.size() == group)
{
if (!cbk->Next(solution))
return false;
}
else if (next != length)
{
if (!SetCombination(length, group, next, solution, cbk))
return false; //break;
}
solution.pop_back();
}
return true;
}
void SetCombination(const size_t length, const size_t group, Solution* cbk)
{
std::vector<unsigned> solution;
(void)SetCombination(length, group, 0, solution, cbk);
}
size_t Test(size_t total, size_t grouped, bool print = false)
{
GroupOf test(total, grouped, print);
test.Start();
return test.Count();
}
}
到目前为止的所有结果:
assert(3 == lan::Test(4, 2));
assert(10 == lan::Test(6, 3));
assert(15 == lan::Test(6, 2));
assert(126 == lan::Test(10, 5));
assert(280 == lan::Test(9, 3));
assert(945 == lan::Test(10, 2));
if (0)
{
assert(1716 == lan::Test(14, 7));
assert(5775 == lan::Test(12, 4));
assert(126126 == lan::Test(15, 5)); // 1s, Release
}
答案 2 :(得分:-1)
std::next_permutation
可能会有所帮助:
#include <algorithm>
#include <iostream>
int main()
{
int v[] = {1, 2, 3, 4, 5, 6};
do
{
std::cout << "{" << v[0] << "," << v[1] << "," << v[2] << "}, "
<< "{" << v[3] << "," << v[4] << "," << v[5] << "}" << std::endl;
} while (std::next_permutation(std::begin(v), std::end(v)));
return 0;
}
哪个输出(6个!解决方案)
{1,2,3}, {4,5,6}
{1,2,3}, {4,6,5}
...
{6,5,4}, {3,1,2}
{6,5,4}, {3,2,1}
或者如果组内的订单无关紧要,您可以尝试以下方法:
#include <algorithm>
#include <iostream>
int getNextIndex(const int (&v)[6], int start, int value)
{
return std::find(std::begin(v) + start, std::end(v), value) - std::begin(v);
}
void print(const int (&v)[6])
{
// Filter if you want that
// `{1, 2, 3}, {4, 5, 6}` is equivalent to `{4, 5, 6}, {1, 2, 3}`
if (getNextIndex(v, 0, 1) > getNextIndex(v, 0, 2)) return;
//if (getNextIndex(v, 0, 2) > getNextIndex(v, 0, 3)) return; // And so on if you have more groups
const char* sep[] = {", ", ", ", "}"};
for (int i = 1; i != 3; ++i) {
int index = -1;
std::cout << "{";
for (int j = 0; j != 3; ++j) {
index = getNextIndex(v, index + 1, i);
std::cout << 1 + index << sep[j];
}
std::cout << ", ";
}
std::cout << std::endl;
}
int main()
{
int v[] = {1, 1, 1, 2, 2, 2};
do
{
print(v);
} while (std::next_permutation(std::begin(v), std::end(v)));
return 0;
}
输出(10个解决方案):
{1, 2, 3}, {4, 5, 6},
{1, 2, 4}, {3, 5, 6},
{1, 2, 5}, {3, 4, 6},
{1, 2, 6}, {3, 4, 5},
{1, 3, 4}, {2, 5, 6},
{1, 3, 5}, {2, 4, 6},
{1, 3, 6}, {2, 4, 5},
{1, 4, 5}, {2, 3, 6},
{1, 4, 6}, {2, 3, 5},
{1, 5, 6}, {2, 3, 4},