本次数据结构作业是要写一个两份代码查重的系统,还要简单的UI交互。写了几天上网查了好多资料,总算是写完了,写个博客记录下,也算打打编程基础了。
问题分析
编写程序判断给定的一批C源程序文件相互之间是否存在抄袭。程序需标注出有抄袭嫌疑的源代码文件之间相似段落。
从储存代码,提取语句,到计算重复度,展示重复语句,可以分为以下几个步骤。
① 读取代码文本,并保存在对应的数据结构中。
② 将文本并分割成若干个语句。
③ 将不同的语句比对,计算编辑距离(相似度)。
④ 将两份文本的所有语句一一比对,计算查重率。
⑤ 重复上述操作,直到完成所有代码文本的比对。
⑥ 设置ui交互,引导用户输入路径,完成代码查重。
数据结构设计
五个结构体, filename_list、Afile、sentence、node、match,分别保存
文件名链表、一份文件、一个句子、两份文件的匹配结果、两个语句的匹配结果。
* 一个文件夹对应一个文件名链表filename_list 里面有很多文件
* 一个文件Afile 里面有很多句子sentence
* 一个结点node有两份文件 记录这两份文件的查重信息
* 句子查重信息保存在match匹配结构体中 一个node有很多match
* 这里说的"很多"是一个链表 而上一级结构体会保存下一级链表的头指针
*
* 我们规定有两组代码 分别保存在两个文件名链表filename_list中
* A组一份代码文件会和B组所有代码文件一一匹配,匹配结果保存在node中
* A组所有文件都会重复上述操作 从而实现
* 一对一
* 一对多
* 多对一
* 多对多
* 混杂查重(当A组和B组文件相同时)
*
* 因此每个filename_list结点会保存一个node链表的头指针 将记录所有与另一组文件一一匹配的信息
* 一个node记录一个一对一结果
//1 文件名链表 多个文件组成的一支链表
struct filename_list
{
string name;
filename_list* next = NULL;
node* head = NULL;
};
//2 文本结构体 记录一个文件
struct Afile
{
//文本路径
//句子头节点
//句子数量
filename_list* path = NULL;
sentence* head = NULL;
int num = 0;
};
//3 句子结构体 记录一个句子
struct sentence
{
//原始文本
//修改后文本
//字符串长度
//下一个句子
string s1;
string s2;
int len1 = 0;
int len2 = 0;
sentence* next = NULL;
};
//4 结点 一个结点记录两份文件的查重信息
struct node
{
//标记位 (有的文件不需要匹配)
//A文件指针
//B文件指针
//匹配结构体指针
//匹配数目
int flags = 0;
Afile* a_file = NULL;
Afile* b_file = NULL;
string path1;
string path2;
match* head = NULL;
int num = 0;
node* next = NULL;
double rate = 0;
int code = 0;
};
//5 匹配结构体 记录两份文件中查重率高的一对语句
struct match
{
//A文件的句子指针
//B文件的句子指针
//匹配度
//编辑距离
//下一个匹配结构体
sentence* a_sen = NULL;
sentence* b_sen = NULL;
double similar = 0;
int score = 0;
match* next = NULL;
};
主要算法实现
- 编辑距离
采用动态规划法,长度为s1和s2的字符串可以分为几种情况。
1)最后一个字符相同,可以转化为长度为s1-1的子串和长度为s2-1的字串编辑距离。
编辑次数不变。
2)最后一个字符不同
1》删除s1的最后一个字符 (变成s1-1的字串和s2计算)
2》将s1的最后一个字符变成和s2一样的字符 (相当于删除两个字串的最后一个字符,变成s1-1和s2-1的子串的计算)
3》增加一个和s2最后一个字符一样的字符(相当于删除s2的最后一个字符,变成s1和s2-1的子串计算)
无论是这三种的哪一种,编辑次数都+1。
经过递归处理,两个字串长度一定会越来越小,最后返回编辑次数最小的值,便是两个字符串s1 s2的编辑距离。
从后往前推,可以用循环来完成。核心为这三行代码
for (int i = 1; i <= len1; i++)
for (int j = 1; j <= len2; j++)
dp[i][j] = s1[i - 1] == s2[j - 1] ?dp[i - 1][j - 1] : min(dp[i - 1][j - 1], min(dp[i - 1][j], dp[i][j - 1])) + 1;
在此之前要对dp二维数组初始化
for (int i = 0; i < 60; i++)
{
dp[i][0] = i;
dp[0][i] = i;
}
dp二维数组的第一行和第一列默认从0开始增加到59,从第二行和第二列才正式开始计算编辑距离。所以当为dp[60][60]时,两个字符串最长为59。dp[59][59]的值就是两个字符串的编辑距离。
2.从一个文件夹读取所有文件
filename_list* dir_Allfile(string path)
{
//创建头节点
filename_list* f1 = create_filename_list();
intptr_t hFile = 0;
//文件信息
struct _finddata_t fileinfo;
string p;
if ((hFile = _findfirst(p.assign(path).append("\\*").c_str(), &fileinfo)) != -1)
{
do
{
//printf("%s\n", p.assign(path).append("\\").append(fileinfo.name).c_str());
string q1 = p.assign(path).append("\\").append(fileinfo.name);
//只找cpp文件
if (iscpp(q1))
{
create_filename_list(f1, q1);
}
} while (_findnext(hFile, &fileinfo) == 0);
_findclose(hFile);
}
//因为头节点是空的
return f1->next;
}
上网查到的是用 _finddata_t 和 _findnext 可以遍历这个文件夹。简单一些我们就不深度遍历了。
3.读取一个文件的内容
Afile* get_file(string path)
{
//读取文件内容 这里假定字符串路径s是正确的
//string s 传入 char*类型也可以
// 1打开文件
FILE* fp;
if ((fp = fopen(path.c_str(), "r")) == NULL) //以只读的方式打开test。
{
printf("读取文件失败!\n");
return NULL;
}
// 2创建文本结构体 创建句子结构体
Afile* a = create_Afile();
sentence* sen = create_sentence();
// 3循环读取 创建句子
// 1)将文件以 ; 作为切割保存在句子结构体里
// 2)初步处理句子内容
// 3)对句子计数
int num = 0;
char ch = ' ';
string str = "";
while (fp != NULL && (ch = fgetc(fp)) != EOF)
{
str.push_back(ch);
//以分号为分割
if (ch == ';')
{
create_sentence(sen, str);
num++;
str = "";
}
}
create_sentence(sen, str);
num++;
// 4将结果保存在文本结构体中
a->head = sen->next;
a->num = num;
// 一定要记得分配空间!!! 不然就会崩掉
a->path = create_filename_list();
a->path->name = path;
// 5关闭文件
if (fp != NULL)
{
fclose(fp); //关闭文件
}
// 6返回文本指针
return a;
}
我用的是fgetc()函数,一次读取一个,这样在遇到分号时切割成一个个语句,保留代码逻辑性。一句句代码保存在sentence结构体里,而这个函数返回一个Afile结构体的指针。
简单ui设计
我们这是个简单的系统,只要提示用户输入文件或者文件夹的路径就好了
1.利用printf打印一个简单界面。
2.利用#include <conio.h>库中的_getch()函数接受用户命令并交互。
3.设置while循环,重复接收用户命令。
如主界面
void face01()
{
system("cls");
printf("\n\n\n\n\n");
printf("\t\t\t\t\t\t欢迎来到代码查重系统\n\n\n");
printf("\t\t\t\t\t\t 按 0 开始查询\n");
int c = 0;
while (1)
{
c = _getch();
if (c == '0')
{
face02();//进入下一个界面
break;
}
//printf("%d",c);
//printf("%c",c);
}
}
![](https://img-blog.csdnimg.cn/img_convert/3af936d00bdbe1009ae1dde1271ebb43.png)
![](https://img-blog.csdnimg.cn/img_convert/f5023e2df11b1fd2d270beb74815aa66.png)
问题与思考
值得思考的问题
1.如何根据编辑距离判断两句子的重复度?
我们采用的方式是similar = [ 1 - (编辑距离/较长字符串的字符数) ] * 100%
短语句编辑次数越少就能成为长语句,就说明二者的相似度越高
缺点)如果抄袭句子故意加上一大段无关代码,编辑距离和较长字符串数就会同时增大,相似度就会变得比较低
改进)我们采用的是以分号(;)分割语句,保留语句逻辑性,计算编辑距离时加上无关代码的难度就比较高(因为加代码通常要分号分隔)
2.计算完编辑距离后,怎么利用相似度计算查重率?
我们采用的方式是设置阈值,相似度高于0.4的判定为相似语句,相似语句和文本语句总量的占比就是查重率
有以下不足
1)阈值不好确定
2)根据占比,基本上不可能有100%查重率的代码
优点
1)设置简单
2)可以根据查重率再设置阈值,>80%基本上可以认定为完全抄袭 >60%的认定为大量抄袭,而无关代码的查重率通常低于20%,符合我们的认知
3.两文本匹配方式
知道两字符串的匹配方式,怎么利用到文本中去呢?
我们采用的方式是暴力匹配,a文件的每个语句都会和b文件的所有语句匹配,找到相似度最高的语句。
缺点
1)时间复杂度高 为O(n*n)
2)可能出现多语句反复匹配一句的现象
优点
1)实现方便
2)结果较准确 即使有少量误判情况 两份无关代码a b查重率也会低于25%
4.两语句故意更改变量名降低编辑距离怎么办?
这是尚未处理的问题,可参考的思路有
1)变量名归一化,长的变量名全部变为a1 a2...
实现困难 难免有短的变量名a ,不能把文本中所有的a字符变为归一化后的字符
5.动态规划法计算编辑距离的问题
当字符串的长度比较长时(超过100)二维数组dp[100][100]就可能会栈溢出
而动态再堆区开辟二维数组实现较困难
而用递归的方式动态规划成本较高,也有可能溢出
解决:将字符串拆成50字符的若干个子串,将子串一一匹配
1)需要内存较小,只需dp[50][50]即可
2)降低了时间复杂度
3)效果与真实计算结果相近
总结:通过这次编程还是学到了很多东西,对c/c++的一些函数操作更熟悉了一些,希望以后继续多写代码,多写博客记录一些实现流程和思考。
源代码
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <io.h>
#include <string>
#include <iostream>
#include <conio.h>
using namespace std;
//1 声明全局变量
struct filename_list;
struct Afile;
struct sentence;
struct node;
struct match;
filename_list* create_filename_list();
filename_list* create_filename_list(const string s);
filename_list* findlast_filename_list(filename_list* head);
void create_filename_list(filename_list* head, const string s);
filename_list* dir_Allfile(string path);
filename_list* get_files(string path);
sentence* create_sentence();
sentence* create_sentence(string s);
sentence* findlast_sentence(sentence* head);
void create_sentence(sentence* head, const string s);
Afile* create_Afile();
Afile* get_file(string path);
match* create_match();
node* create_node();
bool is_exist(const char* s);
bool is_dir(const char* fileName);
bool iscpp(string s);
string& replace_all(string& src, const string& old_value, const string& new_value);
void fix_sentence(string& s);
void fix_sentence2(string& s);
void inil_sentence(sentence* s, int);
void inil_sentence(sentence* s);
int ex_dis(string q1, string q2);
int calc_dis(string s1, string s2);
double rate_dis(string s1, string s2, int score);
match* find_best(sentence* one, sentence* many, int);
match* find_best(sentence* head1, sentence* head2);
match* find_best(Afile* a, Afile* b);
match* fix_match(match* head);
void inil_node(node* n);
void inil_node(node* n, filename_list* f1, filename_list* f2);
void one_to_many(filename_list* f1, filename_list* head);
void many_to_many(filename_list* head1, filename_list* head2);
int get_length(match* m);
int get_length(sentence* s);
int get_length(filename_list* f);
void repeat_rate(node* n);
bool judge(node* n);
void print_percent(double d);
void print_filename_list(filename_list* f);
void print(const string& s);
void print_sentence1(sentence* f);
void print_sentence2(sentence* f);
void print(sentence* s, int flags);
void print(const char* s, double num);
void print(match* m, int);
void print(match* m);
void print(Afile* a);
void print(Afile* a, int);
void print(node* n);
void print(filename_list* f);
void print_info(node* n);
void print_info(filename_list* f);
bool print_info(filename_list* f, int code);
void face01();
void face02();
void face03();
//一、数据结构
//1 文件名链表 多个文件组成的一支链表
struct filename_list
{
string name;
filename_list* next = NULL;
node* head = NULL;
};
//2 文本结构体 记录一个文件
struct Afile
{
//文本路径
//句子头节点
//句子数量
filename_list* path = NULL;
sentence* head = NULL;
int num = 0;
};
//3 句子结构体 记录一个句子
struct sentence
{
//原始文本
//修改后文本
//字符串长度
//下一个句子
string s1;
string s2;
int len1 = 0;
int len2 = 0;
sentence* next = NULL;
};
//4 结点 一个结点记录两份文件的查重信息
struct node
{
//标记位 (有的文件不需要匹配)
//A文件指针
//B文件指针
//匹配结构体指针
//匹配数目
int flags = 0;
Afile* a_file = NULL;
Afile* b_file = NULL;
string path1;
string path2;
match* head = NULL;
int num = 0;
node* next = NULL;
double rate = 0;
int code = 0;
};
//5 匹配结构体 记录两份文件中查重率高的一对语句
struct match
{
//A文件的句子指针
//B文件的句子指针
//匹配度
//编辑距离
//下一个匹配结构体
sentence* a_sen = NULL;
sentence* b_sen = NULL;
double similar = 0;
int score = 0;
match* next = NULL;
};
//二、数据结构创建
//1 创建文件名链表1 空创建
filename_list* create_filename_list()
{
//笔记:如果用malloc创建结构体,其中包含string类就会报错
filename_list* f = new filename_list();
if (f == NULL)
{
printf("创建失败!\n");
return NULL;
}
else
{
//printf("创建成功\n");
f->name = "";
f->next = NULL;
f->head = NULL;
return f;
}
}
//2 创建文件名链表2 指定文件名创建结点
filename_list* create_filename_list(const string s)
{
filename_list* f = create_filename_list();
f->name = s;
return f;
}
//3 找链尾指针
filename_list* findlast_filename_list(filename_list* head)
{
if (head == NULL)
{
printf("传入的filename_list指针有误\n");
return NULL;
}
else
{
filename_list* f1 = head;
while (f1->next != NULL)//一直找链尾
{
f1 = f1->next;
}
return f1;
}
}
//4 创建文件名链表3 指定链头和文件名,自动加在链尾
void create_filename_list(filename_list* head, const string s)
{
//找链尾
filename_list* f = findlast_filename_list(head);
filename_list* f2 = create_filename_list(s);
f->next = f2;
}
//5 创建文件名链表4 输入一个文件夹路径 返回文件头节点
filename_list* dir_Allfile(string path)
{
//创建头节点
filename_list* f1 = create_filename_list();
intptr_t hFile = 0;
//文件信息
struct _finddata_t fileinfo;
string p;
if ((hFile = _findfirst(p.assign(path).append("\\*").c_str(), &fileinfo)) != -1)
{
do
{
//printf("%s\n", p.assign(path).append("\\").append(fileinfo.name).c_str());
string q1 = p.assign(path).append("\\").append(fileinfo.name);
//只找cpp文件
if (iscpp(q1))
{
create_filename_list(f1, q1);
}
} while (_findnext(hFile, &fileinfo) == 0);
_findclose(hFile);
}
//因为头节点是空的
return f1->next;
}
//6 创建文件名链表5 输入一个路径 如果是文件返回一个结点 如果是文件夹则返回链表头指针
filename_list* get_files(string path)
{
if (iscpp(path))
return create_filename_list(path);
return dir_Allfile(path);
}
//7 句子创建1
sentence* create_sentence()
{
sentence* f = new sentence();
if (f == NULL)
{
printf("创建失败!\n");
return NULL;
}
else
{
//printf("创建成功\n");
f->s1 = "";
f->s2 = "";
f->len1 = 0;
f->len2 = 0;
f->next = NULL;
return f;
}
}
//8 句子创建2
sentence* create_sentence(string s)
{
sentence* f = create_sentence();
f->s1 = s;
return f;
}
//9 找链尾指针
sentence* findlast_sentence(sentence* head)
{
if (head == NULL)
{
printf("传入的sentence指针有误\n");
return NULL;
}
else
{
sentence* f1 = head;
while (f1->next != NULL)//一直找链尾
{
f1 = f1->next;
}
return f1;
}
}
//10 句子创建3
void create_sentence(sentence* head, const string s)
{
//找链尾
sentence* f = findlast_sentence(head);
sentence* f2 = create_sentence(s);
f->next = f2;
}
//11 文件创建1
Afile* create_Afile()
{
Afile* f = new Afile();
if (f == NULL)
{
printf("创建失败!\n");
return NULL;
}
else
{
f->head = NULL;
f->path = NULL;
f->num = 0;
return f;
}
}
//12 文件创建2 指定路径读取文本 并把文本数据保存(以后不用再打开文件)
Afile* get_file(string path)
{
//读取文件内容 这里假定字符串路径s是正确的
//string s 传入 char*类型也可以
// 1打开文件
FILE* fp;
if ((fp = fopen(path.c_str(), "r")) == NULL) //以只读的方式打开test。
{
printf("读取文件失败!\n");
return NULL;
}
// 2创建文本结构体 创建句子结构体
Afile* a = create_Afile();
sentence* sen = create_sentence();
// 3循环读取 创建句子
// 1)将文件以 ; 作为切割保存在句子结构体里
// 2)初步处理句子内容
// 3)对句子计数
int num = 0;
char ch = ' ';
string str = "";
while (fp != NULL && (ch = fgetc(fp)) != EOF)
{
str.push_back(ch);
//以分号为分割
if (ch == ';')
{
create_sentence(sen, str);
num++;
str = "";
}
}
create_sentence(sen, str);
num++;
// 4将结果保存在文本结构体中
a->head = sen->next;
a->num = num;
// 一定要记得分配空间!!! 不然就会崩掉
a->path = create_filename_list();
a->path->name = path;
// 5关闭文件
if (fp != NULL)
{
fclose(fp); //关闭文件
}
// 6返回文本指针
return a;
}
//13 创建匹配
match* create_match()
{
match* f = new match();
if (f == NULL)
{
printf("创建失败!\n");
return NULL;
}
else
{
f->a_sen = NULL;
f->b_sen = NULL;
f->next = NULL;
f->similar = 0;
return f;
}
}
//14 创建结点
node* create_node()
{
node* f = new node();
if (f == NULL)
{
printf("创建失败!\n");
return NULL;
}
else
{
f->flags = 0;
f->a_file = NULL;
f->b_file = NULL;
f->head = NULL;
f->num = 0;
f->next = NULL;
return f;
}
}
//三、功能函数
//1)输入文件路径进行处理
//1 输入文件路径 看是否存在
bool is_exist(const char* s)
{
//1)输入字符串.cpp,判断是不是文件
if (!_access(s, 0))
{
//printf("%s存在\n", s);
return true;
}
printf("%s不存在\n", s);
return false;
}
//2 输入字符串,判断是不是文件夹
bool is_dir(const char* fileName)
{
struct stat buf;
int result;
result = stat(fileName, &buf);
if (S_IFDIR & buf.st_mode) {
//printf("folder\n");
return true;
}
else if (S_IFREG & buf.st_mode) {
//printf("file\n");
}
else
{
//printf("no\n");
}
return false;
}
//3 判断是不是.cpp文件
bool iscpp(string s)
{
if (!is_exist(s.c_str()))
return false;
string::size_type idx;
idx = s.find(".cpp"); //在a中查找b.
if (idx == string::npos) //不存在。
{
//cout << "不是.cpp\n";
return false;
}
else
{
//printf("是\n");
return true;
}
}
//2)对句子预处理
//4 替换字符串
string& replace_all(string& src, const string& old_value, const string& new_value)
{
// 每次重新定位起始位置,防止上轮替换后的字符串形成新的old_value
for (string::size_type pos(0); pos != string::npos; pos += new_value.length())
{
if ((pos = src.find(old_value, pos)) != string::npos) {
src.replace(pos, old_value.length(), new_value);
}
else break;
}
return src;
}
//5 去除句子中的空格 tab {} ; 换行符等
void fix_sentence(string& s)
{
replace_all(s, " ", "");
replace_all(s, "{", "");
replace_all(s, "}", "");
replace_all(s, ";", "");
replace_all(s, "\n", "");
replace_all(s, "/*", "");
replace_all(s, "*/", "");
replace_all(s, "//", "");
replace_all(s, " ", "");
}
void fix_sentence2(string& s)
{
replace_all(s, " ", "");
}
//6 预处理一个句子sentence
void inil_sentence(sentence* s, int)
{
s->s2 = s->s1;
fix_sentence(s->s2);
fix_sentence2(s->s1);
}
//7 预处理所有句子sentence
void inil_sentence(sentence* s)
{
while (s != NULL)
{
inil_sentence(s, 1);
s = s->next;
}
}
//3)计算两语句的编辑距离参数
//8 编辑距离算法,传入两个字符串,计算编辑距离(长度不超过60)
int ex_dis(string q1, string q2)
{
//1 确定两字符串大小
int len1 = (int)q1.length();
int len2 = (int)q2.length();
const char* s1 = q1.c_str();
const char* s2 = q2.c_str();
//printf("字符串长度%d %d\n", len1, len2);
//没办法 只能分块
/*
* 1 堆区创建二维数组困难,需要多个指针维护
* 2 栈区[100][100]就会超过空间
* 3 将一个字符串分成两份或多份 两两匹配编辑距离(不用求最大最小) 最后求和
*/
//2 创建dp数组
int dp[60][60];
//3 初始化dp
for (int i = 0; i < 60; i++)
{
dp[i][0] = i;
dp[0][i] = i;
}
//4 循环
for (int i = 1; i <= len1; i++)
for (int j = 1; j <= len2; j++)
dp[i][j] = s1[i - 1] == s2[j - 1] ? dp[i - 1][j - 1] : min(dp[i - 1][j - 1], min(dp[i - 1][j], dp[i][j - 1])) + 1;
/*
//5 打印输出
for (int i = 0; i <= len1; i++)
{
for (int j = 0; j <= len2; j++)
printf("%d ", dp[i][j]);
printf("\n");
}
*/
//printf("%s\n%s的编辑距离为%d\n", s1, s2, dp[len1][len2]);
return dp[len1][len2];
}
//9 传入两个字符串 自动拆分为多个小字串计算编辑距离(任意长度)
int calc_dis(string s1, string s2)
{
// 实际上字符串长度较长时,分成多个字串也不会有很大影响
// 因为实际比较时也不会考虑调换顺序的问题,只能增删改
// 分成多个字串还能降低时间复杂度 (n^2)
//1 基本信息
int len1 = (int)s1.length();
int len2 = (int)s2.length();
int x = 0;
int dis = 58;
int score = 0;
while (x < len1 || x < len2)
{
//2 讨论特殊情况 一个字符串无法切割了
// substr(a,b) a最大可以等于字符串长度 超过就会报错
if (x >= len1)
{
score += len2 - len1;
break;
}
if (x >= len2)
{
score += len1 - len2;
break;
}
//3 每次切割dis长度的字串
string s3(s1.substr(x, dis));
string s4(s2.substr(x, dis));
x += dis;
//4 匹配字串
score += ex_dis(s3, s4);
//printf("%s|%s|%d %d\n", s3.c_str(), s4.c_str(), s3.length(), s4.length());
}
//printf("字符串长度%d %d\n", len1, len2);
//printf("%s\n%s的编辑距离为%d\n", s1.c_str(), s2.c_str(), score);
//5 返回编辑距离
return score;
}
//10 编辑比例 (编辑的字符数占总数的比例)
double rate_dis(string s1, string s2, int score)
{
//编辑比例越小,编辑量越少,抄袭可能就越大
int len1 = (int)s1.length();
int len2 = (int)s2.length();
double x1 = score * 1.0 / len1;
double x2 = score * 1.0 / len2;
//printf("编辑比例:%lf %lf\n", x1, x2);
//返回较小值
return x1 < x2 ? x1 : x2;
}
//4)匹配两份文本 保存在一个node里
//11 传入一个sentence 和一个句子链头 找到最匹配的句子 返回一个match
match* find_best(sentence* one, sentence* many, int)
{
//注意 此处是匹配s2
string s = one->s2;
//1 设置初始最佳匹配
int score = calc_dis(s, many->s2);
double min_rate = rate_dis(s, many->s2, score);
sentence* best_sen = many;
many = many->next;
while (many != NULL)
{
//2 计算一次两句子匹配度
int new_score = calc_dis(s, many->s2);
double new_rate = rate_dis(s, many->s2, new_score);
//3 如果匹配更优 则替换
if (new_rate < min_rate)
{
score = new_score;
min_rate = new_rate;
best_sen = many;
}
//4 链尾结束
many = many->next;
}
//5 结果保存在match中
match* m = create_match();
m->a_sen = one;
m->b_sen = best_sen;
m->similar = min_rate;
m->score = score;
return m;
}
//12 多对多句子匹配
match* find_best(sentence* head1, sentence* head2)
{
//1 创建链头结点
match* m_head = create_match();
match* m = m_head;
while (head1 != NULL)
{
//2 计算匹配度并接在match链尾上
m->next = find_best(head1, head2, 1);
m = m->next;
//3 遍历第一条句子链表
head1 = head1->next;
}
//4 返回match链头
return m_head->next;
}
//13 传入两个文件 匹配所有句子
match* find_best(Afile* a, Afile* b)
{
return find_best(a->head, b->head);
}
//14 删除匹配度低的结点
match* fix_match(match* head)
{
//0 一些参数
//阈值 低于th的语句认为重复度高
double th = 0.6;
//1 空指针
if (head == NULL)
{
printf("传进来的是空指针!!\n");
system("pause");
return NULL;
}
//2 一个结点指针
match* m1 = head;
match* m2 = head->next;
while (m2 != NULL)
{
if (m2->similar > th)
{
//3 删除结点
//1)连接前后
m1->next = m2->next;
//2)回收内存
delete m2;
//3)循环
m2 = m1->next;
}
else
{
//3 保留结点
//1)循环
m1 = m1->next;
m2 = m2->next;
}
}
//4 处理头节点
if (head->similar > th)
{
return head->next;
}
return head;
}
//15 初始化结点
void inil_node(node* n)
{
//1 此时结点有两个文件名 需要对两个文件初始化
n->a_file = get_file(n->path1);
n->b_file = get_file(n->path2);
//2 初始化s2
inil_sentence(n->a_file->head);
inil_sentence(n->b_file->head);
//3 初始化匹配结果
n->head = find_best(n->a_file, n->b_file);
//4 根据匹配结果处理信息
//1)删掉重复度低的匹配
n->head = fix_match(n->head);
//2)根据匹配情况计算查重率
repeat_rate(n);
}
//16 在这里完成node的初始化 赋值 计算
void inil_node(node* n, filename_list* f1, filename_list* f2)
{
n->path1 = f1->name;
n->path2 = f2->name;
inil_node(n);
}
//5)匹配两组文件
//17 一个文件对(一个)多个文件
void one_to_many(filename_list* f1, filename_list* head)
{
//0 如果链2没有文件 需要单独讨论
//1 先匹配一次
node* n = create_node();
inil_node(n, f1, head);
f1->head = n;
//2 匹配多次
filename_list* f2 = head;
while (f2->next != NULL)
{
//指向第二个文件
f2 = f2->next;
//3 创建新结点
node* new_node = create_node();
inil_node(new_node, f1, f2);
//4 接在node链尾
n->next = new_node;
n = n->next;
}
//完成
}
//18 多对多
void many_to_many(filename_list* head1, filename_list* head2)
{
//0 如果链1没有文件 需要单独讨论
//1 匹配多次
while (head1 != NULL)
{
one_to_many(head1, head2);
head1 = head1->next;
}
}
//6)其他
//19 计算match链长度
int get_length(match* m)
{
int l = 0;
while (m != NULL)
{
m = m->next;
l++;
}
//printf("此链表长度为%d\n", l);
return l;
}
//20 计算句子数量
int get_length(sentence* s)
{
int num = 0;
while (s != NULL)
{
s = s->next;
num++;
}
//printf("句子数量为%d\n", num);
return num;
}
//21 计算文本数量
int get_length(filename_list* f)
{
int num = 0;
while (f != NULL)
{
f = f->next;
num++;
}
//printf("文件数量为%d\n", num);
return num;
}
//22 根据匹配度计算查重率
void repeat_rate(node* n)
{
//两份代码句子数
int s1_num = get_length(n->a_file->head);
int s2_num = get_length(n->b_file->head);
int m_num = get_length(n->head);
n->rate = 1.0 * m_num / s1_num;
}
//23 是否达到查重标准
bool judge(node* n)
{
if (n->rate > 0.8)
{
printf("根据标准 我们认为这两份代码属于完全抄袭\n");
return true;
}
else if (n->rate > 0.6)
{
printf("根据标准 我们认为这两份代码存在大量抄袭现象\n");
return true;
}
else if (n->rate > 0.3)
{
printf("根据标准 我们认为这两份代码有抄袭嫌疑\n");
return true;
}
else
{
printf("根据标准 我们认为这两份代码抄袭可能性较低\n");
return false;
}
}
//7)打印
void print_percent(double d)
{
d *= 100;
printf("%.2lf%%", d);
}
void print_filename_list(filename_list* f)
{
//printf("打印一次文件名\n");
while (f != NULL)
{
printf("%s\n", f->name.c_str());
f = f->next;
}
printf("\n");
//泛型会不会减少代码量 一个结构体多用
//只加代码 不改代码
}
void print(const string& s)
{
printf("%s\n", s.c_str());
}
void print_sentence1(sentence* f)
{
printf("打印一次文件原句\n");
while (f != NULL)
{
printf("%s\n", f->s1.c_str());
f = f->next;
}
printf("\n");
}
void print_sentence2(sentence* f)
{
printf("打印一次文件处理后句子\n");
while (f != NULL)
{
printf("%s\n", f->s2.c_str());
f = f->next;
}
printf("\n");
}
void print(sentence* s, int flags)
{
//1 打印这句话的s1
if (flags == 1)
{
print(s->s1);
}
//2 打印这句话的s2
if (flags == 2)
{
print(s->s2);
}
}
void print(const char* s, double num)
{
printf("%s%lf\n", s, num);
}
void print(match* m, int)
{
//暂且先改成这样
//print(m->a_sen, 2);
//print(m->b_sen, 2);
print(m->a_sen, 1);
print(m->b_sen, 1);
int l1 = (int)m->a_sen->s2.length();
int l2 = (int)m->b_sen->s2.length();
printf("句子长度为%d %d\n", l1, l2);
//printf("匹配结果是: 编辑距离为%d 编辑比例为%lf\n\n", m->score, m->similar);
printf("匹配结果是: 编辑距离为%d 相似度为", m->score);
print_percent(1 - m->similar);
printf("\n\n");
}
void print(match* m)
{
printf("\n\n");
int num = 1;
while (m != NULL)
{
printf("\n\n\n第%d对句子:\n", num);
print(m, 1);
m = m->next;
num++;
}
}
void print(Afile* a)
{
//原生句子
print_sentence1(a->head);
get_length(a->head);
}
void print(Afile* a, int)
{
//处理句子
print_sentence2(a->head);
get_length(a->head);
}
void print(node* n)
{
if (n == NULL)
{
printf("这个文件是空的!\n");
}
else
{
while (n)
{
printf("\n\n\n");
print(n->a_file->path->name);
printf("和");
print(n->b_file->path->name);
printf("\n");
print(n->head);
//打印注释信息
print(n->a_file->path->name);
print(n->b_file->path->name);
printf("\n\n\n以上是这两份代码的匹配结果\n");
printf("代码1的语句数为%d\n", get_length(n->a_file->head));
printf("代码2的语句数为%d\n", get_length(n->b_file->head));
printf("重复的语句数量为%d 代码查重率为%lf\n", get_length(n->head), n->rate);
judge(n);
printf("\n\n\n");
break;//输出当前结果就可以了
n = n->next;
//system("pause");
}
}
}
void print(filename_list* f)
{
print(f->head);
}
void print_info(node* n)
{
printf("%s\n%s\n", n->a_file->path->name.c_str(), n->b_file->path->name.c_str());
printf("查重率为%lf\n", n->rate);
printf("按%d 了解详细信息\n\n\n", n->code);
}
void print_info(filename_list* f)
{
int code = 0;
while (f != NULL)
{
node* n = f->head;
while (n != NULL)
{
n->code = code;
print_info(n);
code++;
n = n->next;
}
f = f->next;
}
}
bool print_info(filename_list* f, int code)
{
while (f != NULL)
{
node* n = f->head;
while (n != NULL)
{
if (code == n->code)
{
print(n);
return true;
}
n = n->next;
}
f = f->next;
}
return false;
}
//四、测试函数
void test01()
{
//测试1 bool is_exist(const char* s)
is_exist("D:\\C\\VS\\代码查重系统\\02\\源.cpp");
is_exist("源.cpp");
system("pause");
//测试2 string
printf("\n测试2 string\n");
//1)测试基本属性
string s1 = "good";
string s2 = s1;
s2.push_back('y');
printf("%c\n", s2.at(1));
printf("%p\n%p\n", &s1, &s2);
printf("%s %s\n", s1.c_str(), s2.c_str());
//2)测试find
string::size_type idx;
idx = s1.find("od"); //在a中查找b.
if (idx == string::npos) //不存在。
cout << "not found\n";
//3)测试substr
string q9(s1.substr(4, 6));
print(q9);
//printf("%s?长度%d",q9.c_str(),q9.length());
//system("pause");
//测试3 是否为文件
is_dir("D:\\C\\VS\\代码查重系统\\02");
//测试4 文件名链表
//1)前两种创建方法
printf("?1");
filename_list* f1 = create_filename_list();
f1->name = "a文件";
filename_list* f2 = create_filename_list("b文件");
printf("%s %s\n", f1->name.c_str(), f2->name.c_str());
f1->next = f2;
print_filename_list(f1);
//2)第三种创建方法
create_filename_list(f1, "c文件");
create_filename_list(f1, "d文件");
print_filename_list(f1);
//3)找链尾
printf("链尾%s\n", findlast_filename_list(f2)->name.c_str());
//4)第三种方法空创建
//这种方式会创建一个空头 头节点没有内容
filename_list* f4 = create_filename_list();
create_filename_list(f4, "f文件");
printf("第四种方式");
print_filename_list(f4);
//测试5 遍历目录内文件名
filename_list* f5 = dir_Allfile("D:\\C\\VS\\代码查重系统\\02");
print_filename_list(f5);
//测试6 替换字符串
string q2 = " hello e{e}";
print(q2);
replace_all(q2, "h", "xx");
print(q2);
replace_all(q2, "l", "55");
print(q2);
fix_sentence(q2);
print(q2);
//测试7 句子链尾 + 句子初始化
//1)句子链尾
string q3 = " 句子1";
string q4 = "句 子2";
string q5 = "句子{{3}}";
sentence* ss1 = create_sentence();
ss1->s1 = q3;
sentence* ss2 = create_sentence(q4);
ss1->next = ss2;
create_sentence(ss1, q5);
print_sentence1(ss1);
sentence* ss3 = findlast_sentence(ss1);
print_sentence1(ss3);
//2)句子初始化
inil_sentence(ss1);
print_sentence1(ss1);
print_sentence2(ss1);
//测试8 读取文件
FILE* fp;
char ch = ' ';
if ((fp = fopen("D:\\C\\VS\\代码查重系统\\02\\源2.cpp", "r")) == NULL) //以只读的方式打开test。
{
printf("ERROR");
}
while (fp != NULL && (ch = fgetc(fp)) != EOF)
{
putchar(ch);
//仅为测试
break;
}
if (fp != NULL)
{
fclose(fp); //关闭文件
}
//测试9 测试函数 Afile* get_file(string s)
string ss = "D:\\C\\VS\\代码查重系统\\02\\源2.cpp";
Afile* a = get_file(ss);
print_sentence1(a->head);
//测试10 编辑距离
ex_dis("hio", "hello");
ex_dis("h", "h");
ex_dis("hi", "he");
ex_dis("hio", "heo");
ex_dis("hsdsio", "hello");
ex_dis("dp[i][j] = i * j;printf(\"%d \", dp[i][j]);}", "hello");
int so1 = calc_dis("hsdsio", "hello");
int so2 = calc_dis(" for (int i = 1; i <= len1; i++)\n for (int j = 1;\n dp[i][j] = s1[i - 1] == s2[j - 1] ? dp[i - 1][j - 1] : min(dp[i - 1][j - 1], min(dp[i - 1][j], dp[i][j - 1])) + 1; ", " for (int i = 1; i <= len1; i++)\n for (int j = 1; j <= len2; j++)\n dp[i][j] ] ? dp[i - 1][j - 1] : min(dp[i - 1][j - 1], min(dp[i - 1][j], dp[i][j - 1])) + 1; ");
rate_dis("hsdsio", "hello", so1);
rate_dis(" for (int i = 1; i <= len1; i++)\n for (int j = 1;\n dp[i][j] = s1[i - 1] == s2[j - 1] ? dp[i - 1][j - 1] : min(dp[i - 1][j - 1], min(dp[i - 1][j], dp[i][j - 1])) + 1; ", " for (int i = 1; i <= len1; i++)\n for (int j = 1; j <= len2; j++)\n dp[i][j] ] ? dp[i - 1][j - 1] : min(dp[i - 1][j - 1], min(dp[i - 1][j], dp[i][j - 1])) + 1; ", so2);
//测试11 编辑距离2
calc_dis("a", "a");
calc_dis("a", "aaaa");
calc_dis("a", "abc");
calc_dis("a", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
calc_dis("a", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
//测试12 打印句子+匹配
//1)打印句子
sentence* q6 = create_sentence("a");
sentence* q7 = create_sentence("a");
create_sentence(q7, "ab");
create_sentence(q7, "abc");
create_sentence(q7, "abcd");
create_sentence(q7, "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
create_sentence(q6, "b");
create_sentence(q6, "z");
create_sentence(q6, "bc");
create_sentence(q6, "12345678");
create_sentence(q6, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
print_sentence1(q6);
print_sentence1(q7);
print(q6, 1);
print(q7, 1);
//2)初始化s2
inil_sentence(q6);
inil_sentence(q7);
print_sentence2(q6);
print_sentence2(q7);
print(q6, 2);
print(q7, 2);
//3)匹配
match* m = find_best(q6, q7);
print(m);
}
void test02()
{
//测试13 两文本匹配
printf("----------分割线----------\n\n\n");
//1)创建结点
node* n = new node();
n->path1 = "D:\\C\\VS\\代码查重系统\\02\\源2.cpp";
n->path2 = "D:\\C\\VS\\代码查重系统\\02\\源3.cpp";
//2)初始化结点
inil_node(n);
//3)查看结点的两文本
//print(n->a_file);
//print(n->b_file);
print(n->a_file, 1);
print(n->b_file, 1);
print(n->head);
//测试14 一对一
filename_list* k1 = create_filename_list("D:\\C\\VS\\代码查重系统\\02\\源2.cpp");
filename_list* k2 = create_filename_list("D:\\C\\VS\\代码查重系统\\02\\源3.cpp");
one_to_many(k1, k2);
print(k1->head);
print(k2->head);
//测试15 一对多
filename_list* k3 = create_filename_list("D:\\C\\VS\\代码查重系统\\02\\源2.cpp");
filename_list* k4 = dir_Allfile("D:\\C\\VS\\代码查重系统\\02");
one_to_many(k3, k4);
print(k3->head);
print(k4->head);
}
void test03()
{
//测试16 多对多
filename_list* k5 = dir_Allfile("D:\\C\\VS\\代码查重系统\\02");
filename_list* k6 = dir_Allfile("D:\\C\\VS\\代码查重系统\\02");
many_to_many(k5, k6);
filename_list* k7 = k5;
while (k7 != NULL)
{
print(k7);
k7 = k7->next;
system("pause");
}
}
//五、ui交互
void start(filename_list* f1, filename_list* f2)
{
system("cls");
printf("加载中...");
many_to_many(f1, f2);
loou:
system("cls");
print_info(f1);
printf("按对应编号选择\n");
printf("按 -1 退出\n");
while (true)
{
int code = 0;
cin >> code;
if (print_info(f1, code))
{
system("pause");
goto loou;
return;
}
else if (code == -1)
{
exit(0);
}
else
{
printf("您的输入不合法\n");
}
}
}
void face03()
{
system("cls");
printf("\n\n");
printf("\t请输入第一组文件路径\n");
printf("\t请输入第二组文件路径\n");
printf("\t在这里您只需输入两组文件路径,可以是文件名,也可以是目录名,系统将为您比对这两组文件中的相似度 例如:\n");
printf("\t1.一对一查询:可以依次输入一份cpp文件路径,如\n");
printf("\t\t\tD:\\\\C\\\\VS\\\\代码查重系统\\\\02\\\\源.cpp\n");
printf("\t\t\t源2.cpp\n");
printf("\t2.一对多查询:可以先输入一份cpp文件路径,再输入一个目录路径,如\n");
printf("\t\t\t源.cpp\n");
printf("\t\t\tD:\\\\C\\\\VS\\\\代码查重系统\\\\02\n");
printf("\t3.多对一查询:可以先输入一个目录路径,再输入一份cpp文件路径,如\n");
printf("\t\t\tD:\\\\C\\\\VS\\\\代码查重系统\\\\02\n");
printf("\t\t\t源.cpp\n");
printf("\t4.多对多查询:可以依次输入一个目录的路径,如\n");
printf("\t\t\tD:\\\\C\\\\VS\\\\代码查重系统\\\\01\n");
printf("\t\t\t02\n");
printf("\t5.混杂查询:如果您想查询一批代码中是否有两两抄袭现象,可以两次输入同一个文件夹路径,如\n");
printf("\t\t\tD:\\\\C\\\\VS\\\\代码查重系统\\\\02\n");
printf("\t\t\tD:\\\\C\\\\VS\\\\代码查重系统\\\\02\n");
printf("\n\t../返回上一级\n");
printf("\t\\\\(或者/)进入下一级\n");
printf("\t按任意键继续");
char c = _getch();
face02();
}
void face02()
{
system("cls");
printf("\n\n\n\n\n");
printf("\t\t\t\t\t\t请输入第一组文件路径\n\n");
printf("\t\t\t\t\t\t请输入第二组文件路径\n\n");
printf("\t\t\t\t\t\t 按 1 获取帮助\n");
printf("\t\t\t\t\t\t 按 2 查看示例\n");
printf("\t\t\t\t\t\t 按 0 退出\n");
string s1;
string s2;
filename_list* f1;
filename_list* f2;
int num1 = 0;
int num2 = 0;
while (true)
{
printf("\n请输入第一组文件路径:");
cin >> s1;
if (s1 == "0")
{
exit(0);
}
if (s1 == "1")
{
face03();
return;
}
if (s1 == "2")
{
looc:
printf("\n\n将为您展示这两个文件夹下的代码查重\n");
s1 = "代码库1";
s2 = "代码库2";
f1 = get_files(s1);
f2 = get_files(s2);
num1 = get_length(f1);
num2 = get_length(f2);
printf("第一组文件夹为%s\n", s1.c_str());
printf("此文件中有 %d 份.cpp文件\n", num1);
print_filename_list(f1);
printf("第二组文件夹为%s\n", s2.c_str());
printf("此文件中有 %d 份.cpp文件\n", num2);
print_filename_list(f2);
goto loop;
}
if (iscpp(s1))
{
f1 = get_files(s1);
printf("第一组文件为%s\n", s1.c_str());
break;
}
else if (is_dir(s1.c_str()))
{
f1 = get_files(s1);
num1 = get_length(f1);
if (num1 == 0)
{
printf("此文件中.cpp文件份数为0 请重新输入\n");
}
else
{
printf("此文件中有 %d 份.cpp文件\n", num1);
print_filename_list(f1);
break;
}
}
}
while (true)
{
printf("\n请输入第二组文件路径:");
cin >> s2;
if (s2 == "0")
{
exit(0);
}
if (s2 == "1")
{
face03();
return;
}
if (s2 == "2")
{
goto looc;
}
if (iscpp(s2))
{
f2 = get_files(s2);
printf("第2组文件为%s\n", s2.c_str());
break;
}
else if (is_dir(s2.c_str()))
{
f2 = get_files(s2);
num2 = get_length(f2);
if (num2 == 0)
{
printf("此文件中.cpp文件份数为0 请重新输入\n");
}
else
{
printf("此文件中有 %d 份.cpp文件\n", num2);
print_filename_list(f2);
break;
}
}
}
loop:
system("pause");
start(f1, f2);
}
void face01()
{
system("cls");
printf("\n\n\n\n\n");
printf("\t\t\t\t\t\t欢迎来到代码查重系统\n\n\n");
printf("\t\t\t\t\t\t 按 0 开始查询\n");
int c = 0;
while (1)
{
c = _getch();
if (c == '0')
{
face02();
break;
}
//printf("%d",c);
//printf("%c",c);
}
}
int main()
{
//test01();
//test02();
//test03();
//主界面
face01();
}