//這個算法的求解目標:給定一個集合,同時給定一個目標值。找出集合中有多少種組合,它們的和
//都是目標值(逆向思考:即將目標值可以分界爲只包含給定集合中元素的幾種組合情況)。
//輔助函數
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;
}