动态规划实例(十四):划分问题

来源:互联网 发布:默沙东临床数据管理员 编辑:程序博客网 时间:2024/06/05 11:56
    划分问题是指,有一个集合,判断是否可以把这个结合划分为总和相等的两个集合。
    例如:
        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)

原创粉丝点击