实验III: 背包问题求解
本次实验拟解决生活中常见的问题之一:背包问题。该问题要求在一个物品集合中选择合适的物品放入背包,在放入背包中的物品总重量不超过背包容量的前提下,希望放入背包的物品总价值最大。根据是否允许部分物品放入背包的要求,背包问题可以分为分数背包问题和0-1背包问题。
对于分数背包问题,可以通过设计贪心算法得到问题实例的最优解。对于0-1背包问题,该问题已经被证明为NP-Hard,即不存在多项式时间算法那求解,但可以通过贪心算法得到问题的近似解,或者通过蛮力法、动态规划法得到问题的最优解。本次实验需要学生根据所给问题限制条件采取有效算法解决背包问题,并能分析各个算法所使用的算法设计技术和时间复杂度。下列基本要求必须完成:
- 设计一个交互界面(例如菜单)供用户选择,如果可能,最好是一个图形化用户界面;
- 能够人工输入一个背包问题具体实例,涉及物品个数、每个物品的重量和价值,以及背包容量;
- 设计一个贪心算法求解分数背包问题给定实例的最优解,并分析算法的时间复杂度;
- 设计一个贪心算法求解0-1背包问题给定实例的近似解,请提供一个反例判断该算法不能总是能够给出最优解,并证明算法的解和最优解的值的比值大于等于1/2。
- 设计一个蛮力法算法求解0-1背包问题给定实例的最优解,并分析算法的时间复杂度;
- 设计一个动态规划算法求解求解0-1背包问题给定实例的最优解,并分析算法的时间复杂度;
- 使用记忆功能改进6中的动态规划算法,尽量避免不必要的填表计算。
- 对比以上方法的执行效率,是否可以得到最优解。
Bagquestion .java
package com;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Scanner;
import javax.swing.JOptionPane;
import javax.swing.JTextArea;
public class Bagquestion {
public void choose(int num) {
// Scanner scanner = new Scanner(System.in);
// 输入物品个数
// System.out.print("请输入物品个数:");
// int itemCount = scanner.nextInt();
int itemCount = Integer.parseInt(JOptionPane.showInputDialog("请输入物品个数:"));
// 输入每个物品的重量和价值
// 输入每个物品的重量和价值
int[][] items = new int[itemCount][2];
JTextArea textArea = new JTextArea(10, 20);
JOptionPane.showOptionDialog(null, textArea, "请输入每个物品的重量和价值,换行继续", JOptionPane.OK_CANCEL_OPTION, JOptionPane.INFORMATION_MESSAGE, null, null, null);
String inputs = textArea.getText();
String[] inputsArray = inputs.split("\n");
for (int i = 0; i < itemCount; i++) {
String[] itemValues = inputsArray[i].split(" ");
items[i][0] = Integer.parseInt(itemValues[0]);
items[i][1] = Integer.parseInt(itemValues[1]);
}
// 输入背包容量
int capacity = Integer.parseInt(JOptionPane.showInputDialog("请输入背包容量:"));
// scanner.close();
switch(num) {
case 1:
//贪心分数背包
double maxTotalValue = fractionalKnapsack(items, capacity);
JOptionPane.showMessageDialog(null, "贪心分数背包问题的最优解为:" + maxTotalValue, "结果", JOptionPane.INFORMATION_MESSAGE);
break;
case 2:
//贪心01背包
int maxTotalValue1 = knapsackApproximation(items, capacity);
JOptionPane.showMessageDialog(null, "贪心0-1背包问题的近似解为:" + maxTotalValue1, "结果", JOptionPane.INFORMATION_MESSAGE);
break;
case 3:
//蛮力01背包
int maxTotalValue2 = knapsackBruteForce(items, capacity);
JOptionPane.showMessageDialog(null, "蛮力0-1背包问题的最优解为:" + maxTotalValue2, "结果", JOptionPane.INFORMATION_MESSAGE);
break;
case 4:
//动态01背包
// 创建背包问题实例
KnapsackProblem knapsackProblem = new KnapsackProblem(itemCount, items, capacity);
// 进行背包问题求解
int result = knapsackProblem.DTsuanfa(itemCount, capacity);
// 输出结果
JOptionPane.showMessageDialog(textArea, "动态0-1背包问题的最优解为:" + result, "结果", JOptionPane.INFORMATION_MESSAGE);
break;
case 5:
//记忆功能的动态规划
int maxTotalValue3 = knapsackDPWithMemoization(items, capacity);
JOptionPane.showMessageDialog(null, "记忆0-1背包问题的最优解为:" + maxTotalValue3, "结果", JOptionPane.INFORMATION_MESSAGE);
break;
}
}
//贪心分数背包 O(n^2)
//给定一组物品(每个物品有特定的重量和价值)和一个背包的容量限制下,最大化背包中物品的总价值
public static double fractionalKnapsack(int[][] items, int capacity) {
int itemCount = items.length;
// 计算每个物品的单位重量价值
double[][] itemValuePerWeight = new double[itemCount][2];
for (int i = 0; i < itemCount; i++) {
itemValuePerWeight[i][0] = i; //排序物体
itemValuePerWeight[i][1] = (double) items[i][1] / items[i][0]; // 单位重量价值 = 物品价值 / 物品重量
}
// 根据单位重量价值进行降序排序
// 使用冒泡排序 O(n^2)
for (int i = 0; i < itemCount - 1; i++) {
for (int j = 0; j < itemCount - i - 1; j++) {
if (itemValuePerWeight[j][1] < itemValuePerWeight[j + 1][1]) {
double tempValue = itemValuePerWeight[j][1];
itemValuePerWeight[j][1] = itemValuePerWeight[j + 1][1];
itemValuePerWeight[j + 1][1] = tempValue;
int tempIndex = (int) itemValuePerWeight[j][0];
itemValuePerWeight[j][0] = itemValuePerWeight[j + 1][0];
itemValuePerWeight[j + 1][0] = tempIndex; //获取当前物品的索引
}
}
}
double maxTotalValue = 0;
int remainingCapacity = capacity;
// 逐个选取物品放入背包
for (int i = 0; i < itemCount; i++) {
//根据排序后的itemValuePerWeight数组,获取当前物品的索引和单位重量价值
int currentItemIndex = (int) itemValuePerWeight[i][0];
double currentItemValuePerWeight = itemValuePerWeight[i][1];
if (remainingCapacity >= items[currentItemIndex][0]) {
// 放入整个物品
//如果背包的剩余容量大于等于当前物品的重量,将整个物品放入背包,更新最大总价值和剩余容量
maxTotalValue += items[currentItemIndex][1];
remainingCapacity -= items[currentItemIndex][0];
} else {
// 放入部分物品
//计算放入部分物品的价值,通过剩余容量和单位重量价值的比例,乘以当前物品的单位重量价值
maxTotalValue += currentItemValuePerWeight * remainingCapacity;
break;
}
}
return maxTotalValue;
}
//贪心01背包 O(n)
//按照单位重量价值对物品进行排序,然后贪婪地选择物品放入背包,直到背包满为止
public static int knapsackApproximation(int[][] items, int capacity) {
// 按照单位价值进行排序
sortItemsByValuePerWeight(items);
int totalValue = 0;
int remainingCapacity = capacity;
// 逐个选择物品放入背包
for (int[] item : items) {
if (remainingCapacity >= item[0]) { //如果当前物品的重量小于等于背包剩余容量,将整个物品放入背包,更新总价值和剩余容量
// 放入整个物品
totalValue += item[1];
remainingCapacity -= item[0]; //更新总价值和剩余容量
} else {
// 放不下整个物品,跳出循环
break;
}
}
return totalValue;//返回总价值作为背包问题的近似解
}
// 按照单位价值进行排序 O(n log n)
// 实现了按单位价值对物品进行排序的比较器(Comparator)
// 它计算每个物品的单位价值,然后使用Double.compare方法进行比较,确保按照单位价值的降序进行排序
private static void sortItemsByValuePerWeight(int[][] items) {
Arrays.sort(items, new Comparator<int[]>() {
public int compare(int[] item1, int[] item2) {
double valuePerWeight1 = (double) item1[1] / item1[0];
double valuePerWeight2 = (double) item2[1] / item2[0];
return Double.compare(valuePerWeight2, valuePerWeight1);
}
});
}
//蛮力01背包 O(2^n)
public static int knapsackBruteForce(int[][] items, int capacity) {
int itemCount = items.length;
int maxTotalValue = 0;
// 枚举所有可能的物品组合 二进制
for (int i = 0; i < (1 << itemCount); i++) { //2的itemCount次方 要么在背包中(选择它),要么不在背包中(不选择它)
int totalWeight = 0;
int totalValue = 0;
for (int j = 0; j < itemCount; j++) {
if ((i & (1 << j)) != 0) { //检查每个物品是否被选择在当前组合中 i和j是否都为1
totalWeight += items[j][0];
totalValue += items[j][1];//如果第j个物品在当前组合中,那么将其重量和价值分别加到totalWeight和totalValue中
}
}
// 检查组合是否符合背包容量要求并更新最优解
//检查当前组合的总重量是否不超过背包的容量,以及当前组合的总价值是否大于之前找到的最大总价值
//如果这两个条件都满足,那么更新maxTotalValue为当前组合的总价值
if (totalWeight <= capacity && totalValue > maxTotalValue) {
maxTotalValue = totalValue;
}
}
return maxTotalValue;
}
//记忆功能的动态规划算法
//memo 用于存储已经计算过的子问题的解 O(itemCount * capacity)
public static int knapsackDPWithMemoization(int[][] items, int capacity, int[][] memo,int itemCount) {
//检查 memo 数组中是否已经存储了当前子问题的解
//如果是,则直接返回存储的解
if (memo[itemCount][capacity] != -1) {
return memo[itemCount][capacity];
}
// 基本情况:没有物品或背包容量为0
if (itemCount == 0 || capacity == 0) {
memo[itemCount][capacity] = 0;
return 0;
}
int weight = items[itemCount - 1][0];
int value = items[itemCount - 1][1];
// 考虑最后一个物品是否放入背包中
//如果最后一个物品的重量大于背包的当前容量,则它不能放入背包中
//递归调用 knapsackDPWithMemoization 方法来求解不包含最后一个物品的子问题,并将结果存储在 memo 数组中
if (weight > capacity) {
// 最后一个物品放不进背包中
memo[itemCount][capacity] = knapsackDPWithMemoization(items, capacity, memo,itemCount-1);
return memo[itemCount][capacity];
} else {
//可以考虑将最后一个物品放入或不放入背包中
//分别递归计算这两种情况下的最大价值,然后取两者中的较大值,并将其存储在 memo 数组中
// 最后一个物品放入背包或不放入背包,取较大值
int include = value + knapsackDPWithMemoization(items, capacity - weight, memo,itemCount-1);
int exclude = knapsackDPWithMemoization(items, capacity, memo,itemCount-1);
memo[itemCount][capacity] = Math.max(include, exclude);
return memo[itemCount][capacity]; //返回存储在 memo 数组中的当前子问题的解
}
}
//用于初始化记忆化数组并调用递归方法来求解0-1背包问题
public static int knapsackDPWithMemoization(int[][] items, int capacity) {
int itemCount = items.length;
//初始化一个 (itemCount + 1) × (capacity + 1) 大小的二维数组
int[][] memo = new int[itemCount + 1][capacity + 1];
// 初始化记忆数组为-1
for (int i = 0; i <= itemCount; i++) {
for (int j = 0; j <= capacity; j++) {
memo[i][j] = -1;
}
}
return knapsackDPWithMemoization(items, capacity, memo,itemCount);
}
}
//动态规划01背包问题 O(2^n * m) 其中 n 是物品的数量,m 是背包的容量。这是因为对于每个物品,我们都有两种选择:装入背包或不装入背包
class KnapsackProblem {
private int itemCount; // 物品个数i
private int[][] items; // 物品的重量和价值 wi vi
private int capacity; // 背包容量 j
public KnapsackProblem(int itemCount, int[][] items, int capacity) {
this.itemCount = itemCount;
this.items = items;
this.capacity = capacity;
}
public int DTsuanfa(int i, int j) {
if (i == 0 || j == 0) {
return 0;
}
// 如果物品的重量大于背包容量,则跳过该物品
if (items[i-1][0] > j) {
return DTsuanfa(i-1, j);
}
return Math.max(DTsuanfa(i-1, j), items[i-1][1] + DTsuanfa(i-1, j - items[i-1][0]));
}
}
UI.java
package com;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class UI {
public static void main(String[] args) {
UI.ViewUI();
}
public static void ViewUI() {
// 创建用户界面
JFrame frame = new JFrame("UI界面");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(400, 300);
frame.setLocation(600, 300);
// 创建背景图片组件
JLabel backgroundImageLabel = new JLabel(new ImageIcon("E:\\androidTXT\\Bagquestion\\src\\com\\bc.jpg"));
backgroundImageLabel.setBounds(0, 0, frame.getWidth(), frame.getHeight());
// 创建内容面板,并设置布局为绝对布局
JPanel contentPane = (JPanel) frame.getContentPane();
contentPane.setLayout(null);
// 添加背景图片组件到内容面板
contentPane.add(backgroundImageLabel);
JMenuBar menuBar = new JMenuBar();
JMenu searchMenu = new JMenu("背包算法选择");
JMenu TXsuanfa = new JMenu("贪心算法");
JMenu MLsuanfa = new JMenu("蛮力算法");
JMenu DTsuanfa = new JMenu("动态规划算法");
JMenu GJDTsuanfa = new JMenu("改进动态规划算法");
JMenuItem ML01 = new JMenuItem("0-1背包问题");
JMenuItem TX01 = new JMenuItem("0-1背包问题");
JMenuItem TXfenshu = new JMenuItem("分数背包问题");
JMenuItem DT01 = new JMenuItem("0-1背包问题");
JMenuItem GJDT01 = new JMenuItem("0-1背包问题");
//菜单项添加
TXsuanfa.add(TXfenshu);
TXsuanfa.add(TX01);
MLsuanfa.add(ML01);
DTsuanfa.add(DT01);
GJDTsuanfa.add(GJDT01);
//菜单添加
searchMenu.add(TXsuanfa);
searchMenu.add(MLsuanfa);
searchMenu.add(DTsuanfa);
searchMenu.add(GJDTsuanfa);
TXfenshu.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
Bagquestion v = new Bagquestion();
v.choose(1);
}
});
TX01.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
Bagquestion v = new Bagquestion();
v.choose(2);
}
});
ML01.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
Bagquestion v = new Bagquestion();
v.choose(3);
}
});
DT01.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
Bagquestion v = new Bagquestion();
v.choose(4);
}
});
GJDT01.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
Bagquestion v = new Bagquestion();
v.choose(5);
}
});
menuBar.add(searchMenu);
frame.setJMenuBar(menuBar);
frame.setVisible(true);
}
}