1.DFS
對於一個走迷宮問題,我們的一個解決方法是:
從起點開始走,每碰到一個岔口時,我們選擇一個岔路前進,重複這個操作。如果最後走到死路,那麼我們就退回到上一個岔口,重新選擇一個岔路,直到找到出口。
這裏迷宮問題,面對每一個岔口,都是以“深度”爲關鍵詞,即往下走到不能走爲止,因此這種方式稱爲深度優先搜索(DFS)
注意到,DFS會走遍迷宮所有路徑,也就是說DFS是一種枚舉所有完整路徑以遍歷所有情況的搜索方法。
同時,因爲DFS中有重複的操作,所以很容易想到和遞歸結合。
這裏我們在看下面的問題:
有n件物品,每件重量爲w[i],價值爲c[i],要選出若干件物品放入體積爲V的揹包中,使得揹包中價值最大。
對於每個物品,都有選擇和不選擇兩種選擇,這就可以理解爲“岔道口”;
而選擇物品後的總體積不能超過V,這就可以理解爲“死衚衕”。
於是DFS函數中,我們需要記錄當前處理物品的編號index,對這個物品處理後(選/不選)的總重量以及價值。
void DFS(int index,int sumW,int sumC)
{
if(index==n) //限制條件1,總數量
return ;
if(sumW+w[index]<=V){
if(sumC+c[index]>ans) //限制條件2,重量
ans=sumC+c[index]
DFS(index+1,sumW+w[index],sumC+c[index]; //選index
}
DFS(index+1,sumW,sumC); //不選index
}
其中,通過題目條件的限制來節省DFS計算量的方法稱作剪枝。
我們可以總結:
DFS可以解決的問題是一類,枚舉從N個整數中選擇K個數的且滿足限制條件的所有方案的問題,可以理解爲是選擇問題。
我們再看下面的問題:
給定N個整數(可能有負數),從中選K個數,使得這K個數的和等於一個給定的整數M,並且獲得其中平方和最大的一種。
題目限制條件:
1.總數量限制,N;
2.選擇數量限制,K
3.選擇結果限制,和等於M且平方和最大。
int A[maxn] //存放n個數
vector<int> temp,ans;//temp記錄臨時方案,ans記錄平方和最大方案
void DFS(int index,int nuwK,int sum,int sumSqu)
{
if(now==k&&sum==x){
if(sumSqu>maxSumSqu){
maxSumSqu=sumSqu;
ans=temp;
}
if(index==n||nowK>k||sum>x) return; //剪枝
temp.push_back(A[index]; //選擇的數保存下來;
DFS(index+1,nowK+1,sum+A[index],sumSqu+A[index]*A[index]);
temp.pop_back(A[index] //上步完後,即A[index]不選的情況;
DFS(index+1,nowK,sumSqu);
}
當然,如果每個數可以被多次選擇,那麼可以理解爲,在選擇某個數後,index不用增加(因爲還可以繼續選擇該數,直到不能選擇爲止)。代碼改爲下面的即可,
DFS(index,nowK+1,sum+A[index],sumSqu+A[index]*A[index]);
注意,DFS處理的選擇問題,在問題中,數據對結果的影響只有選擇或者未選擇。
例如,全排序問題。
我們對n個數,實際上每個數都要被選擇,只是選擇的順序不同。我們每次遞歸都要選擇n個數,只是之前選擇的數,我們不能選,所以不能簡單理解成DFS問題,需要回溯。
該問題遞歸處理可以參考:
https://blog.csdn.net/qq_40725780/article/details/104071456
當然也可直接用next_permutation()。
2.BFS
回到DFS開頭提到的迷宮問題,我們還有一個解決方法:
當碰到岔道口時,總是優先訪問從該岔口能直接到達的所有結點,然後再按這些結點被訪問的順序去依次訪問它們能直接到達的所有結點。
這裏,則是以“廣度”爲關鍵詞,即先把該位置能到達的點都走一次,因此被稱爲廣度優先搜索
因爲,搜索的過程中是按層次來找,所以如果找到結果,那麼按照層數可以得到最少步數 。
整個算法,可以通過隊列來實現,且總是按層次的順序遍歷。
模板:
void BFS(int s)
{
queue<int> q;
q.push(s); //起點入隊;
while(!q.empty())
{
取出隊首元素top;
訪問隊首元素top; //進行操作
將隊首元素出隊;
將top下一層結點中未曾入隊的結點全部入隊,並設置爲以入隊; //這裏防止二次訪問
}
}
BFS適用問題:給定初始狀態跟目標狀態,要求從初始狀態到目標狀態的最短路徑
最簡單的就是走迷宮問題;