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

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