//这个算法的求解目标:给定一个集合,同时给定一个目标值。找出集合中有多少种组合,它们的和
//都是目标值(逆向思考:即将目标值可以分界为只包含给定集合中元素的几种组合情况)。
//辅助函数
int nSum(const vector<int>& v)
{
int nSum = 0;
for (int i = 0; i < v.size(); i++)
{
nSum += v[i];
}
return nSum;
}
//1.给定元素集合
vector<int> vNums{2,3,5};
vector<vector<int>> vTrees;
vector<vector<int>> vResults;
//2.给定目标值
int nTarget = 8;
vector<int> vTreeStart;
//3.构造有几种情况:分别以给定集合中每个元素为根节点构建"树"型结构
for (int l = 0; l < vNums.size(); l++)
{
//3.1 开始构建一棵以该集合元素为根的树
vTreeStart.push_back(vNums[l]);
//3.2 对以该元素为根节点的树进行生长,增加树深
//并将所有从"根"元素到各个树节点的路径存储在变量"vTrees"中
for (int i = 0; i < vNums.size(); i++)
{
vector<int> vTree = vTreeStart;
vTree.push_back(vNums[i]);
vTrees.push_back(vTree);//这里得到的vTrees变量包含所有深度是"2"的路径
//这里检查给定集合中是否有单个元素的值(和)是等于nTarget
//这里也是"蹭车"---搭生长树for循环来寻找单个集合元素等于nTarget的元素
//它是可以独立于此for循环的(为了效率,这里在生长子树的时候,
//同时对给定集合中的单个元素进行了“判定”:判定它是否与nTarget相同,相同则将其
//置于存储最终结果的变量vResults中)
if (vNums[i] == nTarget)
{
//这里也是"偷懒":直接应用置空的vTree变量来存放临时数据
vTree.clear();
vTree.push_back(vNums[i]);
vResults.push_back(vTree);
}
}
//这里开始进行深度为3的树生长,或者对树进行裁剪
//首先将当前树赋给一个临时变量,方便后续树的生长
vector<vector<int>> vTreesCopy = vTrees;
//这里定义一个标志变量:记录当前的的搜索路径是否是经过上次裁剪的路径
bool bReturnBack = false;
//对以当前元素为树根的子树进行生长或裁剪
for (int i = 0; i < vTreesCopy.size(); i++)
{
//这里设置好该子树搜索跳出条件:当该子树中无元素或元素个数为1的时候跳出该搜索
//无元素代表没有组合,也就不可以和构成nTarget的值
//1个元素代表只有树根:而树根是否单个元素和为nTarget已经进行了判断(上面"偷懒"地)
if (!(vTrees.size() && vTrees[0].size()>1))
{
break;
}
//这里表示该路径和与目标值nTarget相同
if (nTarget == nSum(vTreesCopy[i]))
{
//此时我们要存储组合到结果变量vResults,并更新以该元素为根的子树
//即将该子树路径从搜索候选路径集合中删除
vResults.push_back(vTreesCopy[i]);
vTrees.erase(vTrees.begin() + i);//delete the origin sub tree
//同时更新当前树的临时变量,并将标记变量置假
vTreesCopy = vTrees;
bReturnBack = false;
}
else if (nTarget > nSum(vTreesCopy[i]))
{
//这里表示搜索候选路径和小于目标值nTarget
//这里首先判断该路径是否已经经过裁剪
if (bReturnBack)
{
//如果该路径是经过裁剪以后,则需要更新当前树的临时变量和该子树
vTreesCopy.erase(vTreesCopy.begin() + i);
vTrees.erase(vTrees.begin() + i);
bReturnBack = false;
i = -1;
continue;
}
//加入当前搜索路径小于目标值nTarget,且没有经过遍历裁剪,则对其进行生长
vector<vector<int>> vGrows;
vector<int> vNow;
for (int j = 0; j < vNums.size(); j++)
{
//这里表示获得当前搜索路径并对其进行生长,最后将生长结果放置在一个临时变量vGrows
vNow = vTreesCopy[i];
vNow.push_back(vNums[j]);
vGrows.push_back(vNow);
}
//这里就要删除刚刚进行过子树生长的路径(因为树的生长表示它的搜索路径不够长)
//然后插入经过生长以后的搜索路径
vTrees.erase(vTrees.begin() + i);
//这里的nInsertPosition代表的是原搜索路径的索引位置
int nInsertPosition = i - 1;
if (nInsertPosition < 0)
{
nInsertPosition = 0;
}
//这里将生长以后的新的那些搜索路径插入树的临时变量中
//这里有个有趣的问题(纯粹是为了娱乐):我插入到第2(nInsertPosition)名后,我就是第2名;
//然后下一个人也插入到第2名前,此时只要保持插入图一位置,新人便可以永远插入到(nPositon)
for (int k = 0; k < vGrows.size(); k++)
{
//将所有生成的搜索路径插入到树中
vTrees.insert(vTrees.begin() + nInsertPosition + k, vGrows[k]);
}
//更新临时树变量
vTreesCopy = vTrees;
//将标记变量置假
bReturnBack = false;
}
else
{
//如果当前搜索路径和大于目标值nTarget,则我们需要对当前路径进行裁剪
//即通过pop函数来同时更新临时变量和树存储变量,并记标记变量为真,为后续生长裁剪准备
vTreesCopy[i].pop_back();
vTrees[i].pop_back();
bReturnBack = true;
}
//这里设置 i = -1 的意义是:让每次树的路径搜索都从最左面子树开始搜索(搜索方法为:深度优先搜索)
i = -1;
}
//这里表示清空当前树,为下一次不同的集合元素作为根元素的的树构造做准备
vTreeStart.clear();
}
//vResults中收集的是不同搜索路径的和为"nTarget"(即使搜索元素值相同,但是顺序不同---代表不同的搜索路径)
//如果我们需要的是组合,而不是"排列"的话,这里就是去除那些冗余组合情况的代码
vector<vector<int>> vNewData;
if (vResults.size())
{
vNewData.push_back(vResults[0]);
for (int i = 1; i < vResults.size(); i++)
{
vector<vector<int>>::iterator iter = find(vNewData.begin(), vNewData.end(), vResults[i]);
if (iter == vNewData.end())
{
vNewData.push_back(vResults[i]);
}
}
vResults = vNewData;
}