201971010259-张圆圆 实验二 软件工程个人项目-0-1背包问题
项目 | 内容 |
---|---|
课程班级博客链接 | 2022年春软件工程课程班(2019级计算机科学与技术) |
作业要求链接 | 实验二 软件工程个人项目 |
我的课程学习目标 | * 仔细阅读《构建之法》,学习并掌握软件项目个人开发流程 * 掌握Github发布软件项目的操作方法,并将此次个人项目在GitHub进行托管 |
这个作业在哪些方面帮助我实现学习目标 | * 通过阅读《构建之法》中的相关内容,对PSP流程有了深入的学习和了解 * 通过实验中对0-1背包问题的解决,重新熟悉了利用贪心算法,回溯算法,动态规划算法分别解决0-1背包问题的思想 * 通过将解决0-1背包问题的项目在GitHub进行托管,更加熟悉了GitHub的相关操作 |
项目Github的仓库链接地址 | 0-1背包问题 |
任务1 :阅读教师博客及点评实验一班级同学博客#
- 阅读教师博客“常用源代码管理工具与开发工具”
了解了各种源代码管理工具与开发工具,具体总结博客链接:常用源代码管理工具与开发工具总结 - 点评班级博客
博客链接 | 评论内容展示 |
---|---|
201971010118-梁春云 实验一 软件工程准备-认识及学习软件工程 | 博主所提出的三个问题都在一定的文字资料背景下提出,提出的问题很好,对于问题三,软件开发过程包含很多方面的技术,其时间也比较长,这过程中难免会遇到各种问题,这正是我们所要去学习的。 |
201971010116-姜婷 实验一 软件工程准备-学习历程一 | 博主所写内容充实,所提出的四个问题很不错,并且在博客中插入了一些图片,对于问题四,通过流程图图片的方式进行提问,这个方式值得学习,支持支持!!! |
201971010146-杨凯 实验一 软件工程准备 | 博主提的三个问题很精简,排版短小精悍,看上去很精炼,在排版时相应的更加突出标题,可能会好一点 |
201971010231-毛玉贤 实验一 软件工程准备—基本操作及《构建之法》初步三问 | 博主的这篇博客排版整洁舒适,背景色彩丰富,博客整体很好看,在任务5中提出问题时,所提问题都有理有据,经过深思熟虑所提出的,所罗列出的问题很有条理,很有逻辑,值得人深思,很不错!!! |
任务2:总结详细阅读《构建之法》第1章、第2章,掌握PSP流程#
《构建之法》第1章#
- 软件=程序+软件工程
“程序=数据结构+算法”,程序(算法、数据结构)是基本功,这是众所周知的,但是在算法和数据结构之上,软件工程更为重要,其决定了软件的质量;而对于“软件=程序+软件工程”有一个扩展的推论是:“软件企业=软件+商业模式”,商业模式决定了一个软件企业的成败,软件从业人员和软件企业的道德操守会极大地影响软件用户的利益。 - 软件工程
软件工程是把系统的、有序的、可量化的方法应用到软件的开发、运营和维护上的过程。软件工程包括下列领域:软件需求分析、软件设计、软件构建、软件测试和软件维护。 - 软件工程与计算机科学
软件工程是研究和应用如何以系统性的、规范化的、可定量的过程化方法去开发和维护软件,以及如何把经过时间考验而验证正确的管理技术和当前能够得到的最好的技术方法结合起来的学科,而计算机科学,是研究计算机及其周围各种现象和规律的科学,亦即研究计算机系统结构、程序系统(即软件)、人工智能以及计算本身的性质和问题的学科。 - 软件的特殊性
复杂性、不可见性、易变性、非连续性
《构建之法》第2章#
- 好的单元测试的标准
- 单元测试应该在最基本的功能1参数上验证程序的正确性。
- 单元测试必须由最熟悉代码的人( 程序的作者)来写。
- 单元测试过后,机器状态保持不变。
- 单元测试要快(一个测试的运行时间是几秒钟,而不是几分钟)。
- 单元测试应该产生可重复、一致的结果。
- 独立性一单元测试的运行1通过1失败不依赖于别的测试,可以人为构造数据,以保持单元测试的独立性。
- 单元测试应该覆盖所有代码路径。
- 单元测试应该集成到自动测试的框架中。
- 单元测试必须和产品代码一起保存和维护。
- 回归测试目的
- 验证新的代码的确改正了缺陷。
- 同时要验证新的代码有没有破坏模块的现有功能,有没有Regression。
- 个人开发流程PSP特点
- 不局限于某一种软件技术(如编程语言), 而是着眼于软件开发的流程,开发不同应用的软件工程师可以互相比较。
- 不依赖于考试,而主要靠工程师自己收集数据,然后分析,提高。
- 在小型、初创的团队中,很难找到高质量的项目需求,给程序员的输入质量不高,所以程序员的输出(程序/软件)往往质量也不高。
- PSP依赖于数据,需要工程师输人数据,记录工程师的各项活动,数据可能会有不准确或遗失的情况。
- PSP的目的是记录工程师如何实现需求的效率。
任务3:0-1背包问题项目开发#
背包问题(Knapsack Problem,KP)是NP Complete问题,也是一个经典的组合优化问题,有着广泛而重要的应用背景。{0-1}背包问题({0-1 }Knapsack Problem,{0-1}KP)是最基本的KP问题形式,其一般描述为:从若干具有价值系数与重量系数的物品(或项)中,选择若干个装入一个具有载重限制的背包,如何选择才能使装入物品的重量系数之和在不超过背包载重前提下价值系数之和达到最大。
1.需求分析#
- 读取数据
正确读取实验数据文件的有效{0-1}KP数据; - 绘制散点图
对任意一组{0-1}KP数据绘制以重量为横轴、价值为纵轴的数据散点图 - 排序
对任意一组{0-1}KP数据按重量比进行非递增排序 - 算法求解
用户自主选择贪心算法、动态规划算法、回溯算法求解指定{0-1} KP数据的最优解和求解时间(以秒为单位) - 保存结果
将任意一组{0-1} KP数据的最优解、求解时间和解向量可保存为txt文件
2.功能设计#
该项目开发过程中主要实现了7个功能,具体功能设计如下图所示:
- 读取数据
通过在窗口输入框中输入读取文件数据的功能编号,在弹出框口的单选框中选中所要读取数据的文件名,点击确定按钮即可查看相关数据。 - 贪心算法
在用户选择所要读取的数据后,在窗口输入框中输入贪心算法求解的功能编号,就可对所读取的{0-1} KP数据利用贪心算法求得其解、求解时间和解向量。(贪心算法在求解过程中,所求得解并非最优解) - 回溯算法
在用户选择所要读取的数据后,在窗口输入框中输入回溯算法求解的功能编号,就可对所读取的{0-1} KP数据利用回溯算法求得其最优解、求解时间和解向量。 - 动态规划算法
在用户选择所要读取的数据后,在窗口输入框中输入动态规划算法求解的功能编号,就可对所读取的{0-1} KP数据利用动态规划算法求得其最优解、求解时间和解向量。 - 绘制散点图
选择该功能后,将根据当前所获取的{0-1} KP数据绘制以重量为横轴、价值为纵轴的数据散点图,并对用户通过窗口进行展示。 - 按重量比进行非递增排序
将用户所选择数据经过数学处理(物品价值/物品重量)后,根据所得大小进行非递增排序,并将结果进行展示。 - 保存结果
用户在通过自主选择数据及算法求解0-1背包问题后,可自主选择将其结果以txt文件形式进行保存。
3.设计实现#
* 整体项目结构#
在此次项目开发过程中,主要包含有5个类文件,分别为:Algorithm.java,Frame.java,Main.java,Paint.java,Goods.java,如下图所示:

