201871010129-郑文潇 实验二 个人项目—《D{0-1}背包问题 》项目报告
项目 | 内容 |
---|---|
课程班级博客链接 | 课程链接 |
这个作业要求链接 | [作业要求]( https://www.cnblogs.com/nwnu-daizh/p/14552393.html) |
我的课程学习目标 | 1、掌握软件项目个人开发流程; 2、掌握Github发布软件项目的操作方法。 |
这个作业在哪些方面帮助我实现学习目标 | 1、总结《构建之法》第1章、第2章,掌握PSP流 2、开发个人项目,并掌握背包问题。 |
项目Github的仓库链接地址 | 我的Github |
任务一:
已按照要求对几位优秀的1同学进行了评论
任务二:
详细阅读《构建之法》第1章、第2章,掌握PSP流程
-
通过阅读《构建之法》后,学习了第1章主要讲的开发的不同阶段:玩具阶段、业余爱好阶段、探索阶段、成熟的产业阶段;软件工程的定义是:把系统的、有序的、可量化的方法应用到软件的开发、运营和维护上的过程;软件的特殊性:复杂性、不可见性、易变性、服从性、非连续性。计算基础、数学基础、工程基础是软件工程的三大类基础知识领域;我了解到软件工程真正的样子是软件=程序+软件工程。软件工程不仅仅是简单的软件开发的过程。
-
第二章主要讲了单元测试、回归测试、效能分析、个人软件开发流程。这一章讲述了一个好的软件工程师应该怎样合理分析软件错误并明确各个模块功能和提高效能还有如何管理自己的源代码。并且这一章还给了我们锻炼编程基本功的方法和如何实践的过程。学习结束之后对 PSP 有了相关重点的总结如下:
-
软件工程师的成长
-
知识
-
经验,
-
通用的软件设计思想,软件工程思想的提高
-
职业技能
PSP的特点
- 不局限于某一种软件技术(如编程语言),而是着眼于软件开发的流程,这样,开发不同应用的软件工程师可以互相比较。
- 不依赖于考试,而主要靠工程师自己收集数据,然后分析,提高。
- 在小型、初创的团队中,很难找到高质量的项目需求,这意味着给程序员的输入质量不高。在这种情况下,程序员的输出(程序/软件)往往质量也不高,然而这并不能全部由程序员负责。
- PSP依赖于数据。
- 需要工程师输入数据,记录工程师的各项活动,这本身就需要不小的时间代价。
- PSP的目的是记录工程师如何实现需求的效率,而不是记录顾客对产品的满意度。)
-
任务三:
项目开发背景:
-
背包问题(Knapsack Problem,KP)是NP Complete问题,也是一个经典的组合优化问题,有着广泛而重要的应用背景。{0-1}背包问题({0-1 }Knapsack Problem,{0-1}KP)是最基本的KP问题形式,它的一般描述为:从若干具有价值系数与重量系数的物品(或项)中,选择若干个装入一个具有载重限制的背包,如何选择才能使装入物品的重量系数之和在不超过背包载重前提下价值系数之和达到最大?
-
D{0-1} KP 是经典{ 0-1}背包问题的一个拓展形式,用以对实际商业活动中折扣销售、捆绑销售等现象进行最优化求解,达到获利最大化。D{0-1}KP数据集由一组项集组成,每个项集有3项物品可供背包装入选择,其中第三项价值是前两项之和,第三项的重量小于其他两项之和,算法求解过程中,如果选择了某个项集,则需要确定选择项集的哪个物品,每个项集的三个项中至多有一个可以被选择装入背包,D{0-1} KP问题要求计算在不超过背包载重量 的条件下,从给定的一组项集中选择满足要求装入背包的项,使得装入背包所有项的价值系数之和达到最大;D{0-1}KP instances数据集是研究D{0-1}背包问题时,用于评测和观察设计算法性能的标准数据集;动态规划算法、回溯算法是求解D{0-1}背包问题的经典算法。查阅相关资料,设计一个采用动态规划算法、回溯算法求解D{0-1}背包问题的程序。
动态规划就是通过采用递推(或者分而治之)的策略,通过解决大问题的子问题从而解决整体的做法。动态规划的核心思想是巧妙的将问题拆分成多个子问题,通过计算子问题而得到整体问题的解。而子问题又可以拆分成更多的子问题,从而用类似递推迭代的方法解决要求的问题。基本思想:若要解一个给定问题,我们需要解其不同部分(即子问题),再合并子问题的解以得出原问题的解。 通常许多子问题非常相似,为此动态规划法试图仅仅解决每个子问题一次(将结果存储起来),从而减少计算量: 一旦某个给定子问题的解已经算出,则将其记忆化存储,以便下次需要同一个子问题解之时直接查表。 这种做法在重复子问题的数目关于输入的规模呈指数增长时特别有用。 -
回溯法(探索与回溯法)是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。
需求分析:
- 生活中的很多问题,都需要我们得出最优解,列举法是一种比较慢的办法,在算最优解的时候,所以我们可以采用动态规划算法,在基于{0,1}背包文题的算法基础中,对实际的问题求出最优解。如在商场折扣销售活动中,设计一个项目,使得用户能够清楚地知道怎样购物才能最经济实惠,能够读取文件,表示出以重量为横轴,价值为纵轴的数据散点图。
功能设计:
1.可正确读入实验数据文件的有效D{0-1}KP数据;
2.能够绘制任意一组D{0-1}KP数据以重量为横轴、价值为纵轴的数据散点图;
3.能够对一组D{0-1}KP数据按项集第三项的价值:重量比进行非递增排序;
4.用户能够自主选择动态规划算法、回溯算法求解指定D{0-1} KP数据的最优解和求解时间(以秒为单位);
5.任意一组D{0-1} KP数据的最优解、求解时间和解向量可保存为txt文件或导出EXCEL文件。
重要代码:
读取文件:
# =========================数据定义开始============================
# 三维列表,存放整个文件各组数据价值的列表,该列表分为若干子列表,每个子列表用于存储一组价值数据,每个子列表的数据又按照三个一组分为若干个列表
global profit
profit = []
# 三维列表,存放整个文件各组数据重量的列表,同profit
global weight
weight = []
# 三维列表,存放整个文件各组数据价值-重量-价值重量比的列表,该列表分为若干子列表,每个子列表用于存储一组价值-重量-价值重量比数据,每个子列表
# 的数据为一个九元组,包含三条价值数据-三条重量数据-三条价值重量比信息
global prowei
prowei = []
# 存放价值初始数据,即刚读入并且仅对结尾做了处理的字符串
global profitData
profitData = []
# 存放重量初始数据,即刚读入并且仅对结尾做了处理的字符串
global weightData
weightData = []
# =========================数据定义结束============================
# =======================文件读取和处理函数=========================
def getData():
# -------打开指定文件,读入数据-------
fileName = str(input('请输入文件名'))
file = open(fileName, 'r')
line = file.readline()
while (line):
# 读入一行数据
line = file.readline()
# 如果匹配到profit关键字,则读入下一行的价值信息
if line.__contains__("profit"):
# 去除结尾的换行符,逗号,原点,便于分割
line = file.readline().strip('\n').strip('.').strip(',')
# 将该行数据存入列表
profitData.append(line)
# 如果匹配到weight关键字,则读入下一行的重量信息
elif line.__contains__("weight"):
# 去除结尾的换行符,逗号,原点,便于分割
line = file.readline().strip('\n').strip('.').strip(',')
# 将该行数据存入列表
weightData.append(line)
# ------------数据读取完成---------------
# ------------profitData存放初始价值信息---------------
# ------------weightData存放初始重量信息---------------
# 处理数据,外层遍历profitData和weightData的每一组数据,将profitData和weightData的数据进一步划分为三元组和九元组
for group in range(len(profitData)):
# 临时数据,价值三元组
three_P_List = []
# 临时数据,重量三元组
three_W_List = []
# 临时数据,价值重量比三元组
three_PW_List = []
# 存放一组价值数据
group_P_List = []
# 存放一组重量数据
group_W_List = []
# 存放一组价值+重量构成的数据
group_PW_List = []
# 临时变量,计数器
n = 0
# 将每一组价值/重量数据按照逗号分组,两个列表分别用于存放每一组价值/重量数据分组后的结果
proList = str(profitData[group]).split(',')
weiList = str(weightData[group]).split(',')
# 内层循环遍历上述分组后的每一组数据,将每组数据按照三元组/九元组进行存储
for p in range(len(proList)):
# 将该组价值/重量/价值重量比数据的一个放入三元组列表
three_P_List.append(int(proList[p]))
three_W_List.append(int(weiList[p]))
three_PW_List.append(int(proList[p]) / int(weiList[p]))
# 三元组中数量+1
n = n + 1
# 如果三元组已有三条数据
if n == 3:
# 将价值/重量三元组放入该组列表
group_P_List.append(three_P_List)
group_W_List.append(three_W_List)
# 构造九元组,并将价值-重量-价值重量比九元组放入该组列表
group_PW_List.append(three_P_List + three_W_List + three_PW_List)
# 将三个临时三元组/九元组变量置空,为下一次做准备
three_P_List = []
three_W_List = []
three_PW_List = []
# 计数器置0
n = 0
# 将内层循环处理完成的一组数据(列表)放入最终结果列表
profit.append(group_P_List)
weight.append(group_W_List)
prowei.append(group_PW_List)
return 'ok'
# ==========================文件读取和处理函数结束========================
回溯法:
#include<stdio.h>
#define max 100
int weight[max];
int value[max];
int n,max_weight,max_value;
int best_answer[max],answer[max];
void print()
{
int i,j,k,l;
printf("最大容量为:%d\n",max_value);
printf("结果为:");
for(i=1;i<=n;i++)
printf("%d ",best_answer[i]);
printf("\n");
}
void DFS(int level,int current_weight,int current_value)
{
if(level>=n+1)
{
if(current_value>max_value)
{
int i;
max_value = current_value;
for(i=1;i<=n;i++)
best_answer[i] = answer[i];
}
} else {
if(current_weight>=weight[level+1])
{
current_weight = current_weight - weight[level+1];
current_value = current_value + value[level+1];
answer[level+1] = 1;
DFS(level+1,current_weight,current_value);
answer[level+1] = 0;
current_weight = current_weight + weight[level+1];
current_value = current_value - value[level+1];
}
DFS(level+1,current_weight,current_value);
}
}
void init()
{
int i,j,k,l;
max_value = 0;
for(i=1;i<=n;i++)
answer[i] = 0;
}
/*
3 30
16 15 15
45 25 25
5 10
2 2 6 5 4
6 3 5 4 6
*/
int main()
{
int i,j,k,l;
while(scanf("%d%d",&n,&max_weight)!=EOF)
{
for(i=1;i<=n;i++)
scanf("%d",&weight[i]);
for(j=1;j<=n;j++)
scanf("%d",&value[j]);
init();
DFS(0,max_weight,0);
print();
}
return 0;
}
散点图:
import numpy as np
import matplotlib.pyplot as plt
# 在区间 [a, b] 上均匀地取指定数量的值
x = np.linspace(0.05, 10, 1000)
np.random.seed(22)
y = np.random.randn(1000)
# 图表元素中正常显示中文字符
plt.rcParams['font.sans-serif'] = 'SimHei'
# 坐标轴刻度标签正常显示负号
plt.rcParams['axes.unicode_minus'] = False
plt.scatter(x, y,
s=87, # 标记点大小
marker='*', # 标记点的样式 星号
c='g', # green 绿色
linewidths=0.41,
edgecolor='y', # 边缘颜色
label='scatter figure')
plt.xticks(size=12, color='grey') # x 轴刻度标签
plt.yticks(size=12, color='grey') # y 轴刻度标签
plt.title('散点图', size=14, color='y'); # 添加图表标题
plt.legend() # 添加图例
# 设置坐标轴刻度范围
plt.xlim(-0.5, 10.5)
plt.ylim(-3.5, 3.5);
动态规划:
static void Main(string[] args)
{
//记录重量的数组,物品有3种重量,分别是3、4、5
int[] w = { 0, 3, 4, 5 };
//记录物品价值的数组,和重量数组对应,物品价值分别是4、5、6
int[] p = { 0, 4, 5, 6 };
//调用Exhaustivity函数得到7kg的背包放置物品的最大价值
Console.WriteLine(Exhaustivity(9 , w, p));
Console.ReadKey();
}
/// <summary>
/// 计算给定的mkg背包放置物品的最大价值
/// </summary>
/// <param name="m">给定的物品重量</param>
/// <param name="w">记录所有物品重量的数组</param>
/// <param name="p">记录所有物品对应价格的数组</param>
/// <returns>背包中放置物品的最大价值</returns>
public static int Exhaustivity(int m,int[] w,int[] p)
{
//记录放入物品的最大的价格
int maxPrice = 0;
//有3个物品,外层就循环2的3次方,即i值从0取到7,i的二进制数字最多3位,每一个i的取值的二进制数字都对应3个物品是否放入背包
//如i为6,二进制为110,代表取前两个物品,不取第3个物品
//又如i为0,二进制为000,代表3个物品均不取;i为1,二进制001,代表只取第3个物品
//通过这种方式穷举所有物品放入背包的情况,然后判断每一种情况重量是否满足要求,再比较价格
for(int i = 0;i < Math.Pow(2,w.Length - 1); i++)
{
//记录所有被放入背包物品的总重量
int weightTotal = 0;
//记录所有被放入背包物品的总价格
int priceTotal = 0;
//内层循环负责计算并比较所有物品的总重量和总价格
//内层循环的次数就是物品个数,然后分别判断当前情况下每个物品是否放入背包,j代表第几个物品
for(int j = 1;j <= w.Length - 1; j++)
{
//计算当前物品是否放入背包
int result = Get2(i, j);
//在放入背包的情况下,将重量和价格累加到总重量和总价格上
if(result == 1)
{
weightTotal += w[j];
priceTotal += p[j];
}
}
//判断是否超重,不超重的情况下判断价格是不是大于最大价格,如果是更新最大价格
if (weightTotal <= m && priceTotal > maxPrice)
maxPrice = priceTotal;
}
//返回最大价格
return maxPrice;
}
/// <summary>
/// 通过按位与运算得到给定物品number在给定放入情况i中是否放入背包
/// </summary>
/// <param name="i">给定的物品放入情况</param>
/// <param name="number">给定的物品编号</param>
/// <returns></returns>
public static int Get2(int i,int number)
{
int A = i;
int B = (int)Math.Pow(2,number - 1);
int result = A & B;
if (result == 0)
return 0;
return 1;
}
psp:
PSP2.1 | 任务内容 | 计划共完成需要的时间(min) | 实际完成需要的时间(min) |
---|---|---|---|
Planning | 计划 | 10 | 10 |
•Estimate | • 估计这个任务需要多少时间,并规划大致工作步骤 | 10 | 10 |
Development | 开发 | 300 | 295 |
••Analysis | 需求分析 (包括学习新技术) | 10 | 15 |
•Design Spec | • 生成设计文档 | 5 | 7 |
•Design Review | • 设计复审 (和同事审核设计文档) | 5 | 7 |
•Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 5 | 6 |
•Design | 具体设计 | 10 | 10 |
•Coding | 具体编码 | 200 | 220 |
•Code Review | • 代码复审 | 5 | 10 |
•Test | • 测试(自我测试,修改代码,提交修改) | 15 | 20 |
Reporting | 报告 | 13 | 15 |
••Test Report | • 测试报告 | 4 | 3 |
•Size Measurement | 计算工作量 | 4 | 4 |
•Postmortem & Process Improvement Plan | • 事后总结 ,并提出过程改进计划 | 5 | 8 |