算法12.求给定目标值和子元素集合情况下,子元素和为目标值的组合详细情况

     //这个算法的求解目标:给定一个集合,同时给定一个目标值。找出集合中有多少种组合,它们的和
    //都是目标值(逆向思考:即将目标值可以分界为只包含给定集合中元素的几种组合情况)。

//辅助函数

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章