- Algorithm.java
主要实现了在求解0-1背包问题中所用到的贪心算法,回溯算法,动态规划算法,还实现了通过插入排序对物品按重量比进行非递增排序。 - Frame.java
主要实现了运行窗口界面的布局设置,实现了用户可根据不同的功能编号选择所需要的功能进行操作。 - Main.java
项目开发过程中的测试类,对已完成功能进行测试运行。 - Paint.java
主要实现了散点图的绘制,通过重写paintComponent方法,根据用户所选择数据绘制相应的散点图并进行显示。 - Goods.java
物品实体类,在该类中定义了物品的两个属性,分别为重量weight和价值value。
* 项目整体系统流程图#
- 核心函数介绍
- 贪心算法-函数Greedy( )
为了求解该背包问题,我们首先计算每个商品的单位重量价值比(v/w),将物品按照其单位重量价值比来进行排序,遵循贪心策略,首先尽量多地拿走单位重量价值比最高的商品,如果该商品已全部拿走而背包未装满,则继续尽量多地拿走单位重量价值比第二高的商品,依次类推,直到达到重量上限W,背包已无法容纳其他物品,则可得到解,但所得解不是最优解,因为它无法保证最终能将背包装满,部分闲置的背包空间使每公斤背包空间的价值降低了,其算法流程图如下所示:
- 贪心算法-函数Greedy( )

