(1)問題描述:子集和問題的一個實例為<data, num>。其中 data = {x1, x2, ......, xn} 是一個正整數的集合,targetValue 是一個正整數。子集和問題判定是否存在 data 的一個子集 data1,使得
x1 + x2 + ...... + xn = targetValue (x € data1)
(2)算法設計:使用回溯算法子集樹來解決,對於給定的集合 data = {x1, x2, ......, xn} 和正整數 targetValue,計算 data 的一個子集 data1,滿足【x1 + x2 + ...... + xn = targetValue (x € data1)】
(3)算法代碼:

public class SubsetSum { /** * 目標值 */ private static Integer targetValue; /** * 當前所選元素之和 */ private static Integer sum = 0; /** * 數據個數 */ private static Integer num; /** * 未確定值 */ private static Integer indeterminacyValue = 0; /** * 數據數組 */ private static Integer[] data; /** * 數據存放【0:不存放 1:存放】 */ private static Integer[] store; /** * 初始化數據 */ private static void initData() { Scanner input = new Scanner(System.in); System.out.println("請輸入目標值:"); targetValue = input.nextInt(); System.out.println("請輸入數據個數:"); num = input.nextInt(); data = new Integer[num]; store = new Integer[num]; System.out.println("請輸入各個數:"); for (int i = 0; i < data.length; i++) { data[i] = input.nextInt(); store[i] = 0; // 初始化都不存放 indeterminacyValue += data[i]; } } /** * 回溯查找 */ private static Boolean backtrack(int i) { if (sum == targetValue) { // 找到可行解,直接返回 true return true; } if (i == data.length) { // 找不到可行解,直接返回 false return false; } indeterminacyValue -= data[i]; // 計算還未確定數的總和 if (sum + data[i] <= targetValue) { // 當前 sum + data[i] <= targetValue 直接進入左子樹 store[i] = 1; // 數據 i 存放,列入所選加數之中 sum += data[i]; if (backtrack(i + 1)) { // 繼續深入下一層判定求和 return true; } sum -= data[i]; // 求解深入完畢,若不滿足所求的解,需要回溯,恢復當前的 sum 起始值 } if (sum + indeterminacyValue >= targetValue) { // 剪枝函數【若當前 sum + 未確定的值 >= 目標值,才進入右子樹深度搜索;否則沒有任何意義】 store[i] = 0; // 數據 i 此時不存放,列入所選加數之中 if (backtrack(i + 1)) { return true; } } indeterminacyValue += data[i]; // 求解深入完畢,若不滿足所求的解,需要回溯,恢復當前的 indeterminacyValue 起始值 return false; } /** * 輸出 */ private static void print() { System.out.println("\n數據數組:"); Stream.of(data).forEach(element -> System.out.print(element + " ")); System.out.println(); System.out.println("數據存放:"); Stream.of(store).forEach(element -> System.out.print(element + " ")); System.out.println(); System.out.println("組成該目標值的數為:"); for (int i = 0; i < store.length; i++) { if (store[i] == 1) { System.out.print(data[i] + " "); } } System.out.println(); } public static void main(String[] args) { // 初始化數據 initData(); // 回溯查找 backtrack(0); // 輸出 print(); } }
(4)輸入輸出:

請輸入目標值: 10 請輸入數據個數: 5 請輸入各個數: 2 2 6 5 4 數據數組: 2 2 6 5 4 數據存放: 1 1 1 0 0 組成該目標值的數為: 2 2 6
(5)總結:子集和同樣也完全體現了回溯法中子集樹的核心思想,時間復雜度 O(2n) ,通過存與不存,判斷是否剪枝,進入深度搜索一個解,一旦搜索到一個解直接返回即可;
建議:若肉眼看不太懂,可以在紙上根據我的代碼思路,畫一畫走一遍求解的流程,便於理解代碼,掌握回溯法子集樹的核心思想;