项目 | 内容 |
---|---|
这个作业属于哪个课程 | 软件工程 |
这个作业的要求在哪里 | 结对项目-最长英语单词链 |
我在这个课程的目标是 | 学习软件工程的一般方法并实践 |
这个作业在哪个具体方面帮助我实现目标 | 实践结对编程方法,锻炼工程能力 |
在文章开头给出教学班级和可克隆的 Github 项目地址。
- 教学班级:周四上午
- 项目地址:https://gitee.com/mamba0824/buaapaireprogramming
- 项目成员:20373974 阮正浩, 20373447 朱承烜
在开始实现程序之前,在下述 PSP 表格记录下你估计将在程序的 各个模块的开发上耗费的时间。
见文末表格
看教科书和其它资料中关于 Information Hiding,Interface Design,Loose Coupling 的章节,说明你们在结对编程中是如何利用这些方法对接口进行设计的。
这三个概念联系紧密,信息隐藏指的是类或函数对外只暴露必要的部分,保护内部复杂或敏感的信息,这需要良好的功能抽象与模块化。接口设计便是模块化工作中意义最重要的一步,优秀的接口应当能将功能抽象成逻辑分明的多个子模块,便于编程和测试,同时由于接口的隔离各模块能够松耦合。
编程过程中我们将任务先分为输入、计算、输出三个逻辑模块,模块间用合适的接口隔离,再对各模块进行进一步的细分,例如将输入分为读文件和解析参数两部分,直至任务规模较小。每个模块间仅通过设计的接口进行数据交换,不关心各自的实现细节。
计算模块接口的设计与实现过程。 设计包括代码如何组织,比如会有几个类,几个函数,他们之间关系如何,关键函数是否需要画出流程图?说明你的算法的关键(不必列出源代码),以及独到之处。
整体流程图:
具体解释:
EXPORT_DLL int gen_chain_word(char* words[], int len, char* result[], int head, int tail, int banned,int enable_loop);
EXPORT_DLL int gen_chains_all(char* words[], int len, char* result[]);
EXPORT_DLL int gen_chain_char(char* words[], int len, char* result[], int head, int tail, int reject, int enable_loop);
计算模块对外开放上述三个函数作为接口,分别对应三个主任务。
选择以单词为节点建图,节点存储邻接数组,两层循环复杂度 O ( V 2 ) O(V^2) O(V2)。
若为-n,由于要输出所有单词链,直接dfs即可。
若为-w,由于仅要求最大链,考虑优化方法。对于无环情况,我们发现可以利用DAG图的拓扑性质进行dp解决问题。首先进行拓扑排序检测环路同时得到拓扑序(复杂度 O ( V + E ) O(V+E) O(V+E)),令dp[i]代表以节点i为结尾的最大单词链长度并初始化为1,按照拓扑序进行遍历更新dp[i]。
具体的方法是对于节点i,依次考虑接在其后的所有相邻节点,相邻节点取j时,若dp[j] < dp[i] + 1,则更新dp[j]为dp[i] + 1, 并记录dp[j]的前置节点为i,否则不进行操作,遍历完成后取dp表的最大值并根据前置节点恢复整条单词链即可。这种方法之所以能成立是由于拓扑序保证了处于遍历序列后面的节点不可能到达前面的节点,所以也不会造成更新。
考虑附加参数,-t容易处理,若节点i尾字母不合要求,考虑最大值时不计算dp[i]即可;-j和-h需要对图进行一定的重构,-j可以遍历所有节点将首字母不合要求的排除(代码实现上是用disabled标记),-h稍微复杂一点,有-h时唯一影响答案的情况是非指定首字符的单词作为链的头部,所以只需要再通过一次拓扑排序的过程将首字母不合要求且入度为0的节点排除即可。
对于-r的有环图,上述优化失效,目前还未找到正确的线性方法,直接类似-n使用dfs暴力搜索,同时对附加参数进行处理,这部分抽象至gen_chain_loop方法进行处理,比较常规便不赘述细节。
展示在所在开发环境下编译器编译通过无警告的截图
阅读有关 UML 的内容,画出 UML 图显示计算模块部分各个实体之间的关系。
计算模块接口部分的性能改进。 记录在改进计算模块性能上所花费的时间,并展示你程序中消耗最大的函数,陈述你的性能改进策略。
一开始我们基于-n的暴力dfs方法对-w,-c的情况进行了简单实现,初步验证了正确性,但是对每个节点进行dfs的开销巨大难以接受。
于是我们希望利用剪枝或dp方法进行优化,并发现DAG图的拓扑序具有良好性质,能够保证dp的简单性和正确性,通过前文提到的具体算法,我们成功地将无环情况的求解复杂度降到了线性,小于建图的 O ( V 2 ) O(V^2) O(V2)复杂度,这种情况下消耗最大的函数为:
Node *create_graph(char *words[], int len) {
Node *nodes = (Node *) malloc((len + 5) * sizeof(Node));
if (nodes == nullptr) return nullptr;
int idx = 0;
for (int i = 1; i <= len; i++) {
char *word = words[i];
auto tmp =