- 回溯算法-函数backtrack( )
利用回溯法解决0-1背包,属于找最优解问题,需要构造解的子集树。对于每一个物品i,对于该物品只有选与不选2个决策,若总共有n个物品,可以顺序依次考虑每个物品,这样就形成了一棵解空间树, 遍历这棵树,以枚举所有情况。为了更好地计算,首先利用函数knapsack()先将物品按照其单位重量价值从大到小排序,然后再进行遍历搜索。在搜索状态空间树时,只要左子节点是可一个可行结点,搜索就进入其左子树。对于右子树时,先计算上界函数,以判断是否将其减去(剪枝),最后进行判断,如果重量不超过背包容量,且价值最大的话,该方案就是最后的答案,其中构造了上界函数bound():当前价值cw+剩余容量可容纳的最大价值<=当前最优价值bestp,利用其进行剪枝,其算法流程图如下所示:

- 动态规划算法-函数Dynamic( )
利用动态规划算法求解0-1背包问题时,在选择装入背包的物品时,对每种物品i只有两种选择:装入背包或不装入背包。在0/1背包问题中,物品i或者被装入背包,或者不被装入背包,设xi表示物品i装入背包的情况,则当xi=0时,表示物品i没有被装入背包,xi=1时,表示物品i被装入背包,根据问题的要求,有如下约束条件和目标函数:
约束条件:
目标函数:
根据上述条件可得:0/1背包问题的动态规划函数为:
V(i,j)表示把前i个物品放入容量为j的背包中的最大价值和,根据上述分析,设n个物品的重量存储在数组w[n]中,价值存储在数组v[n]中,背包容量为C,数组V[n+1][C+1]存放迭代结果,其中V[i][j]表示前i个物品装入容量为j的背包中获得的最大价值,数组x[n]存储装入背包的物品。其算法流程图如下所示:

- 插入排序-函数Selectsort ( )
插入排序的基本思想是将一个记录插入到已经排好序的有序表中,在其实现过程使用双层循环,外层循环从第二个开始比较,外层循环对除了第一个元素之外的所有元素,内存循环与前面排好序的数据比较,如果后面的数据小于前面的则交换,如果不小于,说明插入完毕,退出内层循环,接着执行外层循环,直至序列全部有序。其算法流程图如下所示:

