回溯算法 - 子集和問題


(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) ,通過存與不存,判斷是否剪枝,進入深度搜索一個解,一旦搜索到一個解直接返回即可;

  建議:若肉眼看不太懂,可以在紙上根據我的代碼思路,畫一畫走一遍求解的流程,便於理解代碼,掌握回溯法子集樹的核心思想;


免責聲明!

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



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