目录
1 需求分析
1.1 任务
依据“折半查找、链式存储上的顺序查找”中的一种方法建立存储结构,并完成文件中单词的字频统计。完成字频统计后,根据输入的单词输出该单词是否存在于文件中,若存在输出频度,若不存在,输出“查找失败”。依据“基于开放定址法的哈希查找、基于链地址法的哈希查找” 中的一种方法建立存储结构,并完成文件中单词的字频统计。完成字频统计后,根据输入的单词输出该单词是否存在于文件中,若存在输出频度,若不存在,输出“查找失败”。
1.2 输入形式
本程序共两种输入形式
(1)第一种:输入A、B、C来选择存储结构
(2)第二种:输入1、2、3选择操作
1.3 输出形式
在字频统计功能中会将统计好的信息写入文件中进行保存,在单词检索功能中会将单词的字频输出到控制台。
1.4 程序功能
(1)“void CreatLink(LinkList *L);”和“void CreatHash(LinkList *L);” 读入文件中的单词,并构建单词节点和链表头节点;
(2)“void LinkInsert(LinkList *L,LinkList *t);”和“void HashInsert(LinkList *L,LinkList *t);”将构建完成的单词节点进行字频分析和节点插入;
(3)“void LinkFound(LinkList *L);”和“void HashFound(LinkList *L);”分别为“基于链式存储的顺序查找”和“基于链地址法的哈希查找”。
2 概要设计
2.1 抽象数据类型
typedef struct LN{
char data[WORDLENGTH]; //存放单词
int Frequency; //存放字频
struct LN *next; //指针
}LinkNode,*LinkList;
2.2 主程序流程图
3 详细设计
3.1 功能函数设计
//链式存储结构的建立及字频统计
void CreatLink(LinkList *L){ //链式存储结构的建立及字频统计
FILE *fp1,*fp2; //文件指针
LinkList t,pre,p;
char s1[WORDLENGTH]; //存放来自文件的一个单词
(*L)=(LinkList)malloc(1*sizeof(LinkNode));
(*L)->next=NULL;
fp1=fopen("InFile.txt","r");
while(!feof(fp1)){
fscanf(fp1,"%s",s1); //读文件
t=(LinkList)malloc(1*sizeof(LinkNode)); //为来自文件的单词构建存储节点
strcpy(t->data,s1);
t->Frequency=1;
LinkInsert(L,&t); //将节点进行字频分析和插入操作
}
fclose(fp1);
fp2=fopen("OutFile1.txt","w");
p=(*L)->next;
while(p!=NULL){
fprintf(fp2,"%s %5d\n",p->data,p->Frequency); //将每个单词的频度写进文件
/*
fprintf(fp2,"%s",p->data);
num_Space=25-strlen(p->data);
for(i=0;i<num_Space;i++) fprintf(fp2," ");
fprintf(fp2,"%5d\n",p->Frequency);
*/
p=p->next;
}
fclose(fp2);
printf("字频统计完成!\n\n");
}
//插入单词信息
void LinkInsert(LinkList *L,LinkList *t){ //插入单词信息
LinkList p,pre,loc;
if((*L)->next==NULL){ //当链表为空时,直接将节点插入
(*t)->next=(*L)->next;
(*L)->next=(*t);
}
else{
pre=(*L);
p=pre->next;
loc=NULL;
while(p!=NULL){ //链表不为空,在已有链表中寻找当前单词是否出现过
if(strcmp(p->data,(*t)->data)==0) {p->Frequency++; return ;} //出现过,频度加1
// if(stricmp(p->data,(*t)->data)>0) {loc=pre;}
pre=pre->next;
p=pre->next;
}
(*t)->next=pre->next; //未出现过,插入节点
pre->next=(*t);
}
}
//基于链式存储的顺序查找
void LinkFound(LinkList *L){ //基于链式存储的顺序查找
char word[WORDLENGTH];
int flag=0;
LinkList p,pre;
printf("请输入待查找的单词:");
scanf("%s",&word);
pre=(*L);
p=pre->next;
while(p!=NULL){ //顺序查找单词
if(strcmp(p->data,word)==0) {
printf("待查找单词的频度:%d\n\n",p->Frequency);
flag=1;
break;
}
p=p->next;
}
if(!flag) printf("查找失败\n\n");
}
//链式哈希存储的构建及字频统计
void CreatHash(LinkList *L){ //链式哈希存储的构建及字频统计
LinkList t,p;
int i;
FILE *fp1,*fp2;
char s1[WORDLENGTH]; //存放单词
for(i=0;i<26;i++){ //生成26个头结点,并初始化
L[i]=(LinkList)malloc(sizeof(LinkNode));
(*L[i]).Frequency=-1;
(*L[i]).next=NULL;
}
fp1=fopen("InFile.txt","r");
while(!feof(fp1)){
fscanf(fp1,"%s",s1);
t=(LinkList)malloc(sizeof(LinkNode)); //为来自文件的单词构建存储节点
strcpy(t->data,s1);
t->Frequency=1;
HashInsert(L,&t); //将节点进行字频分析和插入操作
}
fclose(fp1);
fp2=fopen("OutFile2.txt","w");
for(i=0;i<26;i++){ //将分析统计结构写入文件
p=(*L[i]).next;
while(p!=NULL){
fprintf(fp2,"%s %5d\n",p->data,p->Frequency);
p=p->next;
}
}
fclose(fp2);
printf("字频统计完成!\n\n");
}
//插入单词信息
void HashInsert(LinkList *L,LinkList *t){ //插入单词信息
LinkList p,pre,loc;
int number;
number=(tolower((*t)->data[0]))-'a'; //单词的存储位置为单词首字母的序号(不区分大小写,A字母为0号位置
if((*L[number]).next==NULL){ //链表为空直接插入节点
(*t)->next=(*L[number]).next;
(*L[number]).next=(*t);
}
else{
pre=&(*L[number]);
p=pre->next;
loc=NULL;
while(p!=NULL){ //链表不为空,在已有链表中寻找当前单词是否出现过
if(strcmp(p->data,(*t)->data)==0) {p->Frequency++; return ;}//出现过,频度加1
pre=pre->next;
p=pre->next;
}
(*t)->next=pre->next; //未出现过,插入节点
pre->next=(*t);
}
}
//基于链地址法的哈希查找
void HashFound(LinkList *L){ //基于链地址法的哈希查找
char word[WORDLENGTH];
int flag=0,number;
LinkList p,pre;
printf("请输入待查找的单词:");
scanf("%s",&word);
number=(tolower(word[0]))-'a';//计算单词存储位置的头结点序号
pre=&(*L[number]);
p=pre->next;
while(p!=NULL){
if(strcmp(p->data,word)==0) {
printf("待查找单词的频度:%d\n\n",p->Frequency);
flag=1;
break;
}
p=p->next;
}
if(!flag) printf("查找失败\n\n");
}
//操作提示
void Tips(int flag){ //操作提示
if(flag==0){
printf("\n+---------------Word_Frequency_Statistics_And_Retrieval---------------+\n");
printf("+ A:链式存储的顺序查找 B:基于链地址法的哈希查找 +\n");
printf("+ C:退出程序 +\n");
printf("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n");
}
if(flag==-1){
printf("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n");
printf("+ 1:字频统计 2:顺序查找 3.退出当前结构 +\n");
printf("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n");
}
if(flag==1){
printf("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n");
printf("+ 1:字频统计 2:哈希查找 3.退出当前结构 +\n");
printf("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n");
}
}
3.2 主函数设计
int main(){
char OP;
int function,EXIT=1,flag_exit;
LinkList LinkStruct,LinkHash;
while(1){
Tips(0);
printf("请选择存储结构:");
scanf("%c",&OP);
if(OP=='A'){
do{
flag_exit=0;
Tips(-1);
printf("现存储结构为“链式存储的顺序查找”。请输出您的下一步操作:");
scanf("%d",&function);
switch(function) {
case 1:CreatLink(&LinkStruct);getchar();break;
case 2:LinkFound(&LinkStruct);getchar();break;
case 3:flag_exit=1;break;
default :printf("抱歉,输入不合法。请重新输入!\n\n");
}
if(flag_exit==1) printf("已退出当前结构!\n\n");
}while(function!=3);
}
if(OP=='B'){
do{
flag_exit=0;
Tips(1);
printf("现存储结构为“基于链地址法的哈希查找”。请输出您的下一步操作:");
scanf("%d",&function);
switch(function){
case 1:CreatHash(&LinkHash);getchar();break;
case 2:HashFound(&LinkHash);getchar();break;
case 3:flag_exit=1;break;
default :printf("抱歉,输入不合法。请重新输入!\n\n");
}
if(flag_exit==1) printf("已退出当前结构!\n\n");
}while(function!=3);
}
if(OP=='C') break;
}
printf("即将退出程序,欢迎下次使用^_^\n");
return 0;
}
4 调试分析
4.1 调试过程的问题:
(1)问题1:写入文件的结果在文件中位置凌乱,不统一。
解决1:将写入文件语句中的“空格”换成“制表符”。
(2)问题2:类似“while(p!=NULL){}”的循环无法使用。
解决2:检查过后发现是因为粗心,忘记在循环体中加入“p=p->next”的移位语句。
(3)问题3:在判断两个单词是否相同时,无法正确判断。
解决3:因为单词是以字符串的形式存储的,不能直接使用“=”进行比较,而应该使用特定的函数或者自定义函数。
4.2 时空分析
函数 | 时间复杂度 | 空间复杂度 |
void CreatLink(LinkList *L); | O(n) | O(1) |
void LinkInsert(LinkList *L,LinkList *t); | O(n) | O(1) |
void LinkFound(LinkList *L); | O(n) | O(1) |
void CreatHash(LinkList *L); | O(n) | O(1) |
void HashInsert(LinkList *L,LinkList *t); | O(n) | O(1) |
void HashFound(LinkList *L); | O(n) | O(1) |
4.3 改进设想
在链表节点的插入过程中可以对单词进行字符串大小比较确定位置,使节点按字典序进行存储。
4.4 经验体会
要按时对以前的知识进行复习回顾或者在平时多写多练,尽量避免自己生疏,造成代码编写过程中浪费大量时间在修改代码上。
4.5 性能比较
(1)基于链式存储上的顺序查找:ASL=(n+1)/2;
(2)基于链地址法的哈希查找:ASL=∑piai/num;(pi为相同首字母的单词的相对于其头结点的偏移,ai为相同pi的个数,num为使用的头结点的个数)