4.代码展示#
* 代码规范说明#
代码要求 | 代码规范 |
---|---|
缩进 | * 缩进采用4个空格 * 第二行相对第一行缩进 4个空格,从第三行开始,不再继续缩进,参考示例。 * 运算符与下文一起换行。 * 方法调用的点符号与下文一起换行。 * 在多个参数超长,逗号后进行换行 |
变量命名 | * 标识符必须以字母或者下划线开头,其他可以是数字、字母、下划线 * 标识符中的字母不限大小写,但大小写意义不同 * 变量名长度不得超过最大长度限制 * 关键字不能作为变量名,变量命名时应尽量避开预定义变量 * 采取驼峰命名法,首字母小写,后续单词开头大写,专业术语缩写可全用大写 |
每行最多字符数 | 单行字符数限制不超过 120个,超出需要换 |
函数最大行数 | 75行,超过需要封装 |
函数、类命名 | 采用驼峰命名法,首字母需大写 |
常量 | 常量定义需要全部大写 |
空行规则 | 一句代码一行,函数与其他代码之间不空行 |
注释规则 | 单行注释不换行,多行注释需换行 |
操作符前后空格 | 无需空格 |
其他规则 | * 大括号。左大括号不另起一行,右大括号单独占一行。 * 循环语句里的变量。一般变量设置为i,若有嵌套循环则依次使用i、j、m、n。 |
* 核心代码展示#
- 贪心算法代码展示
public double[][] Greedy(double[][] x ,int w[],int v[],int n, int C)
{
double[] c = new double[1000];
//计算每个物品单位重量价值比
for(int i = 0;i < n;i++)
{
c[i] =(double)v[i]/(double)w[i];
}
//对每个物品按照单位重量价值比进行排序
for(int i = 0;i < n;i++)
{
for(int j = i+1;j < n;j++)
{
if(c[i] <= c[j]) //c[i] <= c[j],将对应物品进行交换
{
//交换物品单位重量价值比
double temp = c[i];
c[i] = c[j];
c[j] = temp;
//交换物品重量
int tempw = w[i];
w[i] = w[j];
w[j] = tempw;
//交换物品价值
int tempv = v[i];
v[i] = v[j];
v[j] = tempv;
}
}
}
//初始化数组x
for(int i = 0;i < n;i++)
{
x[i][0] = 0;
}
int maxValue= 0;//背包最大价值
int i;
for (i=0; w[i]<C; i++ )
{
x[i][0]=1;//装入当前物品
maxValue+=v[i] ;//更新背包价值
C=C-w[i] ;//更新背包容量
}
x[n][0] =maxValue ;
return x;
}
- 回溯算法代码展示
//回溯函数
void backtrack(int i,int[] w,int[] v,int C,int n)
{
bound(i,w,v,n,C); //计算上界
if(i>=n) //所有元素都已遍历到,回溯结束
{
bestp = cp;
return ;
}
if(cw+w[i]<=C)
{
//放入背包
cw+=w[i];
cp+=v[i];
put[i]=1;
backtrack(i+1,w,v,C,n);
cw-=w[i];
cp-=v[i];
}
if(bound(i+1,w,v,n,C)>bestp)//符合条件搜索右子数
backtrack(i+1,w,v,C,n);
}
//计算上界函数
double bound(int i ,int[] w,int[] v,int n,int C)
{
double leftw= C-cw;
double b = cp;
while(i<n && w[i]<=leftw)
{
leftw-=w[i];
b+=v[i];
i++;
}
if(i<n)
b+=v[i]/w[i]*leftw;
return b;//返回上界值
}
- 动态规划算法代码展示
//动态规划算法
public int[] Dynamic(int w[ ], int v[ ],int n,int C)
{
int i,j;
int[] x = new int [10000];
int[][] V = new int [10000][10000];
for (i=0; i<=n; i++) //初始化第0列
{
V[i][0]=0;
}
for (j=0; j<=C; j++) //初始化第0行
{
V[0][j]=0;
}
for (i=1; i<=n; i++) //计算第i行,进行第i次迭代
{
for (j=1; j<=C; j++)
{
if (j<w[i])
{
V[i][j]=V[i-1][j];
}
else
{
if(V[i-1][j] > V[i-1][j-w[i]]+v[i])
{
V[i][j]=V[i-1][j];
}
else
{
V[i][j]=V[i-1][j-w[i]]+v[i];
}
}
}
}
j=C; //求装入背包的物品
for (i=n; i>0; i--)
{
if (V[i][j]>V[i-1][j])
{
x[i]=1;
j=j-w[i];
}
else
{
x[i]=0;
}
}
x[n] = V[n][C];//V[n][C]----背包取得的最大价值
return x;
}
- 插入排序代码展示
public double[][] Selectsort(double r[][], int first, int end){
//插入排序
for (int i = 1; i < end; i++) {
//外层循环,从第二个开始比较
for (int j = i; j > 0; j--) {
//内存循环,与前面排好序的数据比较,如果后面的数据小于前面的则交换
if (r[j][0] < r[j - 1][0]) {
double[] temp = new double[100];
temp = r[j - 1];
r[j - 1] = r[j];
r[j] = temp;
} else {
//如果不小于,说明插入完毕,退出内层循环
break;
}
}
}
return r;
}
5.功能测试#
* 正确读取实验数据文件的有效{0-1}KP数据#
根据提示信息,输入功能编号1,在弹出窗口的单选框中选择所要读取的文件,点击确定即可查看数据。

