算法设计与分析-实验4 利用动态规划的方法解决子集等和分割判断问题

算法设计与分析-实验4  利用动态规划的方法解决子集等和分割判断问题

一、实验目的

1. 了解动态规划的主要思想。

2. 掌握背包问题解决方法用以解决该问题。

3. 分析核心代码的时间复杂度和空间复杂度。

二、实验内容和要求

题目:给定一个只包含正整数的非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。

注意:

每个数组中的元素不会超过 100

数组的大小不会超过 200

示例 1:

输入: [1, 5, 11, 5]

输出: true

解释: 数组可以分割成 [1, 5, 5] 和 [11].

示例 2:

输入: [1, 2, 3, 5]

输出: false

解释: 数组不能分割成两个元素和相等的子集.

【请说明动态规划算法的核心思想】

动态规划算法核心思想是将待求解问题分解成若干个子问题。

【请说明背包问题与题目之间的关联性在哪】

判断其是否可以分为相等的两部分,看是否能在数组中挑选出和为数组和一半的序列。那么就可以转化为背包问题,在N个数中挑选数字使其和为数组和的一半。

【该判断函数原型及功能说明】

函数原型:

public boolean canPartition(int nums[0..n])
    sum ← 0;
    for i ← 0 to n-1 do sum ← sum + num[i]
    if sum%2 != 0 return false;
    target ← sum / 2;;
    dp[0] ← true;
    for  i ← 0 to n-1 do
        for  j ← target downto num[i] do
            dp[i] ← dp[i] or dp[i - num[i]];
    return dp[target];

功能说明:计算出所有数字之和;将数字之和除以2两个for循环,循环源数组的每个数字,以及遍历当前 剩余的总和的值。借助数组dp保存每次的结果符合与否。

【核心函数实现代码及时间复杂度与空间复杂度分析】

public boolean canPartition(int []nums) {
    int sum = 0;
    for (int num : nums) {
        sum = sum + num;
    }
    if (sum % 2 != 0)
        return false;
    int target = sum / 2;
    boolean[] dp = new boolean[target + 1];
    dp[0] = true;
    for (int num : nums) {
        for (int i = target; i >= num; i--) { //从后向前,先计算dp[i],再计算dp[i-num]
            dp[i] = dp[i] || dp[i - num];
        }
    }
    return dp[target];
}

时间复杂度:O(n)

空间复杂度:O(n)

三、实验总结

本次实验为本学期第四次实验,也是本学期最后的一下实验,实验主题为利用动态规划的方法解决子集等和分割判断问题。任务为将一个数组分割成两个子集,使得两个子集的元素和相等。通过本次实验,我学习到了动态规划的主要思想,学到了如何用背包问题解决方法用以解决该问题以及学到如何分析核心代码的时间复杂度和空间复杂度,为今后得程序开发奠定基础,更有利于今后对算法设计与分析这一门课程的学习。

四、优化及改进(选做)

【提出你觉得解决这个问题更好的算法,并加以说明】

  • 9
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
设计分析: 0-1背包问题是一个经典的NP完全问题,它的求解方法有很多,其中分支限界算法是一种比较有效的解法。该算法的基本思路是,将问题的解空间划分成许多子集,每个子集对应一个候选解,通过对每个候选解进行评估,剪枝和扩展,最终找到最优解。 算法描述: 1. 初始化队列,将根节点入队 2. 当队列不为空时,取出队首节点 3. 对节点进行扩展,生成左右儿子节点 4. 对左右儿子节点进行评估,若可行则更新当前最优解 5. 对当前最优解进行界限,若当前节点无法产生更优的解,则剪枝 6. 将可行的左右儿子节点入队 7. 重复执行2-6步骤,直到队列为空或者找到最优解 程序实现: ``` #include<bits/stdc++.h> using namespace std; struct Node { int cost, profit; // 节点的花费和收益 double bound; // 节点的上界 }; // 用于优先队列的比较函数,按照bound从大到小排序 struct cmp { bool operator() (const Node& a, const Node& b) { return a.bound < b.bound; } }; // 分支限界算法求解0-1背包问题 int knapsack(int n, int C, int* w, int* v) { priority_queue<Node, vector<Node>, cmp> q; // 优先队列 Node u, v; u.cost = u.profit = 0; u.bound = 0; int maxprofit = 0; q.push(u); while (!q.empty()) { u = q.top(); q.pop(); if (u.bound > maxprofit) { v.cost = u.cost + w[u.profit + 1]; v.profit = u.profit + 1; if (v.cost <= C && v.profit > maxprofit) { maxprofit = v.profit; } v.bound = bound(v, n, C, w, v.profit, v.cost); if (v.bound > maxprofit) { q.push(v); } v.cost = u.cost; v.profit = u.profit + 1; v.bound = bound(v, n, C, w, v.profit, v.cost); if (v.bound > maxprofit) { q.push(v); } } } return maxprofit; } // 计算节点的上界 double bound(Node u, int n, int C, int* w, int i, int j) { if (j >= C) { return 0; } double bound = u.profit; int k = i + 1; int totweight = j; while (k <= n && totweight + w[k] <= C) { totweight += w[k]; bound += k->v[k]; k++; } if (k <= n) { bound += (C - totweight) * k->v[k]; } return bound; } int main() { int n, C; cin >> n >> C; int w[n+1], v[n+1]; for (int i = 1; i <= n; i++) { cin >> w[i] >> v[i]; } cout << knapsack(n, C, w, v) << endl; return 0; } ``` 测试分析: 我们可以通过一些实例来测试算法的正确性和效率。例如,对于以下实例: ``` n = 4, C = 8 w = {2, 3, 4, 5} v = {3, 4, 5, 6} ``` 使用分支限界算法求解,得到的最优解为`10`,即选择物品2和3。 总结: 分支限界算法是一种非常实用的求解NP完全问题方法,它通过对问题的解空间进行剪枝和扩展,可以快速找到最优解。在0-1背包问题中,该算法的效果也非常好,可以在较短的时间内求解出最优解。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值