一、问题描述
C语言关键词共37个(如main,if,for等)(不同教材可能不同)。用Hash表存储全部关键词,设Hash函数为:Hash(Key)=[(Key的首字母序号)*100+(Key的尾字母序号)] Mod 41。设计一个扫描C源程序,统计该程序中的关键词出现的次数。分别使用线性探测法和链地址法解决Hash冲突,比较分析两者的执行结果。
二、设计思路:
三、两种方法对比
1、线性探测法的优点是解决冲突简单,一个重大的缺点是容易产生堆积。这是由于当连续出现若干个同义词时(设第一个同义词占用单元d0,这连续的若干个同义词将占用Hash 表的d0,d0+1,d0+2等单元),随后任何d0+1,d0+2等单元上的哈希映射都会由于前面的同义词堆积而产生冲突,尽管随后的这些关键字并没有同义词。
2、与开放定址法相比,拉链法有如下几个优点: ①链地址法处理冲突简单,且无堆积现象,即非同义词决不会发生冲突,因此平均查找长度较短; ②由于链地址法中各链表上的结点空间是动态申请的,故它更适合于造表前无法确定表长的情况; ③开放定址法为减少冲突,要求装填因子α较小,故当结点规模较大时会浪费很多空间。而链地址法中可取α≥1,且结点较大时,链地址法中增加的指针域可忽略不计,因此节省空间; ④在用链地址法构造的散列表中,删除结点的操作易于实现。只要简单地删去链表上相应的结点即可。而对开放地址法构造的散列表,删除结点不能简单地将被删结 点的空间置为空,否则将截断在它之后填人散列表的同义词结点的查找路径。这是因为各种开放地址法中,空地址单元(即开放地址)都是查找失败的条件。因此在 用开放地址法处理冲突的散列表上执行删除操作,只能在被删结点上做删除标记,而不能真正删除结点。
链地址法的缺点: 指针需要额外的空间,故当结点规模较小时,开放定址法较为节省空间,而若将节省的指针空间用来扩大散列表的规模,可使装填因子变小,这又减少了开放定址法中的冲突,从而提高平均查找速度。
3、用链地址法解决冲突,更容易知道哪些关键词发生了冲突;而用线性探测法解决冲突容易知道该关键词是否发生冲突,但不易知道是哪些关键词发生了冲突。
四、代码
1、线性探测法
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define N 41
char key[37][10]={"auto","break","case","char", "const","continue",
"default","do","double","else","enum","extern","float","for","goto",
"if","int","long","register","return","short","signed","sizeof","static",
"struct","switch","typedef","union","unsigned","void","volatile","while",
"inline","restrict","bool","true","false"};
typedef struct mykey{
char data[10];
int con;//冲突次数
int num;//出现次数
}MYKEY;
mykey my[N];//hash表
int sum=-1; //总的关键字数
FILE*fi; //所要读取文件指针
FILE*fo; //所要写的文件指针
void init(); //初始化结构体数组
int isChar(char a); //判断字符是否是字母
void isKey(char str[]); //判断是否是关键字
void myprint(); //打印Hash表
void test(int n); //检测某文件
void choose(int n); //选择读取的文件
bool readFile(); //读取文件
void find(); //查找某个关键词
void menu(); //显示操作菜单
void create(); //建立关键词Hash表
int main(){
create();
menu();
return 0;
}
int isChar(char a){
if(a>='a'&&a<='z'||a>='A'&&a<='Z'){
return 1;
}else{
return 0;
}
}
void init(){
fi=NULL;// 读取文件指针为空
sum=-1; //出现关键词总数置为-1
for(int i=0;i<N;i++){
if( my[i].num!=-1) {//hash表中该位置是否有关键词
my[i].num=0;
}
}
}
void isKey(char str[]){
int n=strlen(str);
int y=(str[0]*100+str[n-1])%N;
bool isfind=false;
int i=0;
do{
i++;
if(strcmp(my[y].data,str)==0){//与hash表中关键词比较是否相同
sum++;//相同关键词总数加1
my[y].num++;//出现次数加1
isfind=true;
break;
}else{
y=(y+1)%N;
}
}while(i<=N);
}
void find(){
char fin[10];
bool flag=false;
do{
printf("请正确输入要查找的关键词:\n");
scanf("%s",fin);
for(int i=0;i<N;i++){
if(strcmp(key[i],fin)==0){
flag=true;
break;
}
}
} while(flag==false);
int n=strlen(fin);
int y=(fin[0]*100+fin[n-1])%N;
bool isfind=false;
int i=0;
do{
i++;
if(strcmp(my[y].data,fin)==0){
isfind=true;
break;
}else{
y=(y+1)%N;
}
}while(i<=N);
if(isfind==false){
printf("很遗憾,该hash表内没有此关键词\n");
}else{
printf("hash表位置:%d 关键字:%s 出现次数:%d 冲突次数:%d\n",y,my[y].data,my[y].num,my[y].con);
}
int bian;
printf("当前操作已完成\n");
printf("接下来你可以做如下操作:\n");
printf("0.退出,1.继续查询关键词,2.进行其它测试\n");
do{
printf("请输入正确的操作编号!\n");
scanf("%d",&bian);
}while(bian<0||bian>2);
switch (bian){
case 0: printf("白白!");exit(0);break;
case 1:find();break;
case 2:init();menu();break;
}
}
void myprint(){
printf("关键词总数:%d\n",sum);
fprintf(fo,"%s ","关键词总数:");
fprintf(fo,"%d \n",sum);
char s1[50]="hash表位置:";
char s2[50]="无数据";
char s3[50]="出现次数:";
char s4[50]="冲突次数:";
char s5[50]="关键字:";
for(int i=0;i<N;i++){
if(my[i].num==-1){
printf("hash表位置:%d 无数据\n",i);
fprintf(fo,"%s ",s1);
fprintf(fo,"%d ",i);
fprintf(fo,"%s \n",s2);
} else{
printf("hash表位置:%d 关键字:%s 冲突次数:%d 出现次数:%d\n",i,my[i].data,my[i].con,my[i].num);
fprintf(fo,"%s ",s1);
fprintf(fo,"%d ",i);
fprintf(fo,"%s ",s5);
fprintf(fo,"%s ",my[i].data);
fprintf(fo,"%s ",s4);
fprintf(fo,"%d \n",my[i].con);
fprintf(fo,"%s ",s3);
fprintf(fo,"%d ",my[i].num);
}
}
int bian;
printf("已打印hash表\n");
printf("你可以做如下操作:\n");
printf("0.退出,1.进行其它测试,2.查找某关键词\n");
do{
printf("请输入正确的操作编号!\n");
scanf("%d",&bian);
}while(bian<0||bian>2);
switch (bian){
case 0: printf("白白!");exit(0);break;
case 1:init();menu();break;
case 2:find();break;
}
}
bool readFile() {
if(fi==NULL){
printf("no file");
return false;
}
char ch;
while(!feof(fi)){
char word[10];
int i=0;
ch=fgetc(fi);
while(isChar(ch)==0&&feof(fi)==0){
ch=fgetc(fi);
}
while(isChar(ch)==1&&feof(fi)==0){
if(i==10){
while(isChar(ch)==1&&feof(fi)==0){
ch=fgetc(fi);
}
i=0;
break;
}else{
word[i++]=ch;
ch=fgetc(fi);
}
}
word[i]='\0';
isKey(word); //从源程序中读的单词是否是hash表中的关键词
}
return true;
}
void menu(){
int bian;
printf("欢迎来到hash表系统首页!\n");
printf("你可以选择如下操作:\n");
printf("0.退出,1.测试集合交并补.cpp,2.测试980(A).cpp,3.测试1051(O).cpp,4.测试1051(S).cpp,5号测试\n");
do{
printf("请输入正确的操作编号!\n");
scanf("%d",&bian);
}while(bian<0||bian>5);
if(bian==0){
printf("白白!");
exit(0);
}else if(bian>0&&bian<=5){
test(bian);
}
}
void test(int n){
int bian;
choose(n);
if(readFile()){
printf("文件读取成功,并已建立hash表\n");
printf("你可以做如下操作:\n");
printf("0.退出,1.打印hash表内容,2进行其它测试,3查找某关键词\n");
do{
printf("请输入正确的操作编号!\n");
scanf("%d",&bian);
}while(bian<0||bian>3);
switch (bian){
case 0: printf("白白!");exit(0);break;
case 1: myprint();break;
case 2:init();menu();break;
case 3:find();break;
}
}else{
printf("很遗憾,只能进行其它测试了!\n");
printf("你有选择如下选择:\n");
printf("0.退出,1.测试集合交并补.cpp,2.测试980(A).cpp,3.测试1051(O).cpp,4.测试1051(S).cpp\n");
int bain;
do{
printf("请输入正确的操作编号!\n");
scanf("%d",&bian);
}while(bian<0||bian>4);
if(bian==0){
printf("白白!");
exit(0);
}else if(bian>0&&bian<=4){
test(bian);
}
}
}
void choose(int n){
switch(n){
case 1:fi=fopen("集合交并补.cpp","r");fo=fopen("集合交并补.txt","w");break;
case 2:fi=fopen("980(A).cpp","r");fo=fopen("980(A).txt","w");break;
case 3:fi=fopen("1051(O).cpp","r");fo=fopen("1051(O).txt","w");break;
case 4:fi=fopen("1056(S).cpp","r");fo=fopen("1056(S).txt","w");break;
case 5:fi=fopen("1051(S).cpp","r");break;
}
}
void create(){
for(int i=0;i<N;i++){
my[i].num=-1;
my[i].con=0;
}
for(int i=0;i<37;i++) {
int n=strlen(key[i]);
int y=(key[i][0]*100+key[i][n-1])%N; //计算关键词hash值
if(my[y].num==-1) {//判断是否冲突
my[y].num++;
strcpy(my[y].data,key[i]);
}else{
my[y].con++;
int count=0,x;
while(count<N){ //寻找下一个没有存放关键词的地方
y++;
x=y%41;
if(my[x].num==-1){//找到下一个没有存放关键词的地方
my[x].num++;
strcpy(my[x].data,key[i]);
break;
}
count++;
}
}
}
}
2、链地址法
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define N 41
char key[37][10]={"auto","break","case","char", "const","continue",
"default","do","double","else","enum","extern","float","for","goto",
"if","int","long","register","return","short","signed","sizeof","static",
"struct","switch","typedef","union","unsigned","void","volatile","while",
"inline","restrict","bool","true","false"};
typedef struct mykey{
char data[10];
int con;//冲突次数
int num;//出现次数
mykey*next;//下一个节点
}MYKEY;
mykey my[N];//hash表
int sum=0;//总的关键字数
FILE*fi;//所要读取文件指针
FILE*fo;//所要写的文件指针
void init();
int isChar(char a);
void isKey(char str[]);
void myprint();
void test(int n);
void choose(int n);
bool readFile();
void find();
void menu();
void create();
int main(){
create();
menu();
return 0;
}
int isChar(char a){
if(a>='a'&&a<='z'||a>='A'&&a<='Z'){
return 1;
}else{
return 0;
}
}
void init(){
fi=NULL;
sum=0;
mykey*p;
for(int i=0;i<N;i++){
p=my[i].next;
while(p!=NULL){
p->num=0;
p=p->next;
}
}
}
void isKey(char str[]){
int n=strlen(str);
int y=((str[0]-96)*50+str[n-1]-96)%N;//计算hash值 找到头结点
mykey*p,*r;
p=my[y].next;
while(p!=NULL){
if(strcmp(p->data,str)==0){//比较两个单词是否相等来判断是否是关键词
sum++;
p->num++;
break;
}
p=p->next;
}
}
void find(){
char fin[10];
bool flag=false;//标志查找关键词是否合法
do{
printf("请正确输入要查找的关键词:\n");
scanf("%s",fin);
for(int i=0;i<N;i++){
if(strcmp(key[i],fin)==0){
flag=true;
break;
}
}
} while(flag==false);
int n=strlen(fin);
int y=((fin[0]-96)*50+fin[n-1]-96)%N;//计算hash值
bool isfind=false; //是否找到关键词
mykey*p;
p=my[y].next;
while(p!=NULL){ //遍历查找关键词
if(strcmp(p->data,fin)==0){
isfind=true;
break;
}
p=p->next;
}
if(isfind==false){
printf("很遗憾,该hash表内没有此关键词\n");
}else{
printf("hash表位置:%d 关键字:%s 出现次数:%d 冲突次数:%d\n",y,p->data,p->num,my[y].con);
}
int bian;
printf("当前操作已完成\n");
printf("接下来你可以做如下操作:\n");
printf("0.退出,1.继续查询关键词,2.进行其它测试\n");
do{
printf("请输入正确的操作编号!\n");
scanf("%d",&bian);
}while(bian<0||bian>2);
switch (bian){
case 0: printf("白白!");exit(0);break;
case 1:find();break;
case 2:init();menu();break;
}
}
void myprint(){
printf("关键词总数:%d\n",sum);
fprintf(fo,"%s ","关键词总数:");
fprintf(fo,"%d \n",sum);
char s1[50]="hash表位置:";
char s2[50]="无数据";
char s3[50]="出现次数:";
char s4[50]="冲突次数:";
char s5[50]="关键字:";
char s6[50]="其它数据如下:";
mykey*p;
for(int i=0;i<N;i++){
p=my[i].next;
if(p==NULL){
printf("hash表位置:%d 无数据\n",i);
fprintf(fo,"%s ",s1);
fprintf(fo,"%d ",i);
fprintf(fo,"%s \n",s2);
}else{
printf("hash表位置:%d 冲突次数:%d\n",i,my[i].con);
fprintf(fo,"%s ",s1);
fprintf(fo,"%d ",i);
fprintf(fo,"%s ",s4);
fprintf(fo,"%d \n",my[i].con);
printf("其它数据如下:\n");
fprintf(fo,"%s \n",s6);
while(p!=NULL){
printf(" 关键字:%s 出现次数:%d \n",p->data,p->num);
fprintf(fo,"%s "," ");
fprintf(fo,"%s ",s5);
fprintf(fo,"%s ",p->data);
fprintf(fo,"%s ",s3);
fprintf(fo,"%d \n",p->num);
p=p->next;
}
}
}
int bian;
printf("已打印hash表\n");
printf("你可以做如下操作:\n");
printf("0.退出,1.进行其它测试,2.查找某关键词\n");
do{
printf("请输入正确的操作编号!\n");
scanf("%d",&bian);
}while(bian<0||bian>2);
switch (bian){
case 0: printf("白白!");exit(0);break;
case 1:init();menu();break;
case 2:find();break;
}
}
bool readFile() {
if(fi==NULL){
printf("no file");
return false;
}
char ch;
while(!feof(fi)){
char word[10];
int i=0;
ch=fgetc(fi);
while(isChar(ch)==0&&feof(fi)==0){
ch=fgetc(fi);
}
while(isChar(ch)==1&&feof(fi)==0){
if(i==10){
while(isChar(ch)==1&&feof(fi)==0){
ch=fgetc(fi);
}
i=0;
break;
}else{
word[i++]=ch;
ch=fgetc(fi);
}
}
word[i]='\0';
isKey(word);
}
return true;
}
void menu(){
init();
int bian;
printf("欢迎来到hash表系统首页!\n");
printf("你可以选择如下操作:\n");
printf("0.退出,1.测试集合交并补.cpp,2.测试980(A).cpp,3.测试1051(O).cpp,4.测试1051(S).cpp,5号测试\n");
do{
printf("请输入正确的操作编号!\n");
scanf("%d",&bian);
}while(bian<0||bian>5);
if(bian==0){
printf("白白!");
exit(0);
}else if(bian>0&&bian<=5){
test(bian);
}
}
void test(int n){
int bian;
choose(n);
if(readFile()){
printf("文件读取成功,并已建立hash表\n");
printf("你可以做如下操作:\n");
printf("0.退出,1.打印hash表内容,2进行其它测试,3查找某关键词\n");
do{
printf("请输入正确的操作编号!\n");
scanf("%d",&bian);
}while(bian<0||bian>3);
switch (bian){
case 0: printf("白白!");exit(0);break;
case 1: myprint();break;
case 2:init();menu();break;
case 3:find();break;
}
}else{
printf("很遗憾,只能进行其它测试了!\n");
printf("你有选择如下选择:\n");
printf("0.退出,1.测试集合交并补.cpp,2.测试980(A).cpp,3.测试1051(O).cpp,4.测试1051(S).cpp\n");
int bain;
do{
printf("请输入正确的操作编号!\n");
scanf("%d",&bian);
}while(bian<0||bian>4);
if(bian==0){
printf("白白!");
exit(0);
}else if(bian>0&&bian<=4){
test(bian);
}
}
}
void choose(int n){
switch(n){
case 1:fi=fopen("集合交并补.cpp","r");fo=fopen("集合交并补.txt","w");break;
case 2:fi=fopen("980(A).cpp","r");fo=fopen("980(A).txt","w");break;
case 3:fi=fopen("1051(O).cpp","r");fo=fopen("1051(O).txt","w");break;
case 4:fi=fopen("1056(S).cpp","r");fo=fopen("1056(S).txt","w");break;
case 5:fi=fopen("1051(S).cpp","r");break;
}
}
void create(){
for(int i=0;i<N;i++){//初始化
my[i].num=0;
my[i].con=0;
my[i].next=NULL;
}
mykey*p,*r;
for(int i=0;i<37;i++) {
int n=strlen(key[i]);
int y=((key[i][0]-96)*50+key[i][n-1]-96)%N;//计算hash值
p=&my[y];
if(p->next==NULL){ //是否冲突
r=(mykey*)malloc(sizeof(mykey));//采用尾插法建立链表
strcpy(r->data,key[i]);
r->num=0;
p->next=r;
r->next=NULL;
}else{
my[y].con++;
while(p->next!=NULL){
p=p->next;
}
r=(mykey*)malloc(sizeof(mykey));
strcpy(r->data,key[i]);
r->num=0;
p->next=r;
r->next=NULL;
}
}
}