用一個例子,理解其中包含的DFS思想。
有n件物品,每件物品的重量是w[i],價值是c[i]。現在需要選出若干件物品放入一個容量為V的背包中,
使得在選入背包的物品重量和不超過容量V的前提下,讓背包中物品的價值之和最大,求最大值n在1到20之間。
在這個問題中,需要從n件物品中選擇若干件物品放入背包中,使它們的價值之和最大。這樣的話,每件物品都有選
或者不選兩種選擇,而這就是迷宮中的“岔道口”。那么什么是“死胡同”呢?——題目要求選擇的物品重量之和不能超過V,
因此一旦選擇的物品重量和超過V,就會達到“死胡同”,需要返回最近的“岔道口”。
顯然,每次都需要對物品進行選擇,因此DFS函數的參數中必須記錄當前處理的物品編號index。而題目涉及了物品的重量
與價值,因此也需要參數來記錄在處理當前物品之前,已選物品的總重量sumW與總價值sumC。於是DFS的函數大致是這樣的……
1 void DFS(int index,int sumW,int sumC){ 2 //終止條件(死胡同) 3
4
5 //岔道口,每次的選擇.
6 }
於是,如果選擇不放入index物品,那么sumW與sumC就將不變,接下來處理index+1號物品,即前往DFS(index+1,sumW,sumC)這條
分支;而如果選擇放入index號物品,那么sumW將增加當前物品重量和當前物品價值,接着處理index+1號物品,即前往
DFS(index+1,sumW+w[index],sumC+c[index])這條分支。
一旦index增長到了n,則說明已經把n件物品都處理完了。根據題目要求更新記錄的最大價值。
我們簡單畫一個數組圖。
代碼實現如下:
1 #include <stdio.h>
2
3
4 #define maxn 30
5
6 int maxPrice = 0;//全局變量,最大價值
7 int n,V; 8 int w[maxn],c[maxn]; 9
10 void DFS(int index,int sumW,int sumC); 11
12 int main(){ 13
14
15 scanf("%d%d",&n,&V);//提供n件物品和一個容量為Vkg的背包
16
17 for(int i=0;i<n;i++){ 18 scanf("%d",w+i);//輸入每件物品的重量
19 } 20
21 for(int i=0;i<n;i++){ 22 scanf("%d",c+i);//輸入每件物品的價值
23 } 24
25 DFS(0,0,0); 26 printf("%d\n",maxPrice); 27
28
29 return 0; 30 } 31
32
33 void DFS(int index,int sumW,int sumC){ 34 if(index == n){//已經完成對n件物品的選擇(死胡同)
35 if(sumW <= V&&sumC > maxPrice){ 36 maxPrice = sumC; 37 } 38
39 return ; 40 } 41
42 //岔道口
43 DFS(index+1,sumW,sumC); //不選擇第index件物品
44 DFS(index+1,sumW+w[index],sumC+c[index]); //選擇第index件物品
45 }
可以注意到,由於每件物品都有兩種選擇,因此上面代碼的復雜度為O(2n),這看起來不是很優秀。但是可以通過算法進行優化,
使其表現出更好的效率。還記得我們之前說的“死胡同”嗎?上面的代碼把index == n作為一個“死胡同”,使得計算量很大,其實
在這之前我們已經遇到“死胡同”了,因為在計算過程中必定存在sumW已經超過題目限定的V這種可能。所以我們可以在“岔道口”判斷
下一步是否是“死胡同”。
1 void DFS(int index,int sumW,int sumC){ 2 if(index == n){//已經完成對n件物品的選擇(死胡同)
3 return ; 4 } 5
6 //岔道口
7 DFS(index+1,sumW,sumC); //不選擇第index件物品 8 //只有加入index件物品后未超過容量V,才能繼續探索
9 if(sumW + w[index] <= V){ 10 //注意更新最大價值
11 if(sumC + c[index] > maxPrice){ 12 maxPrice = sumC + c[index]; 13 } 14 DFS(index+1,sumW+w[index],sumC+c[index]); //選擇第index件物品
15 } 16
17 }
可以看到,原先第二條岔路是直接進入的,但是這里先判斷加入第index件物品后是否滿足容量不超過V的要求(“死胡同”),
只有當條件滿足時,才更新最大價值以及進入這條岔路。這樣可以降低計算量,使算法在數據不極端時有很好的表現。這種通過
題目條件的限制來節省DFS計算量的方法稱作剪枝。
事實上,上面的問題給出了一類常見的DFS問題的解決辦法,即給定一個序列,枚舉這個序列的所有子序列(可以不連續)。例如
對於序列{1,2,3}來說,它的子序列如下
{1},{2},{3}
{1,2},{1,3},{2,3}
{1,2,3}
這一類問題通常等價於枚舉從N個整數中算則K個數的所有方案。
舉一個題目例子,可以參照https://www.cnblogs.com/ManOK/p/12552540.html