201971010231-毛玉贤 实验二 个人项目—《KP问题》项目报告
实验二 软件工程个人项目
一、实验目的与要求
项目 | |
---|---|
课程班级博客链接 | https://edu.cnblogs.com/campus/xbsf/2019nwnucs |
本次作业要求链接 | https://edu.cnblogs.com/campus/xbsf/2019nwnucs/homework/12527 |
我的课程学习目标 | (1)掌握软件项目个人开发流程。 (2)掌握Github发布软件项目的操作方法。 (3)理解《构建之法》前两章的内容并利用PSP流程完成本次项目。 |
本次作业在哪些方面 帮助我实现学习目标 |
(1)老师的帮助以及作业要求文档中清晰的指导步骤让人一目了然。 (2)PSP流程的使用,使我更加合理的分配时间,执行任务计划。 (3)作业要求中所给出的代码规范文档,让我在编程时更加注意变量命名、函数等的规范使用。 (4)实验清晰的功能需求,让我更加合理的结构化的设计了本次项目的界面以及函数的封装等等。 |
项目Github的仓库链接地址 | https://github.com/Mao-cpu/backpack |
二、实验内容与步骤
1、任务一:点评班级博客至少三份
2、任务二:总结详细阅读《构建之法》第1章、第2章,掌握PSP流程
(1)《构建之法》第1章——概论
1.1 软件 = 程序 + 软件工程
- 软件测试:通过一系列工具、流程、文档等确保软件的高质量,这个测试过程即为软件测试;
- 源代码管理:修改源代码,使其质量不断提高,在能满足各种需求的条件下,还能保持之前的质量;
- 程序:就是一行行的代码,是建立在数据结构上的一些算法。程序要对数据进行操作,这些数据有静态有动态。工程师将这些代码和数据构建为机器能懂的可执行代码;
- 需求分析:客户所需要的功能;软件维护:在实际运行的过程中,可能会出现各种各样的问题,需要新老成员来修复问题维护服务;扩展结论:软件企业 = 软件 + 商业模式;
- 软件构建:构建不仅仅是ce和link命令,一个复杂的软件不但要有合理的软件架构、设计与实现,还要有各种文件和数据来描述各个程序文件之间的依赖关系、编译参数、链接参数等等。这些都是软件构建的过程。
1.2 软件工程是什么?
- 软件特性:复杂性、不可见性、易变性、服从性、非连续性;
- 软件工程的目标:用户满意度、可靠性、软件流程质量、可维护性;
- 软件:是可以运行在计算机及电子设备中的指令和数据的有序集合,可分为系统软件和应用软件;
- 软件工程与计算机学科的关系:计算机学科理论研究部分,大多数与数学关系密切,而软件工程和人的行为,社会需求息息相关;
- 软件工程:是把系统的、有序的、可量化的方法应用到软件的开发、运营和维护上的过程。软件工程包括下列领域:软件需求分析、软件设计、软件构建、软件测试和软件维护。
(2)《构建之法》第2章——个人技术与流程
2.1 单元测试
- 解决问题:能让自己负责的模块功能定义尽量明确,模块内部的改变不会影响其他模块,而且模块的质量能得到稳定的、量化的保证;
- 好的单元测试标准:①应该在最基本的功能上验证程序的正确性;②必须由最熟悉代码的人来写;③测试过后机器状态保持不变;④测试要快;⑤应产生可重复、一致的结果;⑥独立性;⑦ 应该覆盖所有代码路径;⑧应集成到自动测试的框架中;⑨必须和产品代码一起保存维护。
2.2 效能分析工具
- 抽样:当程序运行时,VS时不时看一看这个程序运行在哪一个函数内,并记录下来。程序结束后,VS就会得出一个关于程序运行时间分布的大致印象,优点:无需改动程序;缺点:时间不精确;
- 代码注入:将检测的代码加入到每一个函数中,这样程序的举动都被记录在案,程序的各个效能数据都可以被精准地测量。缺点:运行时间大大加长,还会产生很大的数据文件。
2.3 个人开发流程
- PSP(personal software process)流程:
- PSP流程特点:①不局限于某一种软件技术;②不依赖与考试,而是面向工程师自己收集数据,分析、提高;③在小初团队中,由于项目需求质量不高,导致程序员的输入质量不高,不能全由程序员负责;④PSP依赖数据;⑤PSP目的:记录工程师如何实现需求的效率。
3、任务三:用三种算法解决基本0/1背包问题,实现项目需求
(1)项目介绍
- 背包问题(Knapsack Problem,KP)是NP Complete问题,也是一个经典的组合优化问题,有着广泛而重要的应用背景。{0-1}背包问题({0-1 }Knapsack Problem,{0-1}KP)是最基本的KP问题形式,它的一般描述为:从若干具有价值系数与重量系数的物品(或项)中,选择若干个装入一个具有载重限制的背包,如何选择才能使装入物品的重量系数之和在不超过背包载重前提下价值系数之和达到最大?
- {0-1}KP数据集是研究{0-1}背包问题时,用于评测和观察设计算法性能的标准数据集;动态规划算法、回溯算法是求解{0-1}背包问题的经典算法。查阅相关资料,设计一个采用贪心算法、动态规划算法、回溯算法求解{0-1}背包问题的程序。
(2)需求分析
- ① 可正确读入实验数据文件0-9当中任意的有效{0-1}KP数据;
- ② 能够对一组{0-1}KP数据按单位重量价值比进行非递增排序;
- ③ 能够绘制任意一组{0-1}KP数据以重量为横轴、价值为纵轴的数据散点图;
- ④ 任意一组{0-1} KP数据的最优解、求解时间和解向量结果可保存为txt文件或导出EXCEL文件;
- ⑤ 用户能够自主选择贪心算法、动态规划算法、回溯算法求解指定{0-1} KP数据的最优解和求解时间(以秒为单位)。
(3)功能设计
- 根据需求分析,进行功能界面设计:
(4)设计实现
- 本项目的设计实现是在VScode的C++环境中运行编写的,在设计实现功能需求的过程中用到了很多重要的函数;
- 主要函数包括但不限于:动态规划算法(DP)、贪心算法(Greedy)、回溯算法(backtracking)、递减排序算法(sort)、绘制散点图(scatter_diagram)、选择数据文件0-9(input)、正确获取数据(get_data)、输出获取的数据(output)、输出结果并写入文件(output_result)等等。
- 程序整体框架说明图:
- 函数逻辑关系调用图:
- 动态规划实现思路:
-(1)0-1背包问题是求在以下条件下
1)∑wixi<=C, i from 1 to n
2)xi∈{0,1},1<=i<=n
总价值最大,
即∑vixi最大, i from 1 to n
-(2)最优子结构性质
若(x1,x2…xn)是0-1背包的最优解,则(x1,x2…xn-1)是下面相应子问题的最优解。
1)∑wixi<=C-wnxn, i from 1 to n-1
2)xi∈{0,1},1<=i<=n-1
总价值最大,
即∑vi*xi最大, i from 1 to n-1
-(3)递归关系
设m[i][j]为选择前i个物品,容量为j能装入物品价值的最大值。
可得如下递归关系
1)当 0<=j<wi 时,m[i][j]=m[i-1][j];
2)当 j>=wi时,m[i][j]=max{m[i-1][j],m[i-1][j-wi]+vi};
递推关系是这么形成的:
通过选择第i件物品放或不放来形成递推关系,
1)如果不放第i件物品,问题就转化为“前i-1件物品放入容量为c的背包中”,价值为m[i -1][j];
2)如果放第i件物品,那么问题就转化为“前i -1件物品放入剩下的容量为v-Ci的背包中“,价值为m[i-1][j-wi]+vi
而m[n][c]为选择前n个物品,容量为c背包能装入物品价值的最大值。
- 贪心实现思路:
- 先求每个物品单位重量的价值,按单位重量的价值从大到小排序。然后按这个顺序往背包里面放物品。
- 回溯实现思路:
- 回溯法是一个带有系统性和跳跃性的搜索算法。它在问题的解空间树中,按深度优先策略,从根节点出发搜索空间树,遍历所有路径找到最优解。
(5)测试运行
5.1 进入主界面(系统)
5.2 选择功能
5.2.1 选择1——动态规划
- 选择算法:
- 选择数据1:
- 文件结果DP.txt:
5.2.2 选择2——贪心算法
- 退回上级目录选择2:
- 选择数据0:
- 文件结果Greedy.txt:
5.2.3 选择3——回溯算法
- 退回上级目录选择3:
- 选择数据0:
- 文件结果backtracking.txt:
5.2.4 选择4——非递增排序
- 退回上级目录选择4:
- 选择数据2:
5.2.5 选择5——绘制散点图
- 退回上级目录选择5:
- 选择数据7:
5.2.6 选择6——正确读入数据
- 退回上级目录选择6,选择数据0:
5.2.7 选择7——退出程序
- 选择7则程序直接退出。
(6)部分核心代码
6.1 动态规划算法
for (i = 1; i <= N; i++)
{ //计算第i行,进行第i次迭代
for (j = 1; j <= WMAX; j++)
{
//绘制表
T[i][j] = T[i-1][j];
if(j >= w_v[i-1][0] && T[i][j]<w_v[i-1][1] + T[i-1][j-w_v[i-1][0]])
T[i][j] = w_v[i-1][1] + T[i-1][j-w_v[i-1][0]];
}
}
for (j = WMAX, i = N; i >= 1; i--)
{ //求装入背包的物品
if (T[i][j] > T[i-1][j])
{
vector_quantity[i] = 1; //若当前价值大于上一行价值,说明当前物品被装入背包中
j = j - w_v[i][0];
}
}
6.2 贪心算法
for (int i = 0; i < N; i++)
{
//按重量价值排序后,进行选择装入
g = decrease[k];
if (w_v[g][0] <= WMAX && WMAX>0)
{
max_value += w_v[g][1]; //价值增加
WMAX -= w_v[g][0]; //重量减少
vector_quantity[g] = 1;
}
k++;
}
6.3 回溯算法
void Backtracking(int i)
{
if (i >= N) //递归结束的判定条件
{
if (cp > max_value)
{
max_value = cp;
for (int i = 0; i < N; i++)
{
best_results[i] = vector_quantity[i];
}
}
return;
}
left_value -= w_v[i][1];
//如若左子节点可行,则直接搜索左子树;
//对于右子树,先计算上界函数,以判断是否将其减去
if (cw+w_v[i][0] <= WMAX ) //将物品i放入背包,搜索左子树
{
cw += w_v[i][0]; //同步更新当前背包的重量
cp += w_v[i][1]; //同步更新当前背包的总价值
vector_quantity[i] = 1;
Backtracking(i+1); //深度搜索进入下一层
cw -= w_v[i][0]; //回溯复原
cp -= w_v[i][1]; //回溯复原
}
if (cp+left_value > max_value) //如若符合条件则搜索右子树
{
Backtracking(i+1);
}
left_value += w_v[i][1];
}
6.4 非递增排序
//冒泡排序
for (int i = 0; i <N-1; i++)
{
for (int j = 0; j < N-1-i; j++)
{
if (vw[j] < vw[j+1])
{
index = vw[j];
vw[j] = vw[j+1];
vw[j+1] = index;
index = decrease[j];
decrease[j] = decrease[j+1]; //存入下标,用来递减排序
decrease[j+1] = index; //输出时循环输出,下标用的decrease中的
}
}
}
6.5 正确读入数据
if (!ifs.is_open())
{
cout<<"文件打开失败!"<<endl;
return;
}
//存入数组
while (ifs>>t1)
{ //遇到空白符结束
count++;
if (count == 1)
{
WMAX=t1; //最大限重
cout<<"重量限制:"<<WMAX<<"--->";
}else if (count == 2)
{
N = t1; //背包总数
cout<<"背包总数:"<<N<<endl;
}else
{
*p = t1;
p++;
}
}
ifs.close();
6.6 输出并写入文件结果
void Output_result()
{
ofs<<s<<" ";
ofs<<"Max_value:"<<max_value<<" ";
cout<<"\n该算法计算得的最大价值为:"<<max_value<<endl;
ofs<<"solution_vector:{";
cout<<"解向量为:{";
for (int i = 0; i < N; i++)
{
if (i == N-1)
{
ofs<<vector_quantity[i];
cout<<vector_quantity[i];
}else
{
ofs<<vector_quantity[i]<<",";
cout<<vector_quantity[i]<<",";
}
}
cout<<"}"<<endl;
ofs<<"}"<<" ";
}
(7)总结:本次设计的程序如何实现“模块化”原则
- 本次项目开发使用的是C++程序设计语言、软件为VScode C++环境,在实现本项目的过程中,使用到了很多函数,例如动态规划、贪心、回溯等算法、以及选择测试数据、非递增排序、读取数据文件、显示读取数据、输出结果并写入txt文件等,这些功能的实现,均采用了模块化封装的思想,使得各个函数之间互不影响,协同配合工作。即在使用特定功能函数时,直接进行函数调用即可。根据需求分析得到的功能设计图具体实现每个功能代码的编写时,都采用函数封装思想,在实现项目的整体功能实现上可以进行模块之间的相互调用,协同配合来共同实现。
(8)PSP流程分析
PSP2.1 | 任务内容 | 计划设计完成需要时间(min) | 实际需要时间 |
---|---|---|---|
Planning | 计划 | 11 | 10 |
Estimate | 估计任务时间,规划工作步骤 | 5 | 5 |
Development | 开发 | 540 | 520 |
Analysis | 需求分析(包括学习新技术) | 20 | 20 |
Design Spec | 生成设计文档 | 5 | 5 |
Design Review | 设计复审(和同事审核设计文档) | 5 | 5 |
Coding Standard | 代码规范 | 10 | 8 |
Design | 具体设计 | 10 | 5 |
Coding | 具体编码 | 300 | 280 |
Code Review | 代码复审 | 30 | 15 |
Test | 测试(自我测试、修改、提交) | 30 | 35 |
Reporting | 报告 | 60 | 70 |
Test Report | 测试报告 | 15 | 15 |
Size Measurement | 计算工作量 | 5 | 3 |
Postmortem & Process Improvement Plan | 事后总结,提出过程改进计划 | 5 | 5 |
(9)经验分享
- 首先在实现本次项目之前,根据PSP流程来辅助我们梳理思路是极其重要的,以及想清楚在开发具体项目时,用什么语言能更好地实现功能需求。例如在本次项目设计时,最初没有进行PSP的设计,导致我惯性思维,没有考虑语言的问题,直接选用了C++来实现本次项目。在实现的过程中才发现,C++并非是设计本次项目最好的选择,其图像处理方面并不方便,以及在界面的设计上,可能也不够美观,因此在图像处理这件事情上,耗费了我大量的时间。最终在寻求各种解决方法无果后(可能本身C++基础比较弱,所以在散点图绘制实现功能上想不到更好的方法),采用了python语言绘制了10个数据的散点图。因此在我们开发一个项目之前,进行PSP流程的梳理以及思考是很有必要的,可以帮我们节省很多后期因不必要犯错而浪费的时间,拖慢开发进度。PSP流程的运用和设计有效的帮助了我们更合理的安排项目实施过程,使我们项目计划能有条不紊的进行,避免不必要的错误,在这一点上,深有体会。
4、任务四:完成任务3的程序开发,将项目源码的完整工程文件提交到你注册Github账号的项目仓库中
(1)提交代码到github
-上传到github上:
-commit 13次:
- issue 1次:
- release 1次:
- 上传流程(前提git已下载,仓库已创建):
- 先在电脑硬盘里找一块地方存放本地仓库,比如我们把本地仓库建立在E:\Project\backpack文件夹下进入backpack文件夹 鼠标右键操作;
- 在本地仓库里右键选择Git Init Here,会多出来一个.git文件夹,这就表示本地git创建成功。右键Git Bash Here进入git命令行;
- 为了保险起见,我们先执行git init命令:
$ git init
- 为了把本地的仓库传到github,还需要配置ssh key,在本地创建ssh key:
$ ssh-keygen -t rsa -C "your_email@youremail.com"
- 直接点回车,说明会在默认文件id_rsa上生成ssh key,然后系统要求输入密码,直接按回车表示不设密码,重复密码时也是直接回车,之后提示你shh key已经生成成功;
- 进入提示的地址下查看ssh key文件,打开id_rsa.pub,复制里面的key,回到github网站,进入Account Settings,左边选择SSH Keys,Add SSH Key,粘贴key;
- 验证是否成功,在git bash下输入:
$ ssh -T git@github.com
- 接下来我们要做的就是把本地仓库传到github上去,在此之前还需要设置username和email,因为github每次commit都会记录他们:
$ git config --global user.name "your name"
$ git config --global user.email "your_email@youremail.com"
- 进入要上传的仓库,右键git bash,添加远程地址:
$ git remote add origin git@github.com:yourName/yourRepo.git
- 接下来在本地仓库里添加一些文件,比如README,在本地新建一个README文件:
$ git add README
$ git commit -m "first commit"
- 上传到github:
$ git push origin master
- git push命令会将本地仓库推送到远程服务器。
- 首次提交,先git pull下,修改完代码后,使用git status可以查看文件的差别,使用git add 添加要commit的文件即可!
-
学习链接
(2)项目代码规范说明
1. 缩进:
- 程序块要采用缩进风格编写,缩进的空格数为4个。缩进或者对齐只能使用空格键,不可使用TAB键,使用TAB键需要设置TAB键的空格数目是4格。
2. 变量命名:
- 命名尽量使用英文单词 :命名尽量使用英文单词,力求简单清楚,避免使用引起误解的词汇和模糊的缩写,使人产生误解;命名规范必须与所使用的系统风格保持一致,并在同一项目中统一;全小写或全大写(可加下划线)。
3. 每行最多字符数:
- 较长的语句(>80 字符)要分成多行书写 )要分成多行书写。
4. 函数最大行数:
- 函数的规模尽量限制在100 行以内。
- 长表达式要在低优先级操作符处划分新行,操作符放在新行之首,划分出的新行要进行适当的缩进,使排版整齐,语句可读。
5. 函数命名:
- 函数名以大写字母开头,采用谓-宾结构(动-名),且应反映函数执行什么操作以及返回什么内容。
6. 常量:
- 常量、宏和模板名采用全大写的方式,每个单词间用下划线分隔;枚举类型enum常量应以大写字母开头或全部大写。
7. 空行规则:
- 相对独立的程序块之间、变量说明之后必须加空行;函数之间应该用空行分开;用空行将代码按照逻辑片断划分;每个类声明之后应该加入空格同其他代码分开。
8. 空格规则:
- 关键字之后要留空格。象 const、virtual、inline、case 等关键字之后至少要留一个空格, 否则无法辨析关键字。象 if、for、while 等关键字之后应留一个空格再跟左
括号‘( ’, 以突出关键字。
- 函数名之后不要留空格,紧跟左括号’(’ , 以与关键字区别;)‘( ’ 向后紧跟,‘ )’、‘ ,’、‘ ;’ 向前紧跟,紧跟处不留空格;‘ ,’ 之后要留空格,如 Function(x, y, z)。如果‘ ;’ 不是一行的结束符号,其后也要留空格。
9. 注释规则:
- 源文件头部应进行注释:源文件头部应进行注释,列出:生成日期、作者、模块目的/功能等;注释应该和代码同时更新,不再有用的注释要删除;注释的内容要清楚、明了,不能有二义性;避免在注释中使用非常用的缩写或者术语。
10. 操作符前后空格:
- 值操作符、比较操作符、算术操作符、逻辑操作符、位域操作符,如“ =”、“ +=” 、“ >=”、“ <=”、“ +”、“ *”、“ %”、“ &&”、“ ||”、“ ^” 等二元操作符
的前后应当加空格(输入“>>”、输出“<<”除外)。
- 一元操作符如“ !”、“ ~”、“ ++”、“ --”、“ &”( 地址运算符) 等前后不加空格;象“[ ]”、“ .”、“ ->” 这类操作符前后不加空格。
11. 其他规则:
- 程序块的分界符:程序块的分界符(如 C/C++语言的大括号‘{’和‘}’)应各独占一行并且位于同一列,同时与引用它们的语句左对齐。在函数体的开始、类的定义、结构的定义、枚举的定义以及 if、for、do、while、switch、case 语句中的程序都要采用如上的缩进方式。