* 利用贪心算法求解#
根据提示信息,输入功能编号2,即可根据当前显示的{0-1}KP有效数据求其解,点击确定即可在右面显示文本区查看,如下图所示(数据文件为beibao1.in):

* 利用回溯法求解#
根据提示信息,输入功能编号3,即可根据当前显示的{0-1}KP有效数据求其最优解,点击确定即可在右面显示文本区查看,如下图所示(数据文件为beibao3.in):

* 利用动态规划法求解#
根据提示信息,输入功能编号4,即可根据当前显示的{0-1}KP有效数据求其最优解,点击确定即可在右面显示文本区查看,如下图所示(数据文件为beibao3.in):

* 绘制散点图#
根据提示信息,输入功能编号5,即可根据当前显示的{0-1}KP有效数据绘制以价值重量为横轴、价值为纵轴的数据散点图,通过窗口进行显示,如下图所示(数据文件为beibao4.in):

* 按重量比进行非递增排序#
根据提示信息,输入功能编号6,即可将当前显示的{0-1}KP有效数据绘制按重量比进行非递增排序,点击确定即可在右面显示文本区查看,如下图所示(数据文件为beibao2.in):

* 保存结果#
根据提示信息,输入功能编号7,即可将文本区显示的结果以txt文件形式保存下来,如下图所示:

6.总结#
该项目符合模块化设计,在项目中创建的每一个类都有自己不同的功能实现,对于项目中所提到的每一个需求都通过不同的函数封装起来了,将不同的需求功能划分为不同的模块,每个模块都是独立的,若后续要对某一模块进行修改,只需对相应的单独的函数进行修改即可,不影响其他模块函数的执行。
7.展示PSP#
PSP2.1 | 任务内容 | 计划共完成需要的时间(min) | 实际完成需要的时间(min) |
---|---|---|---|
Planning | 计划 | 8 | 10 |
- Estimate | - 估计这个任务需要多少时间,并规划大致工作步骤 | 8 | 10 |
Development | 开发 | 600 | 650 |
- Analysis | - 需求分析(包括学习新技术) | 10 | 8 |
- Design Spec | - 生产设计文档 | 20 | 25 |
- Design Review | - 设计复审(和同事审核设计文档) | 8 | 8 |
- Coding Standard | - 代码规范(为目前的开发指定合适的规范) | 20 | 18 |
- Design | - 具体设计 | 30 | 25 |
- Coding | - 具体编码 | 350 | 400 |
- Code Review | - 代码复审 | 40 | 60 |
- Test | - 测试(自我测试,修改代码,提交修改) | 120 | 150 |
Reporting | 报告 | 60 | 62 |
- Test Report | - 测试报告 | 40 | 35 |
- Size Measurement | - 计算工作量 | 15 | 10 |
- Postmortem & Process Improvement Plan | - 事后总结,并提出过程改进计划 | 25 | 25 |
- PSP总结
在项目开发完成后,计划花费时间与实际花费时间比较接近,对于时间误差存在较大的地方是项目开发阶段的具体编码和测试部分。在项目开发前期,对于项目的具体设计,刚开始打算采用c++编程,但是经过初步尝试,发现c++编程对于函数实现比较方便,但对于绘制散点图这个功能需求目前还不能实现,所以又采用了Java语言进行编码,花费了一些时间。在具体编码过程中,刚开始不知道如何绘制散点图,通过上网查询资料花费了一些时间,整体来说,具体编码阶段花费时间较长。
8.个人项目总结#
0-1背包问题是一个很经典的问题,在该项目中所提到的贪心算法,回溯算法,动态规划算法3种算法原先都已学习过,经过这次项目开发,对这些知识更加熟悉了,原先编程偏向于c++,在这次项目开发中,刚开始尝试用C++编程,没得出结果,浪费了时间,但由于原先学习过0-1背包问题相关知识,所以在用java编程时,速度有所提高,节省了一些时间。通过此次项目开发,对原先学习过的知识有了进一步的巩固,也学习到了一些新知识,但对于项目开发还是缺少经验,在开发过程中,也未能按照PSP规范此次项目开发,通过此次项目开发,多多积累经验,为以后的学习打下基础。
任务4:上传项目至Github#
项目开发完成后,已将项目上传至GitHub,仓库名为0-1-knapsack,传送:仓库链接
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 25岁的心里话
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用