動態規劃實例(十四):劃分問題

    劃分問題是指,有一個集合,判斷是否可以把這個結合劃分爲總和相等的兩個集合。
    例如:
        arr[] = {1, 5, 11, 5}
        Output: true
    這個數組可以劃分爲: {1, 5, 5} 和 {11}
       arr[] = {1, 5, 3}
       Output: false
    無法劃分爲總和相等的兩部分
    如果劃分後的兩個集合總和相等,則原集合的總和肯定爲偶數,假設爲總和爲sum。問題即爲是否有子集合的總和爲sum/2.
    遞歸解決
    設函數 isSubsetSum(arr, n, sum/2) 返回true如果存在arr的一個子集合的總和爲 sum/2,isSubsetSum函數爲分爲下面兩個子問題
         1) 不考慮最後一個元素。問題遞歸到 isSubsetSum(arr, n-1. sum/2)
         2) 考慮最後一個元素。問題遞歸到 isSubsetSum(arr, n-1. sum/2-arr[n])
    上面兩種情況有一個返回TRUE即可
        isSubsetSum (arr, n, sum/2) = isSubsetSum (arr, n-1, sum/2) ||

        isSubsetSum (arr, n-1, sum/2 – arr[n-1])

具體實例及實現代碼如下所示:

/**
 * @Title: Partition.java
 * @Package dynamicprogramming
 * @Description: TODO
 * @author peidong
 * @date 2017-6-12 上午9:12:18
 * @version V1.0
 */
package dynamicprogramming;

/**
 * @ClassName: Partition
 * @Description: 劃分問題
 * @date 2017-6-12 上午9:12:18
 *
 */
public class Partition {

    /**
     *
     * @Title: isSubsetSum
     * @Description: 判斷數組是否可劃分
     * @param arr  數組
     * @param n    數組長度
     * @param sum  數組和
     * @return
     * @return boolean
     * @throws
     */
    public static boolean isSubsetSum(int[] arr, int n, int sum){
        //邊界條件判斷
        if(sum == 0)
            return true;
        if( n == 0 && sum!= 0)
            return false;
        //如果最後一個元素比sum大,就不考慮該元素
        if(arr[n-1] > sum)
            return isSubsetSum(arr, n-1, sum);

        //分別判斷包括最後一個元素和不包括最後一個元素
        return isSubsetSum(arr, n-1, sum) || isSubsetSum(arr, n-1, sum - arr[n-1]);

    }

    /**
     *
     * @Title: findPartitionRecursion
     * @Description: 利用遞歸求解數組劃分問題
     * @param arr 數組名
     * @param n   數組長度
     * @return
     * @return boolean
     * @throws
     */
    public static boolean findPartitionRecursion(int[] arr, int n){
        int sum = 0;
        for(int i = 0; i < n; i++){
            sum += arr[i];
        }
        //sum爲奇數
        if(sum%2 != 0)
            return false;
        return isSubsetSum(arr, n, sum/2);
    }

    //時間複雜度:最快情況爲 O(2^n),即每個元素有選或不選的兩種選擇

    public static boolean findPartition(int[] arr, int n){
        int sum = 0;
        for(int i = 0; i < n; i++){
            sum += arr[i];
        }
        if(sum %2 != 0)
            return false;
        //創建狀態轉移矩陣
        boolean[][] tc = new boolean[sum/2 + 1][n+1];

        //初始化狀態轉移矩陣
        for(int i = 0; i <= n; i++){
            tc[0][i] = true;
        }
        for(int i = 1; i <= sum/2; i++){
            tc[i][0] = false;
        }

        //構建狀態轉移矩陣
        for(int i = 1; i <= sum/2; i++){
            for(int j = 1; j <=n; j++){
                tc[i][j] = tc[i][j-1];
                if(i >= arr[j-1])
                    tc[i][j] = tc[i][j] || tc[i-arr[j-1]][j-1];
            }
        }

        //打印狀態轉移矩陣
        for(int i = 0; i < sum/2; i++){
            for(int j = 0; j <= n; j++){
                System.out.print(tc[i][j] + " ");
            }
            System.out.println();
        }
        return tc[sum/2][n];
    }
    /**
     * @Title: main
     * @Description: 測試用例
     * @param args
     * @return void
     * @throws
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub

        int[] arr = {3, 1, 5, 9, 10};
        int n = arr.length;
        if(findPartitionRecursion(arr, n) == true)
            System.out.println("數組能夠被劃分");
        else
            System.out.println("數組不能被劃分");
        if(findPartition(arr, n) == true)
            System.out.println("dp數組能夠被劃分");
        else
            System.out.println("dp數組不能被劃分");

    }

}
//時間複雜度爲 O(sum*n)

發佈了110 篇原創文章 · 獲贊 65 · 訪問量 16萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章