文章目录
查找
这部分存在的意义是什么?直接顺序查找不就完了?这部分存在的意义就是“计算思维”——时间和空间都寸土寸金的思维。
首先,我们总述一下各种查找算法的时间复杂度:
顺序查找法
从表的第一个记录开始,将用户给出的关键字值与当前被查找记录的关键字值进行比较,若匹配,则查找成功,给出被查到的记录在表中的位置,查找结束。若所有n 个记录的关键字值都已比较,不存在与用户要查的关键字值匹配的记录,则查找失败,给出信息0。太简单了,自行给出代码。
折半查找法
首先,**要保证查找表是有序的。**将要查找的关键字值与当前查找范围内位置居中的记录的关键字的值进行比较。
若匹配,则查找成功,给出被查到记录在文件中的位置,查找结束。
若要查找的关键字值小于位置居中的记录的关键字值,则到当前查找范围的前半部分重复上述查找过程,否则,到当前查找范围的后半部分重复上述查找过程,直到查找成功或者失败。
若查找失败,则给出错误信息(0)。
代码
我们只介绍非递归:
int binsearch(keytype key[], int n, keytype k){
int low = 0, high = n - 1, mid;
while (low <= high) {
mid = (low + high) / 2;
if (k == key[mid])
return mid;
if (k > key[mid])
low = mid + 1;
else
high = mid–1;
}
return -1;
}
索引
这里只介绍非稠密索引——分块索引。
在非稠密索引(分块)文件中查找一个记录存在与否的过程是:先查找索引表 (确定被哈找记录所在快),然后在相应块中查找被查记录存在与否。
在写代码过程中,其实实现原理很简单。比如在英文词典中查找一个单词,先确定首字母,形成一个区间,再在这个区间里边找,用顺序、二分什么的都可以。
二叉查找树(BST)
更详细的部分我们在树的部分已经介绍。我们回顾一下:
定义与解释
二叉排序树(也称二叉查找树)或者是一棵空树,或者是具有下列特性的二叉树:
- 若左子树非空,则左子树上所有结点的值均小于根结点的值。
- 若右子树非空,则右子树上所有结点的值均大于根结点的值。
- 左、右子树也分别是一棵二叉排序树。
根据二叉排序树的定义,左子树结点值<根结点值<右子树结点值,所以对二叉排序树进行中序遍历,可以得到一个递增的有序序列。例如,下图所示二叉排序树的中序遍历序列为123468。
代码
/*二叉树的二叉链表结点结构定义*/
typedef struct BiTNode
{
int data; //结点数据
struct BiTNode *lchild, *rchild; //左右孩子指针
} BiTNode;
BTNodeptr searchBST(BTNodeptr p, Datatype item){
if (p == NULL) {
p = (BTNodeptr)malloc(sizeof(BTNode));
p->data = item;
p->lchild = p->rchild = NULL;
}
else if (item < p->data)
p->lchild = insertBST(p->lchild, item);
else if (item > p->data)
p->rchild = insertBST(p->rchild, item);
else
do - something; //找到该元素
return p;
}
Trie树
BST通常不是一棵平衡树,它的树结构与输入数据的顺序有很大的关系,它很难达到理想的O(log2n)查找性能。对于像单词表(字典)这样的数据,有没有更好的数据结构呢?了解一下即可。
B树
定义
B树,又称多路平衡查找树,B树中所有结点的孩子个数的最大值称为B树的阶,通常用 m 表示。一棵 m 阶B树或为空树,或为满足如下特性的 m 叉树:
- 树中每个结点至多有 m棵子树,即至多含有 m − 1 个关键字。
- 若根结点不是终端结点,则至少有两棵子树。
- 除根结点外的所有非叶结点至少有 ⌈ m / 2 ⌉ 棵子树,即至少含有 ⌈ m / 2 ⌉ − 1 个关键字。
- 所有非叶结点的结构如下:
- 所有的叶结点都出现在同一层次上,并且不带信息(可以视为外部结点或类似于折半查找判定树的查找失败结点,实际上这些结点不存在,指向这些结点的指针为空)。
B-树
定义
下面我们看一个例子:
更多的内容详见BUAA数据结构PPT,这部分内容在这里不多做阐释。
散列查找(哈希表)
能否有一种不经过任何关键字值的比较或者经过很少次的关键字值的比较就能够达到目的方法?哈希表。需要建立记录的关键字和记录的存储位置之间的关系。
这里我们把这种对应关系 f f f称为散列函数,又称为哈希(Hash) 函数。按这个思想,采用散列技术将记录存储在一块连续的存储空间中,这块连续存储空间称为散列表或哈希表(Hash table)。那么关键字对应的记录存储位置我们称为散列地址。
散列函数可能会把两个或两个以上的不同关键字映射到同一地址,称这种情况为冲突,这些发生碰撞的不同关键字称为同义词。一方面,设计得好的散列函数应尽量减少这样的冲突;另一方面,由于这样的冲突总是不可避免的,所以还要设计好处理冲突的方法。
理想情况下,对散列表进行查找的时间复杂度为 O ( 1 ) ,即与表中元素的个数无关。
哈希函数的构造法
主要靠使用现成的哈希函数。因为最优化本来就需要大量的尝试,自己胡搞反而会弄巧成拙。这里简单介绍几种:
处理散列冲突
任何设计出来的散列函数都不可能绝对地避免冲突。为此,必须考虑在发生冲突时应该如何处理,即为产生冲突的关键字寻找下一个“空”的Hash地址。
用 Hi表示处理冲突中第 i 次探测得到的散列地址,假设得到的另一个散列地址H1仍然发生冲突,只得继续求下一个地址 H2,以此类推,直到 Hk不发生冲突为止,则 Hk为关键字在表中的地址。
同样,这一般靠现成的。这里简单介绍几种:
- 开放地址法
所谓的开放定址法就是一旦发生了冲突,就去寻找下一个空的散列地址,只要散列表足够大,空的散列地址总能找到,并将记录存入。
- 邻接表
不换位置。将所有关键字为同义词的记录存储在一个单链表中,我们称这种表为同义词子表,在散列表中只存储所有同义词子表的头指针。
综合运用例
BUAA2024春数据结构大作业——问题描述
【问题描述】
代码相似性(或代码克隆)检测广泛应用于代码优化、知识产权保护和教学领域。根据下面方法,对给定的一组C程序代码进行相似检测,并按要求输出相似性检测结果。
代码相似性检测方法:
1.扫描每个程序,获取每个程序中用户定义函数调用关系(是指main函数直接调用其它用户定义函数情况),以及用户定义函数的关键信息流。程序中提供了实现代码的函数即为用户定义函数。
函数关键信息流:是指对用户定义函数体中去掉非关键信息后得到的信息流串,其获取方法如下:
a) 删除代码中所有空白(空格、制表符及回车符)字符(‘\n’,‘\r’,‘\t’,’ ');
b)针对程序代码中标识符,保留函数体中关键字及标准库函数名,其它调用用户函数名统一用“FUNC”串替换,除此之外删除所有其它标识符(标识符定义见C语言教材)。
课程平台下载区文件“project2024.zip”中keepwords.txt文件包含了ANSI C语言关键字表和标准库函数列表。
c) 函数关键信息流包括函数体的开始’{’和结束’}’,以及所有其它字符。
2.生成每个程序的关键信息流,具体方法如下:每个程序从main函数开始,按函数调用序,依次将主函数和其调用的用户定义函数的关键信息流合并一起得到程序的关键信息流。注意为了简化算法,只将main函数直接调用的用户定义函数信息流合并一起,间接调用(即第二层调用)不用考虑,而且在main函数中重复调用的函数只合并一次。假如某程序A,main函数先后调用了fa, fb, fa, fc等函数,fa函数又调用了fd函数,只须依次将main函数、fa函数、fb函数和fc函数的关键信息流合并在一起得到该程序的关键信息流。
3.计算每个程序与其它程序的相似度值。两程序相似度值的计算方法如下:
a) 调用函数editdistDP计算2个程序间的编辑距离editDistance:
editDistance = editdistDP(P1_keyinfo, P2_keyinfo)
函数editdistDP的参数分别为2个程序的关键信息流串,返回值为编辑距离(≥0的整数)。文件editdistDP.c包含了函数editdistDP的实现代码,可从课程平台下载区下载文件“project2024.zip”,其中包含了editdistDP.c文件,请将该文件中全部内容粘贴到本人综合作业的代码中,以便在程序中调用editdistDP函数。
b) 根据两个程序间编辑距离,按下面公式计算两个程序的相似度:
Sim = 1 - (double) editDistance / MAX(P1_len, P2_len)
其中, sim为两个程序的相似度(为double类型);P1_len和 P2_len分两个程序关键信息流串的长度;函数MAX得到2个正整数的较大数。
3.按相似度值阈值(在此,相似度值阈值设置为0.95)对程序进行代码相似分类,并按程序出现序依次在屏幕上输出代码相似性检测结果。程序代码相似分类方法如下:
a) 从给定的第一个程序开始,列出所有与之相似度大于某阈值的程序,将所有涉及的程序记为集合C1。
b) 若第二个程序不在C1中,列出所有与之相似度大于某阈值的程序。所有涉及的程序记为C2;否则检测第三个程序。但要注意:C1与C2有可能有交集,即:某程序可能与第一个程序相似度大于阈值,与第二个程序的相似度也大于阈值。
c) 若第三个程序不在C1,C2中,列出所有与之相似度大于某阈值的程序。所有涉及的程序记为C3…。
d) 依次类推,直至找到所有程序高相似的其它程序为止
相关说明:
1. 编辑距离:字符串编辑距离(Edit Distance),是俄罗斯科学家Vladimir Levenshtein提出的概念。两个字符串之间的最小编辑距离就是指把一个字符串转换为另一个字符串时,所需要的最小编辑操作(插入、删除、替换)的次数。在此给出了一个计算两个串之间最小编辑距离算法的C实现,可从课程下载区获得相应代码,粘贴到自己编写的代码中直接使用。有关串最小编辑距离算法在网上有多个经典实现,相关原理及实现可上网查找。
2. 标识符:在C语言中,标识符是指以下横线(_)或字母开头的字母数字串,可用于变量名、函数名、类型名、常量名、结构名…。
3. 函数定义形式:在C语言中,函数定义形式如下:
<类型> 函数名(形参列表){ 函数体 }
在处理程序时,当遇到一个标识符,其后是“(…){…}” 则该标识符是一个用户定义函数,以此来识别用户定义函数名及函数体,注意形参列表中可能会有()嵌套、函数体中也有{}嵌套。提示:可用一个标记变量来解决括号匹配问题,变量初值为0,当遇到左括号时,变量加1,遇到右括号时变量减1,当标记变量重新为0时表明2个括号是匹配的。
【输入形式】
课程平台下载区文件“project2024.zip”中包含了codes.txt、keepwords.txt和editdistDP.c等与作业实现相关的文件。codes.txt为需要进行查重的程序代码、keepwords.txt为ANSI C关键字及标准库函数列表、editdistDP.c为一种计算编辑距离算法的实现代码。
codes.txt文件中包含一组需要查重的C程序代码,每个程序首先为程序编号(一组数字),然后是实现代码(程序中函数的实现代码,注意它们不一定是按照执行顺序排列,如main函数可能出现在最前面,也可能在最后面),各程序代码以分页符\f结束,也就是说各程序间以\f字符分隔。
在此,为了简化算法实现,codes.txt文件中的程序代码数据已经过了数据清洗(数据清洗是自然语言处理NLP领域常用方法),具体做法是删除了程序代码中一些按照本文方法进行代码相似比较时不影响算法精度的一些内容,如程序的注释、字符常量和字符串常量中的内容,同时也删除了函数实现代码以外的其它代码(如包含文件、全局变量、外部数据结构定义、函数返回类型等)。因此,在处理程序代码时不用考虑注释、字符常量和字符串常量中是否含有如(、)、{、}等特定内容。
由于Windows系统下文本文件中的’\n’回车符在(评测环境)Linux系统下会变为’\r’和’\n’2个字符,建议用fscanf(fp,”%s”,…)来读取keepwords.txt文件中的关键字及标准库函数名。
【输出形式】
按程序代码在codes.txt文件中出现序输出聚类后的相似程序编号到屏幕上,即首先输出与文件中第一个程序相似(如有的话)的一组程序编号(也按代码出现序),程序编号间以一个空格分隔,最后有一个回车;然后输出与文件中第二个程序相似(如有的话)的一组程序编号(也按代码出现序),以此类推。若所给程序代码中无相似程序,则无输出。详见【样例输出】。
【样例输入】
从课程平台下载区下载文件“project2024.zip”到本地,按本文方法对codes.txt文件中的程序代码进行相似比较,并将比较结果聚类后按要求格式输出到屏幕上。
【样例输出】
程序正确运行后屏幕输出结果为:
100009 100020 100025
100018 110014
【样例说明】
100009、100020、100025为一组相似代码;100018、110014为另一组相似代码,组内和组间的第1个程序编号均按程序代码出现序。
为了方便同学们调试综合作业代码,将针对codes.txt中前2个程序数据的中间处理信息(如程序每个函数的关键信息流、程序关键信息流和相似度值等信息)放到debuginfo.txt文件中(包含在“project2024.zip”文件中),同学们可参考(可将这2个程序数据摘出放到一个XXX.txt文件中来调试作业程序)。
【评分标准】
本题为综合功能测试题,其评分标准为通过测试数据即可得分。程序运行无结果或结果错误将不得分。
代码样例
#define _CRT_SECURE_NO_WARNINGS
#define max2(a,b) ((a)>(b)?(a):(b))
#define TABLE_SIZE 737
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
typedef struct hashNode {
char* word;
struct Node* next;
}hashNode;
typedef struct _Function {
//记录函数
char func[50]; //函数名
char funcSecret[2048]; //函数关键信息流
}Function;
typedef struct _student {
char sequence[20]; //代码序号
char codeSecret[2048]; //程序关键信息流
int flag; //记录是否被其他代码查重过
char tempCodes[10000]; //原始代码
Function function[100]; //用户代码中的函数
int cntFunction; //用户代码中函数的个数
char funcInMain[20][50]; //main函数中的所有调用函数。按照顺序存储
int cntFuncInMain; //main函数中调用函数的个数
}Student;
int** Dp, MaxDP = 3300; //for dynamic programming
Student student[300];
int cntStudent; //代码段的总数
char keepwords[1024][16];
int cntKeepwords; //keepwords总个数
hashNode* hashTable[TABLE_SIZE]; //查找keepwords的哈希表
//动态规划函数
int min3(int a, int b, int c);
int error2(char* s);
int initDP();
int editdistDP(char* str1, char* str2);
//djb2算法 哈希查找keepwords
unsigned int hash(const char* str);
void insert(const char* word);
void readKeepwords();
int searchKeepwords(char str[]);
char* Strdup(const char* s);
int searchFunc(int cntStudent, char word[]);
int searchFuncInMain(int cntStudent, char word[]);
void createFuncSecret(int cntStudent);
void createCodeSecret(int cntStudent);
void createTempCodes(FILE* fp);
void output();
int main()
{
FILE* fp1 = fopen("keepwords.txt", "r");
FILE* fp2 = fopen("codes.txt", "r");
readKeepwords(fp1);
createTempCodes(fp2);
output();
fclose(fp1);
fclose(fp2);
return 0;
}
int min3(int a, int b, int c)
{
int min = a < b ? a : b;
return min < c ? min : c;
}
int error2(char* s)
{
fprintf(stderr, "%s\n", s);
exit(-1);
}
int initDP()
{
int i;
Dp = (int**)malloc(MaxDP * sizeof(int*));
for (i = 0; i < MaxDP; i++)
Dp[i] = (int*)malloc(MaxDP * sizeof(int));
return 0;
}
int editdistDP(char* str1, char* str2)
{
int i, j;
int len1, len2;
static int flag = 0;
(flag++) ? 1 : initDP();
len1 = strlen(str1) + 1; len2 = strlen(str2) + 1;
(max2(len1, len2) >= MaxDP) ? error2("DP memory error!") : len1;
for (i = 0; i <= len1; i++) {
for (j = 0; j <= len2; j++) {
if (i == 0)
Dp[i][j] = j;
else if (j == 0)
Dp[i][j] = i;
else if (str1[i - 1] == str2[j - 1])
Dp[i][j] = Dp[i - 1][j - 1];
else
Dp[i][j] = 1 + min3(Dp[i][j - 1], Dp[i - 1][j], Dp[i - 1][j - 1]);
}
}
return Dp[len1][len2];
}
unsigned int hash(const char* str) {
// 优化的哈希函数(djb2算法)
unsigned int hash = 5381;
int c;
while ((c = *str++)) {
hash = ((hash << 5) + hash) + c; // hash * 33 + c
}
return hash % TABLE_SIZE;
}
char* Strdup(const char* s) {
char* d = malloc(strlen(s) + 1); // 分配内存
if (d == NULL) return NULL; // 分配失败,返回 NULL
strcpy(d, s); // 复制字符串
return d; // 返回副本的指针
}
void insert(const char* word) {
// 插入单词到哈希表
unsigned int index = hash(word);
hashNode* newNode = (hashNode*)malloc(sizeof(hashNode));
newNode->word = Strdup(word);
newNode->next = hashTable[index];
hashTable[index] = newNode;
}
void readKeepwords() {
char keepword[165][50] = {
"auto", "double", "int", "struct", "break", "else", "long", "switch", "case",
"enum", "register", "typedef", "char", "extern", "return", "union", "const",
"float", "short", "unsigned", "continue", "for", "signed", "void", "default",
"goto", "sizeof", "volatile", "do", "if", "while", "static", "fopen",
"freopen", "fflush", "fclose", "remove", "rename", "tmpfile", "tmpnam",
"setvbuf", "setbuf", "fprintf", "printf", "sprintf", "fscanf", "scanf",
"sscanf", "fgetc", "fgets", "fputc", "fputs", "getc", "getchar", "gets",
"putc", "putchar", "puts", "ungetc", "fread", "fwrite", "fseek", "ftell",
"rewind", "fgetpos", "fsetpos", "clearerr", "feof", "ferror", "perror",
"isalnum", "isalpha", "iscntrl", "isdigit", "isgraph", "islower", "isprint",
"ispunct", "isspace", "isupper", "isxdigit", "tolower", "toupper", "strcpy",
"strncpy", "strcat", "strncat", "strcmp", "strncmp", "strchr", "strrchr",
"strapn", "strcapn", "strpbrk", "strstr", "strlen", "strerror", "strtok",
"memcpy", "memmove", "memcmp", "memchr", "memset", "sin", "cos", "tan", "asin",
"acos", "atan", "atan2", "sinh", "cosh", "tanh", "exp", "log", "log10", "pow",
"sqrt", "ceil", "floor", "fabs", "ldexp", "frexp", "modf", "fmod", "atof",
"atoi", "atol", "strtod", "strtol", "strtoul", "rand", "srand", "calloc",
"malloc", "realloc", "free", "abort", "exit", "atexit", "system", "getenv",
"bsearch", "qsort", "abs", "labs", "div", "ldiv", "assert", "va_start",
"va_arg", "va_end", "setjmp", "longjmp", "signal", "raise", "clock", "time",
"difftime", "mktime", "asctime", "ctime", "gmtime", "localtime", "strftime"
};
for (int i = 0; i < 165; i++) {
insert(keepword[i]);
}
}
int searchKeepwords(char str[]) {
unsigned int index = hash(str);
hashNode* current = hashTable[index];
while (current) {
if (strcmp(current->word, str) == 0) {
return 1;
}
current = current->next;
}
return 0;
}
int searchFunc(int cntStudent, char word[]) {
int i;
for (i = 0; i < student[cntStudent].cntFunction; i++) {
if (strcmp(student[cntStudent].function[i].func, word) == 0) {
return i;
}
}
return -1;
}
int searchFuncInMain(int cntStudent, char word[]) {
int i;
for (i = 0; i < student[cntStudent].cntFuncInMain; i++) {
if (strcmp(student[cntStudent].funcInMain[i], word) == 0) {
return 1;
}
}
return 0;
}
void createFuncSecret(int cntStudent) {
//生成函数关键信息流
int i, j;
int flag = 0;
int len = strlen(student[cntStudent].tempCodes);
char word[50] = { 0 };
int cnt1;
int p = -1;
for (i = 0; i < len; i++) {
//录入代码序号
if (flag == 0) {
j = 0;
while (student[cntStudent].tempCodes[i] == ' ') {
i++;
}
while (isdigit(student[cntStudent].tempCodes[i])) {
student[cntStudent].sequence[j++] = student[cntStudent].tempCodes[i++];
}
flag = 1;
}
//清空word
memset(word, '\0', 50);
//检测标识符
j = 0;
if (isalpha(student[cntStudent].tempCodes[i]) || student[cntStudent].tempCodes[i] == '_') {
while (isalpha(student[cntStudent].tempCodes[i]) || isdigit(student[cntStudent].tempCodes[i]) || student[cntStudent].tempCodes[i] == '_') {
word[j++] = student[cntStudent].tempCodes[i++];
}
}
//检测函数调用,替换成FUNC
p = -1;
if (word[0] != '\0' && searchFunc(cntStudent, word) != -1) {
p = searchFunc(cntStudent, word);
j = i;
cnt1 = 0;
if (student[cntStudent].tempCodes[j] == '(') {
cnt1++;
j++;
while (cnt1 != 0) {
if (student[cntStudent].tempCodes[j] == '(') {
cnt1++;
}
else if (student[cntStudent].tempCodes[j] == ')') {
cnt1--;
}
j++;
}
while (student[cntStudent].tempCodes[j] == ' ') {
j++;
}
if (student[cntStudent].tempCodes[j] == '{') {
strcat(student[cntStudent].function[p].funcSecret, "{");
//printf("%s\n{", word);
//处理该函数中的内容
cnt1 = 1;
i = j;
i++;
while (cnt1 != 0) {
//清空word
memset(word, '\0', 50);
//检测标识符
j = 0;
if (isalpha(student[cntStudent].tempCodes[i]) || student[cntStudent].tempCodes[i] == '_') {
while (isalpha(student[cntStudent].tempCodes[i]) || isdigit(student[cntStudent].tempCodes[i]) || student[cntStudent].tempCodes[i] == '_') {
word[j++] = student[cntStudent].tempCodes[i++];
}
}
if (searchKeepwords(word) == 1) {
strcat(student[cntStudent].function[p].funcSecret, word);
//printf("%s", word);
}
else if (searchFunc(cntStudent, word) != -1) {
strcat(student[cntStudent].function[p].funcSecret, "FUNC");
//printf("FUNC");
}
//检查大括号
if (student[cntStudent].tempCodes[i] == '{') {
cnt1++;
}
else if (student[cntStudent].tempCodes[i] == '}') {
cnt1--;
}
//删除空白
if (student[cntStudent].tempCodes[i] == ' ') {
while (student[cntStudent].tempCodes[i] == ' ') {
i++;
}
continue;
}
student[cntStudent].function[p].funcSecret[strlen(student[cntStudent].function[p].funcSecret)] = student[cntStudent].tempCodes[i];
//printf("%c", student[cntStudent].tempCodes[i]);
i++;
}
i--;
}
}
}
//printf("\n");
if (p != -1 && strcmp(student[cntStudent].function[p].func, "main") == 0) {
strcpy(student[cntStudent].codeSecret, student[cntStudent].function[p].funcSecret);
//printf("%s", student[cntStudent].function[p].funcSecret);
}
}
}
void createCodeSecret(int cntStudent) {
int i, j;
int len = strlen(student[cntStudent].tempCodes);
char word[50] = { 0 };
int cnt1, cnt2;
for (i = 0; i < len; i++) {
memset(word, '\0', 50);
j = 0;
if (isalpha(student[cntStudent].tempCodes[i]) || student[cntStudent].tempCodes[i] == '_') {
while (isalpha(student[cntStudent].tempCodes[i]) || isdigit(student[cntStudent].tempCodes[i]) || student[cntStudent].tempCodes[i] == '_') {
word[j++] = student[cntStudent].tempCodes[i++];
}
}
if (strcmp(word, "main") == 0) {
break;
}
}
//此时,i定位到main后面第一个字符
while (student[cntStudent].tempCodes[i] == ' ') {
i++;
}
if (student[cntStudent].tempCodes[i] == '(') {
i++;
cnt1 = 1;
while (cnt1 != 0) {
if (student[cntStudent].tempCodes[i] == '(') {
cnt1++;
}
else if (student[cntStudent].tempCodes[i] == ')') {
cnt1--;
}
i++;
}
}
while (student[cntStudent].tempCodes[i] == ' ') {
i++;
}
//此时i定位到{
cnt1 = 1;
i++;
while (cnt1 != 0) {
//清空word
memset(word, '\0', 50);
//检测标识符
j = 0;
if (isalpha(student[cntStudent].tempCodes[i]) || student[cntStudent].tempCodes[i] == '_') {
while (isalpha(student[cntStudent].tempCodes[i]) || isdigit(student[cntStudent].tempCodes[i]) || student[cntStudent].tempCodes[i] == '_') {
word[j++] = student[cntStudent].tempCodes[i++];
}
}
if (word[0] != '\0' && searchFunc(cntStudent, word) != -1) {
cnt2 = 0;
if (student[cntStudent].tempCodes[i] == '(') {
cnt2++;
i++;
while (cnt2 != 0) {
if (student[cntStudent].tempCodes[i] == '(') {
cnt2++;
}
else if (student[cntStudent].tempCodes[i] == ')') {
cnt2--;
}
i++;
}
if (searchFuncInMain(cntStudent, word) == 0) {
//没找到
strcpy(student[cntStudent].funcInMain[student[cntStudent].cntFuncInMain++], word);
strcat(student[cntStudent].codeSecret, student[cntStudent].function[searchFunc(cntStudent, word)].funcSecret);
}
}
}
if (student[cntStudent].tempCodes[i] == '{') {
cnt1++;
}
else if (student[cntStudent].tempCodes[i] == '}') {
cnt1--;
}
//删除空白
if (student[cntStudent].tempCodes[i] == ' ') {
while (student[cntStudent].tempCodes[i] == ' ') {
i++;
}
continue;
}
i++;
}
}
void createTempCodes(FILE* fp) {
//读入代码并获取所有自定义函数名称
char ch;
int index = 0;
int i, j;
int len;
char word[50] = { 0 }; //用于判断一个标识符是不是关键字
int cnt1 = 0;
while ((ch = fgetc(fp)) != EOF) {
index = 0;
while (ch != '\f' && ch) {
if (ch == '\n' || ch == '\r' || ch == '\t') {
//注意:保留空格
ch = ' ';
}
student[cntStudent].tempCodes[index++] = ch;
ch = fgetc(fp);
}
//一个学生的代码读取结束。首先,获取所有自定义函数名称
len = strlen(student[cntStudent].tempCodes);
for (i = 0; i < len; i++) {
//清空word
memset(word, '\0', 50);
//删除空白
while (student[cntStudent].tempCodes[i] == ' ') {
i++;
}
j = 0;
if (isalpha(student[cntStudent].tempCodes[i]) || student[cntStudent].tempCodes[i] == '_') {
while (isalpha(student[cntStudent].tempCodes[i]) || isdigit(student[cntStudent].tempCodes[i]) || student[cntStudent].tempCodes[i] == '_') {
word[j++] = student[cntStudent].tempCodes[i++];
}
}
if (word[0] != '\0' && searchKeepwords(word) == 0) {
//判断是不是自定义函数,而不是变量
while (student[cntStudent].tempCodes[i] == ' ') {
i++;
}
cnt1 = 0;
if (student[cntStudent].tempCodes[i] == '(') {
cnt1++;
i++;
while (cnt1 != 0) {
if (student[cntStudent].tempCodes[i] == '(') {
cnt1++;
}
if (student[cntStudent].tempCodes[i] == ')') {
cnt1--;
}
i++;
}
while (student[cntStudent].tempCodes[i] == ' ') {
i++;
}
if (student[cntStudent].tempCodes[i] == '{') {
strcpy(student[cntStudent].function[student[cntStudent].cntFunction++].func, word);
//printf("%s\n", student[cntStudent].function[student[cntStudent].cntFunction-1].func);
}
i--;
}
}
}
createFuncSecret(cntStudent);
createCodeSecret(cntStudent);
cntStudent++;
}
}
void output() {
int i, j;
int editDistance; // 编辑距离
double sim; // 相似度
for (i = 0; i < cntStudent; i++) {
if (student[i].flag == 0) {
int flag1 = 0;
for (j = i + 1; j < cntStudent; j++) {
editDistance = editdistDP(student[i].codeSecret, student[j].codeSecret);
sim = 1 - (double)editDistance / max2(strlen(student[i].codeSecret), strlen(student[j].codeSecret));
if (sim > 0.95) {
if (flag1 == 0) {
printf("%s ", student[i].sequence);
flag1 = 1;
}
student[j].flag = 1;
printf("%s ", student[j].sequence);
}
}
if (flag1 != 0) {
printf("\n");
}
}
}
}
注意:这里只给出最基本样例。该代码通过early determination等技巧可以提升至1秒之内。
应用1-(查找-基本题)
【问题描述】
从标准输入中读入一个英文单词及查找方式,在一个给定的英文常用单词字典文件dictionary3000.txt中查找该单词,返回查找结果(查找到返回1,否则返回0)和查找过程中单词的比较次数。查找前,先将所有字典中单词读入至一个单词表(数组)中,然后按要求进行查找。字典中单词总数不超过3500,单词中的字符都是英文小写字母,并已按字典序排好序(可从课件下载区下载该字典文件)。字典中的单词和待查找单词的字符个数不超过20。
注意:读取文件中的单词时,若要判断行末的换行符,换行符可能是’\n’,也可能是两个字符:‘\r’和’\n’。
查找方式说明:查找方式以1~4数字表示,每个数字含义如下:
1:在单词表中以顺序查找方式查找,因为单词表已排好序,遇到相同的或第一个比待查找的单词大的单词,就要终止查找;
2:在单词表中以折半查找方式查找(折半查找的实现方式要参考上课PPT中的实现方式);
3:在单词表中通过索引表来获取单词查找范围,并在该查找范围中以折半方式查找。索引表构建方式为:以26个英文字母为头字母的单词在字典中的起始位置和单词个数来构建索引表,如:
字母 | 起始位置 | 单词个数 |
---|---|---|
a | 0 | 248 |
b | 248 | 167 |
… | … | … |
该索引表表明以字母a开头的单词在单词表中的开始下标位置为0,单词个数为248。
4:按下面给定的hash函数为字典中单词构造一个hash表,hash冲突时按字典序依次存放单词。hash查找遇到冲突时,采用链地址法处理,在冲突链表中找到或未找到(遇到第一个比待查找的单词大的单词或链表结束)便结束查找。
/* compute hash value for string */
#define NHASH 3001
#define MULT 37
unsigned int hash(char *str)
{
unsigned int h=0;
char *p;
for(p=str; *p!=‘\0’; p++)
h = MULT*h + *p;
return h % NHASH;
}
提示:hash表可以构建成指针数组,hash冲突的单词形成一有序链表。
【输入形式】
单词字典文件dictionary3000.txt存放在当前目录下,待查找单词和查找方式从标准输入读取。待查找单词只包含英文小写字母,与表示查找方式的整数之间以一个空格分隔。
【输出形式】
将查找结果和单词比较次数输出到标准输出上,两整数之间以一个空格分隔。
【样例输入与输出】
单词字典文件dictionary3000.txt与课件下载中提供的相同,下面两列中,左侧为待查找单词与查找方式,右侧为对应的输出结果:
wins 1 0 3314
wins 2 0 12
wins 3 0 7
wins 4 0 2
yes 1 1 3357
yes 2 1 10
yes 3 1 4
yes 4 1 1
【样例说明】
wins在单词字典中不存在,4种查找方式都输出结果0,顺序查找、折半查找、索引查找和hash查找的单词比较次数分别为:3314、12、7和2次(wins的hash位置与字典中physics和suggest相同)。
yes在单词字典中存在,4种查找方式都输出结果1,顺序查找、折半查找、索引查找和hash查找的单词比较次数分别为:3357、10、4和1。
【评分标准】
该题要求输出查找结果和查找过程中的单词比较次数,提交程序名为find.c。
#define _CRT_SECURE_NO_WARNINGS
#define NHASH 3001
#define MULT 37
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct _Word {
char word[20];
struct _Word* next;
}Word;
void linearSearch(char str[], char(*dic)[20], int cnt);
void binarySearch(char str[], char(*dic)[20], int cnt);
void indexSearch(char str[], char(*dic)[20], int cnt);
unsigned int hash(char* str);
void hashSearch(char str[], char(*dic)[20], int cnt);
int main()
{
char search[20] = { 0 };
int num;
FILE* fp = fopen("dictionary3000.txt", "r");
char dicitonary[3500][20] = { 0 };
char word[20] = { 0 };
int cnt = 0; //字典中单词总数
//读入字典
while (fscanf(fp, "%s", word) != EOF) {
strcpy(dicitonary[cnt++], word);
memset(word, '\0', sizeof(word));
}
scanf("%s", search);
scanf("%d", &num);
switch (num) {
case 1:linearSearch(search, dicitonary, cnt); break;
case 2:binarySearch(search, dicitonary, cnt); break;
case 3:indexSearch(search, dicitonary, cnt); break;
case 4:hashSearch(search, dicitonary, cnt); break;
}
fclose(fp);
return 0;
}
void linearSearch(char str[], char(*dic)[20], int cnt) {
int i = 0;
int cmp = 1;
while (i < cnt) {
if (strcmp(str, dic[i]) == 0) {
printf("1 %d", cmp);
return;
}
else if (strcmp(dic[i], str) > 0) {
printf("0 %d", cmp);
return;
}
cmp++;
i++;
}
printf("0 %d", cmp);
return;
}
void binarySearch(char str[], char(*dic)[20], int cnt) {
int low = 0;
int high = cnt - 1;
int mid = (low + high) / 2;
int cmp = 0;
while (low <= high) {
cmp++;
if (strcmp(dic[mid], str) == 0) {
printf("1 %d", cmp);
return;
}
else if (strcmp(dic[mid], str) > 0) {
high = mid - 1;
mid = (low + high) / 2;
}
else {
low = mid + 1;
mid = (low + high) / 2;
}
}
printf("0 %d", cmp);
return;
}
void indexSearch(char str[], char(*dic)[20], int cnt) {
int low, high;
int mid;
int cmp = 0;
char ch = str[0];
//获取索引两端
low = 0;
while (dic[low][0] - ch < 0) {
low++;
}
high = low;
while (dic[high][0] == ch) {
high++;
}
high--;
//binary search
mid = (low + high) / 2;
while (low <= high) {
cmp++;
if (strcmp(dic[mid], str) == 0) {
printf("1 %d", cmp);
return;
}
else if (strcmp(dic[mid], str) > 0) {
high = mid - 1;
mid = (low + high) / 2;
}
else {
low = mid + 1;
mid = (low + high) / 2;
}
}
printf("0 %d", cmp);
return;
}
unsigned int hash(char* str)
{
unsigned int h = 0;
char* p;
for (p = str; *p != '\0'; p++)
h = MULT * h + *p;
return h % NHASH;
}
void hashSearch(char str[], char(*dic)[20], int cnt) {
int i;
Word* hashList[NHASH] = { 0 };
Word* w;
int cmp = 0;
//构建哈希表(指针数组
for (i = 0; i < cnt; i++) {
w = (Word*)malloc(sizeof(Word));
strcpy(w->word, dic[i]);
w->next = NULL;
if (hashList[hash(dic[i])] == 0) {
hashList[hash(dic[i])] = w;
}
else {
Word* last = hashList[hash(dic[i])];
while (last->next != NULL) {
last = last->next;
}
last->next = w;
}
}
//搜索哈希表
w = hashList[hash(str)];
if (w == NULL) {
cmp++;
printf("0 %d", cmp);
return;
}
else if (strcmp(w->word, str) == 0) {
cmp++;
printf("1 %d", cmp);
return;
}
else {
while (w != NULL) {
cmp++;
if (strcmp(w->word, str) == 0) {
printf("1 %d", cmp);
return;
}
else if (strcmp(w->word, str) > 0) {
printf("0 %d", cmp);
return;
}
w = w->next;
}
}
printf("0 %d", cmp);
return;
}