搜索——深度優先搜索(DFS)


  設想我們現在身處一個巨大的迷宮中,我們只能自己想辦法走出去,下面是一種看上去很盲目但實際上會很有效的方法。

  以當前所在位置為起點,沿着一條路向前走,當碰到岔道口時,選擇其中一個岔路前進。如果選擇的這個岔路前方是一條死路,就退回到這個岔道口,選擇另一個岔路前進。如果岔路口存在新的岔道口,那么仍然按上面的方法枚舉新岔道口的每一條岔道。這樣,只要迷宮存在出口,那么這個方法一定能夠找到它。

  也就是說,當碰到岔道口時,總是以“深度”作為前進的關鍵詞,不碰到死胡同就不回頭,因此這種搜索的方式稱為深度優先搜索(DFS)

 

  接下來講解一個例子。

  有 n 件物品,每件物品的重量為 w[i],價值為 c[i]。現在需要選出若干件物品放入一個容量為 V 的背包中,使得在選入背包的物品重量和不超過容量 V 的前提下,讓背包中物品的價值之和最大,求最大價值。(1≤n≤20)

  在這個問題中,對每件物品都有選或者不選兩種選擇,而這就是所謂的“岔道口”。那么什么是“死胡同”呢?題目要求選擇的物品重量總和不能超過 V,因此一旦選擇的物品重量總和超過 V,就會到達“死胡同”,需要返回最近的“岔道口”。

  DFS 函數的參數中必須記錄當前處理的物品編號 index,和在處理當前物品之前,已選物品的總重量 sumW 與 總價值 sumC。於是 DFS 函數如下:

void DFS(int index, int sumW, int sumC) {...}

 

 

  思路

  •  如果選擇不放入 index 號物品,那么 sumW 與 sumC 就將不變,接下來處理 index+1 號物品,即前往 DFS(index+1, sumW, sumC) 這條分支;
  •  如果選擇放入 index 號物品,那么 sumW=sumW+w[index], sumC=sumC+c[index],接着處理 index+1 號物品,即前往 DFS(index+1, sumW+w[index], sumC+c[index]) 這條分支;
  •  一旦 index 增長到了 n,則說明已經把 n 件物品處理完畢。此時記錄的 sumW 和 sumC 就是所選物品的總重量和總價值。如果 sumW 不超過 V 且 sumC 大於記錄的最大總價值 maxValue,就說明當前的這種選擇方案可以得到更大的價值,於是用 sumC 更新 maxValue。      

  代碼如下:

 1 /*
 2     搜索_DFS 
 3     有 n 件物品,每件物品的重量為 w[i],價值為 c[i]。現在需要選出若干件物品
 4         放入一個容量為 V 的背包中,使得在選入背包的物品重量和不超過容量 V 的前提下,
 5         讓背包中物品的價值之和最大,求最大價值。(1≤n≤20)
 6 */
 7 
 8 #include <stdio.h>
 9 #include <string.h>
