使用递归和回溯生成所有可能的组合
Using recursion and backtracking to generate all possible combinations
我正在尝试实现一个类,该类将在给定元素数量和组合大小的情况下生成所有可能的无序n元组或组合。
换句话说,当调用这个时:
NTupleUnordered unordered_tuple_generator(3, 5, print);
unordered_tuple_generator.Start();
print()是构造函数中设置的回调函数。输出应为:
{0,1,2}
{0,1,3}
{0,1,4}
{0,2,3}
{0,2,4}
{0,3,4}
{1,2,3}
{1,2,4}
{1,3,4}
{2,3,4}
这就是我目前所拥有的:
class NTupleUnordered {
public:
NTupleUnordered( int k, int n, void (*cb)(std::vector<int> const&) );
void Start();
private:
int tuple_size; //how many
int set_size; //out of how many
void (*callback)(std::vector<int> const&); //who to call when next tuple is ready
std::vector<int> tuple; //tuple is constructed here
void add_element(int pos); //recursively calls self
};
并且这是递归函数的实现,Start()只是一个kick Start函数,有一个更干净的接口,它只调用add_element(0);
void NTupleUnordered::add_element( int pos )
{
// base case
if(pos == tuple_size)
{
callback(tuple); // prints the current combination
tuple.pop_back(); // not really sure about this line
return;
}
for (int i = pos; i < set_size; ++i)
{
// if the item was not found in the current combination
if( std::find(tuple.begin(), tuple.end(), i) == tuple.end())
{
// add element to the current combination
tuple.push_back(i);
add_element(pos+1); // next call will loop from pos+1 to set_size and so on
}
}
}
如果我想生成所有可能的恒定N大小的组合,比如说大小为3的组合,我可以做:
for (int i1 = 0; i1 < 5; ++i1)
{
for (int i2 = i1+1; i2 < 5; ++i2)
{
for (int i3 = i2+1; i3 < 5; ++i3)
{
std::cout << "{" << i1 << "," << i2 << "," << i3 << "}n";
}
}
}
如果N不是常数,则需要一个模仿上述内容的递归函数函数,方法是在自己的框架中执行每个for循环。当for循环终止时,程序返回到前一帧,换句话说,回溯。
我一直在递归方面遇到问题,现在我需要将其与回溯相结合,以生成所有可能的组合。有没有迹象表明我做错了什么?我应该做什么,或者我忽略了?
附言:这是一项大学作业,也包括对有序的n元组做同样的事情。
提前感谢!
/////////////////////////////////////////////////////////////////////////////////////////
只是想跟进正确的代码,以防其他人也在想同样的事情。
void NTupleUnordered::add_element( int pos)
{
if(static_cast<int>(tuple.size()) == tuple_size)
{
callback(tuple);
return;
}
for (int i = pos; i < set_size; ++i)
{
// add element to the current combination
tuple.push_back(i);
add_element(i+1);
tuple.pop_back();
}
}
对于有序n元组的情况:
void NTupleOrdered::add_element( int pos )
{
if(static_cast<int>(tuple.size()) == tuple_size)
{
callback(tuple);
return;
}
for (int i = pos; i < set_size; ++i)
{
// if the item was not found in the current combination
if( std::find(tuple.begin(), tuple.end(), i) == tuple.end())
{
// add element to the current combination
tuple.push_back(i);
add_element(pos);
tuple.pop_back();
}
}
}
感谢Jason的全面回应!
考虑形成N个组合的一个好方法是将结构视为组合树。遍历该树就成为了一种自然的方式,可以思考您希望实现的算法的递归性质,以及递归过程将如何工作。
例如,我们有一个序列{1, 2, 3, 4}
,我们希望找到该集合中的所有3个组合。组合的"树"看起来如下:
root
________|___
| |
__1_____ 2
| | |
__2__ 3 3
| | | |
3 4 4 4
使用预购遍历从根开始遍历,并在到达叶节点时识别组合,我们得到组合:
{1, 2, 3}
{1, 2, 4}
{1, 3, 4}
{2, 3, 4}
因此,基本上,我们的想法是使用索引值对数组进行排序,对于递归的每个阶段(在本例中为树的"级别"),向数组中递增,以获得组合集中包含的值。还要注意,我们只需要递归N次。因此,您会有一些递归函数,其签名看起来如下:
void recursive_comb(int step_val, int array_index, std::vector<int> tuple);
其中,step_val
指示我们必须递归多远,array_index
值告诉我们在集合中的位置,以便开始向tuple
添加值,而一旦我们完成,tuple
将成为集合中组合的实例。
然后,您需要从另一个非递归函数调用recursive_comb
,该函数基本上通过初始化tuple
向量并输入最大递归步骤(即,我们想要在元组中的值的数量)来"启动"递归过程:
void init_combinations()
{
std::vector<int> tuple;
tuple.reserve(tuple_size); //avoids needless allocations
recursive_comb(tuple_size, 0, tuple);
}
最后,您的recusive_comb
函数如下所示:
void recursive_comb(int step_val, int array_index, std::vector<int> tuple)
{
if (step_val == 0)
{
all_combinations.push_back(tuple); //<==We have the final combination
return;
}
for (int i = array_index; i < set.size(); i++)
{
tuple.push_back(set[i]);
recursive_comb(step_val - 1, i + 1, tuple); //<== Recursive step
tuple.pop_back(); //<== The "backtrack" step
}
return;
}
您可以在此处看到此代码的工作示例:http://ideone.com/78jkV
请注意,这不是最快的版本的算法,因为我们正在采取一些不需要采取的额外分支,这些分支会创建一些不必要的复制和函数调用等……但希望它能理解递归和回溯的一般思想,以及这两者如何协同工作。
就我个人而言,我会选择一个简单的迭代解决方案。
将节点集表示为一组位。如果需要5个节点,则有5个比特,每个比特代表一个特定的节点。如果你想在元组中有3个,那么你只需要设置其中3个比特并跟踪它们的位置。
基本上,这是对所有不同的节点组合子集的一个简单的变体。在经典实现为的情况下,将节点集表示为整数。整数中的每个位表示一个节点。然后空集为0。然后,您只需增加整数,每个新值都是一组新的节点(表示节点集的位模式)。只是在这个变体中,你要确保总是有3个节点。
只是为了帮助我思考,我从活动的3个顶部节点{4,3,2}开始。然后我倒计时。但是,将其修改为另一个方向的计数是微不足道的。
#include <boost/dynamic_bitset.hpp>
#include <iostream>
class TuppleSet
{
friend std::ostream& operator<<(std::ostream& stream, TuppleSet const& data);
boost::dynamic_bitset<> data; // represents all the different nodes
std::vector<int> bitpos; // tracks the 'n' active nodes in the tupple
public:
TuppleSet(int nodes, int activeNodes)
: data(nodes)
, bitpos(activeNodes)
{
// Set up the active nodes as the top 'activeNodes' node positions.
for(int loop = 0;loop < activeNodes;++loop)
{
bitpos[loop] = nodes-1-loop;
data[bitpos[loop]] = 1;
}
}
bool next()
{
// Move to the next combination
int bottom = shiftBits(bitpos.size()-1, 0);
// If it worked return true (otherwise false)
return bottom >= 0;
}
private:
// index is the bit we are moving. (index into bitpos)
// clearance is the number of bits below it we need to compensate for.
//
// [ 0, 1, 1, 1, 0 ] => { 3, 2, 1 }
// ^
// The bottom bit is move down 1 (index => 2, clearance => 0)
// [ 0, 1, 1, 0, 1] => { 3, 2, 0 }
// ^
// The bottom bit is moved down 1 (index => 2, clearance => 0)
// This falls of the end
// ^
// So we move the next bit down one (index => 1, clearance => 1)
// [ 0, 1, 0, 1, 1]
// ^
// The bottom bit is moved down 1 (index => 2, clearance => 0)
// This falls of the end
// ^
// So we move the next bit down one (index =>1, clearance => 1)
// This does not have enough clearance to move down (as the bottom bit would fall off)
// ^ So we move the next bit down one (index => 0, clearance => 2)
// [ 0, 0, 1, 1, 1]
int shiftBits(int index, int clerance)
{
if (index == -1)
{ return -1;
}
if (bitpos[index] > clerance)
{
--bitpos[index];
}
else
{
int nextBit = shiftBits(index-1, clerance+1);
bitpos[index] = nextBit-1;
}
return bitpos[index];
}
};
std::ostream& operator<<(std::ostream& stream, TuppleSet const& data)
{
stream << "{ ";
std::vector<int>::const_iterator loop = data.bitpos.begin();
if (loop != data.bitpos.end())
{
stream << *loop;
++loop;
for(; loop != data.bitpos.end(); ++loop)
{
stream << ", " << *loop;
}
}
stream << " }";
return stream;
}
主要是琐碎的:
int main()
{
TuppleSet s(5,3);
do
{
std::cout << s << "n";
}
while(s.next());
}
输出为:
{ 4, 3, 2 }
{ 4, 3, 1 }
{ 4, 3, 0 }
{ 4, 2, 1 }
{ 4, 2, 0 }
{ 4, 1, 0 }
{ 3, 2, 1 }
{ 3, 2, 0 }
{ 3, 1, 0 }
{ 2, 1, 0 }
使用循环的shiftBits()版本
int shiftBits()
{
int bottom = -1;
for(int loop = 0;loop < bitpos.size();++loop)
{
int index = bitpos.size() - 1 - loop;
if (bitpos[index] > loop)
{
bottom = --bitpos[index];
for(int shuffle = loop-1; shuffle >= 0; --shuffle)
{
int index = bitpos.size() - 1 - shuffle;
bottom = bitpos[index] = bitpos[index-1] - 1;
}
break;
}
}
return bottom;
}
在MATLAB中:
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% combinations.m
function combinations(n, k, func)
assert(n >= k);
n_set = [1:n];
k_set = zeros(k, 1);
recursive_comb(k, 1, n_set, k_set, func)
return
function recursive_comb(k_set_index, n_set_index, n_set, k_set, func)
if k_set_index == 0,
func(k_set);
return;
end;
for i = n_set_index:length(n_set)-k_set_index+1,
k_set(k_set_index) = n_set(i);
recursive_comb(k_set_index - 1, i + 1, n_set, k_set, func);
end;
return;
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
Test:
>> combinations(5, 3, @(x) printf('%sn', sprintf('%d ', x)));
3 2 1
4 2 1
5 2 1
4 3 1
5 3 1
5 4 1
4 3 2
5 3 2
5 4 2
5 4 3
- 有可能在Armadillo中复制MATLAB circshift方法吗
- 在他自己的方法中,有可能将一个对象取消引用到另一个对象吗
- 有可能使shared_ptr协变吗
- 有可能在信号处理程序中设置promise吗
- 是否有可能实现O(N)时间和O(1)空间解决方案,以实现C++中的字符串循环移位
- 是否有可能构建面向Linux和Windows的.Net Core C++ / CLI应用程序?
- 是否有可能使用debug_info获取ELF文件的源代码?
- 所有可能长度的所有可能组合
- C++,是否有可能/如何定义在.h和.cpp源文件中调用函数的类构造函数
- 有可能在C++中有类的查找表吗
- 是否有可能让 c++ dll 在后台运行 python 程序并让它填充向量图?如果是这样,如何?
- 向量的大小是否有可能为 1 但其中的元素数量为零?
- 如何计算数组整数的总可能组合
- 如何搜索向单词添加字母的所有可能组合?
- 是否有可能编写新的叮当声现代化规则?
- 是否有可能通过指向另一个未关联的子对象的指针来获取指向一个子对象的指针?
- 是否有可能通过演绎指南实现整个 std::make_tuple 功能?
- 是否有可能在没有复制的情况下传递 std::vector<int> 作为参数来获得 std::vector<std::array<int, 3>>?
- 是否有可能具有放入容器的移动操作的类型?
- 是否有可能组合对称的代码片段