第一次个人编程作业

这个作业属于哪个课程 计科23级12班
这个作业要求在哪里 作业要求
这个作业的目标 使用C++设计一个论文查重算法,并在github上记录各版本并进行测试

一、github链接

仓库地址:https://github.com/hachimiking/hachimiking/tree/main/3123002977
release地址:https://github.com/hachimiking/hachimiking/releases/tag/1

二、PSP表格

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划 10 10
Estimate 估计这个任务需要多少时间 5 5
Development 开发 10 15
Analysis 需求分析(包括学习新技术) 30 40
Design Spec 生成设计文档 30 45
Design Review 设计复审 10 10
Coding Standard 代码规范(为目前的开发制定合适的规范) 5 5
Design 具体设计 30 40
Coding 具体编码 30 45
Code Review 代码复审 10 10
Test 测试(自我测试,修改代码,提交修改) 30 35
Reporting 报告 10 10
Test Report 测试报告 10 10
Size Measurement 计算工作量 5 5
Postmortem & Process Improvement Plan 事后总结,并提出过程改进计划 10 10
Total 合计 235 295

三、计算模块接口的设计与实现过程及优势

在该项目中,我的代码主要分为四个模块:输入模块、字符串处理模块、计算(查重)模块和输出模块

  1. 输入模块:从命令行参数中读取[原文文件] [抄袭版论文的文件] [答案文件]的绝对路径,之后从文件中读取字符串存入string中,并将string传递给字符串处理模块进行utf-8字符的分割处理。
  2. 字符串处理模块:代码根据utf-8标准进行编写,可以根据字符长度的不同来进行不同的处理,而不是均按照同一长度来进行处理。对于不同长度的字符的处理正是这个程序的独到之处。将字符串处理好之后,将对应元素传递给计算(查重)模块进行重复率的计算。
  3. 计算(查重)模块:从字符串处理模块中获取所需元素后进行重复率的计算,该问题的计算使用了经典动态规划算法——最小编辑距离。使用动态规划计算出重复率之后,将答案传递至输出模块。
  4. 输出模块:输出模块负责将答案写入到答案文件中,并将答案显示在屏幕上,之后结束程序的执行。

代码内没有进行class的区分。主要函数包括:splitUTF8(将UTF-8字符串分割成单个字符的向量)、getdis(计算两个UTF-8字符串向量之间的编辑距离)、read_file(从文件读取内容)、write_file(将结果写入文件),其关系在上述模块分类中以给出。


这个程序的优势在于:

  1. 对文本处理得当,可以将多种语言(中文、英文、日文等utf-8支持的语言)混合处理,解决了中文、日文等多字节字符的查重痛点 —— 若直接按字节计算编辑距离,会因 “一个中文字符占 3 字节” 导致结果严重失真(如 “你好” 和 “您好” 的字节差异被放大),而splitUTF8确保了 “字符级” 的精准比对,对utf-8的处理简洁明了,使得代码有较高的可读性。
  2. 基础设计未对文本长度做硬性限制,从 “短文本(如几百字符)” 到 “长文本(如 10 万字符)” 均能兼容,且通过后续优化(分块并行、滚动数组)可进一步适配更大规模文本。
  3. 模块化分层清晰,符合高内聚低耦合原则
  4. 可靠性保障充分,细节处理周全,覆盖边界场景。代码在基础功能实现中充分考虑了 “异常场景” 和 “边界条件”,体现了严谨的设计思维。
  5. 算法模块可替换性强。编辑距离计算封装在独立的getdis函数中,若未来需提升查重精度(如改用 “余弦相似度”“Jaccard 系数”),仅需修改getdis的实现逻辑,无需改动 I/O、文本处理等其他模块。

重复率可以转换为最小编辑距离问题来解决。最小编辑距离(通常指 Levenshtein 距离)是衡量两个字符串相似性的指标,定义为:将一个字符串(源字符串 s,长度为 m)转换为另一个字符串(目标字符串 t,长度为 n)所需的最少单字符操作次数。允许的操作包括:

  • 插入:在源字符串中插入一个字符(如 s→si)
  • 删除:在源字符串中删除一个字符(如 si→s)
  • 替换:将源字符串中的一个字符替换为另一个字符(如 si→sj)

在计算(查重)模块中,我们应用求解最小编辑距离的动态规划算法来进行计算,其核心思路如下:

动态规划三要素

  1. 状态定义dp[i][j]:将s的前i个字符(s[0..i-1])转为t的前j个字符(t[0..j-1])的最小编辑距离。
  2. 边界条件
    • i==0(s为空):dp[0][j] = j(需插入j个字符);
    • j==0(t为空):dp[i][0] = i(需删除i个字符)。
  3. 转移方程
    • s[i-1] == t[j-1](当前字符相同):dp[i][j] = dp[i-1][j-1](无需操作);
    • s[i-1] != t[j-1](当前字符不同):dp[i][j] = min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]) + 1(取删除、插入、替换的最小代价 + 1)。

复杂度:时间O(mn),空间O(mn)(在这个程序的实现中已优化至O(m)))。

值得注意的是,这个程序中的文本均使用utf-8编码

流程图:

流程图

四、计算模块接口部分的性能改进

在计算(查重)模块中,我们应用求解最小编辑距离的动态规划算法来进行计算,其理论空间复杂度为O(mn)。但在这个程序中,我使用了滚动数组优化的方式,使得空间复杂度下降至O(m)
在上述动态规划转移方程中,容易注意到dp[i][j]的值仅与很有限的元素相关,其中关于i的这一维度,其值仅在i和i-1中变化。自然地,我们考虑滚动数组优化,将二维数组dp优化成两个一维数组dplast,用dp来表示当前计算到的答案,而last则存放在原二维数组中上一行的答案,在每一轮i的计算结束后,将last赋值为dp即可。

空间复杂度从 O (nm) 降至 O (m)(如 10 万字符文本,内存占用从 10GB→100KB);

瓶颈节点 关联函数 耗时占比
编辑距离计算 getdis 85%
UTF-8 字符分割 splitUTF8 10%
文件读取 read_file 4%
异常捕获与资源释放 全局异常处理 1%

五、计算模块部分单元测试展示

所有测试数据均上传仓库。
对于群里下发的测试数据,使用cmd进行测试。

数据测试1

对于自行构造的测试数据,使用cmd进行测试。

数据测试2

六、计算模块部分异常处理说明

在这个项目的实现中,计算模块部分相对简单,未出现异常。
但对于命令行参数数量不满足要求时,会输出要求的命令行参数格式并退出程序。

异常处理

posted @ 2025-09-22 23:50  lolte  阅读(44)  评论(0)    收藏  举报