10 #include <math.h>
11 #include <stdlib.h>
12 #include <time.h>
13 #include <stdbool.h>
14 
15 #define maxn 30
16 int n, V, maxValue;    // 物品減數,背包容量,最大價值
17 int w[maxn], c[maxn];    // 每件物品的重量,價值 
18 
19 // index 當前處理的物品編號,sumW 和 sumC 為當前總重量和總價值 
20 void DFS(int index, int sumW, int sumC) {
21     if(index == n) {    // 已經把 n 件物品處理完畢(死胡同) 
22         if(sumW <= V && sumC > maxValue) {
23             maxValue = sumC;    // 有更好的選擇 
24         }
25         return; 
26     }
27     // 岔道口
28     DFS(index+1, sumW, sumC);    // 不選 Index 號物品
29     DFS(index+1, sumW+w[index], sumC+c[index]);    // 選 index 號物品 
30 }
31 
32 int main() {
33     scanf("%d %d", &n, &V);
34     int i;
35     for(i=0; i<n; ++i) {
36         scanf("%d", &w[i]);        // 每件物品的重量 
37     } 
38     for(i=0; i<n; ++i) {
39         scanf("%d", &c[i]);        // 每件物品的價值 
40     }
41     DFS(0, 0, 0);
42     printf("%d\n", maxValue); 
43 
44     return 0;
45 }

 

 

  在上述代碼中,總是把 n 件物品的選擇全部確定之后才去更新最大價值,但是事實上忽視了背包容量不超過 V 這個特點。也就是說,完全可以把對 sumW 的判斷加入“岔道口”中,只有當 sumW ≤ V 時才進入岔道,這樣效率會高很多。代碼如下:

 1 // index 當前處理的物品編號,sumW 和 sumC 為當前總重量和總價值 
 2 void DFS1(int index, int sumW, int sumC) {
 3     if(index == n) {    // 已經把 n 件物品處理完畢(死胡同) 
 4         return; 
 5     }
 6     // 岔道口
 7     DFS(index+1, sumW, sumC);    // 不選 Index 號物品
 8     // 只有加入 index 物品后總重量小於 V 才可以繼續 
 9     if(sumW + w[index] <= V) {
10         if(sumC + c[index] > maxValue) {
11             maxValue = sumC + c[index];
12         }
13         DFS(index+1, sumW+w[index], sumC+c[index]);    // 選 Index 號物品
14     }
15 }

 

 

 

  再來看另外一個問題。

  給定 N 個整數(可能有負數),從中選擇 K 個數,使得這 K 個數之和恰好等於一個給定的整數 X;如果有多種方案,選擇它們中元素平方和最大的一個。

  與之前的問題類似,此處仍然需要記錄當前處理的整數編號 index;由於要求恰好選擇 K 個數,因此需要一個參數 nowK 來記錄當前已經選擇的數的個數;另外,還需要參數 sum 和 sumSqu 分別記錄當前已選整數之和與平方和。於是 DFS 函數如下:

void DFS(int index, int nowK, int sum, int sumSqu) {...} 

 

 

  思路

  •  需要一個數組 temp,用以存放當前選擇的整數。
  •  當試圖進入“選 index 號數”這條分支時,就把 A[index] 加入 temp 中;
  •  當這條分支結束時,就還原 temp 數組,使他不影響“不選 index 號數”這條分支。
  •  如果當前已選擇了 K 個數,且這 K 個數之和恰為 x 時,就將平方和與已有的最大平方和 maxValue 作比較,如果更大,更新 maxValue 和數組 ans。  

 

  代碼如下:

 1 /*
 2     DFS_N個整數選K個 
 3     給定 N 個整數(可能有負數),從中選擇 K 個數,使得這 K 個數之和
 4     恰好等於一個給定的整數 X;如果有多種方案,選擇它們中元素平方和最大的一個。
 5 */
 6 
 7 #include <stdio.h>
 8 #include <string.h>
 9 #include <math.h>
10 #include <stdlib.h>
11 #include <time.h>
12 #include <stdbool.h>
13 
14 #define maxn 30
15 // 序列A中n個數選k個數使得和為x,最大平方和為maxSumSqu 
16 int n, k, x, maxSumSqu=-1, A[maxn]; 
17 int temp[maxn]={0}, ans[maxn]={0};    // 臨時方案,平方和最大的方案
18 
19 void DFS(int index, int nowK, int sum, int sumSqu) {
20     if(nowK == k && sum == x) {        // 找到K個數和為x 
21         if(sumSqu > maxSumSqu) {    // 更優方案 
22             maxSumSqu = sumSqu;        // 更新 maxValue 和數組 ans
23             int i;
24             for(i=0; i<k; ++i) {
25                 ans[i] = temp[i];
26             }
27         }
28     }
29     // 已經處理完n個數,選擇超過k個數,和大於x 
30     if(index==n || nowK>k || sum>x)    return; 
31     // 選 index 號數
32     temp[nowK] = A[index]; 
33     DFS(index+1, nowK+1, sum+A[index], sumSqu+A[index]*A[index]);
34     temp[nowK] = 0; 
35     // 不選 index 號數
36     DFS(index+1, nowK, sum, sumSqu); 
37 } 
38 
39 int main() {
40     scanf("%d %d %d", &n, &k, &x);
41     int i;
42     for(i=0; i<n; ++i) {
43         scanf("%d", &A[i]);    // n個數 
44     }
45     DFS(0, 0, 0, 0);
46     for(i=0; i<k; ++i) {    // 最優方案 
47         printf("%d ", ans[i]);
48     }
49     printf("%d\n", maxSumSqu);    // 最優方案的平方和 
50 
51     return 0;
52 }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM