問題描述
針對某個集體中人名設計一個哈希表,使得平均查找長度不超過R,並完成相應的建表和查表程序。
基本要求
假設人名爲中國人姓名的漢語拼音形式。待填入哈希表的人名共有30個,取平均查找長度的上限爲2。哈希函數用除留餘數法構造,用線性探測再散列法或鏈地址法處理衝突。
測試數據
取讀者周圍較熟悉的30個人名。
選作內容
(1) 從教科書上介紹的集中哈希函數構造方法中選出適用者並設計幾個不同的哈希函數,比較他們的地址衝突率(可以用更大的名字集合作實驗)。
(2) 研究這30個人名的特點,努力找一個哈希函數,使得對於不同的拼音名一定不發生地址衝突。
(3) 在哈希函數確定的前提下嘗試各種不同處理衝突的方法,考察平均查找長度的變化和造好的哈希表中關鍵字的聚集性。
源碼
代碼在VS中運行會出錯,建議在VC或CB中編譯
#include<stdio.h>
#include<time.h>//time用到的頭文件
#include<stdlib.h>//隨機數srand用到的頭文件
#include<ctype.h>//toascii()用到的頭文件
#include<string.h>//查找姓名時比較字符串用的頭文件
#define HASH_LEN 50//哈希表的長度
#define P 47//小於哈希表長度的P
#define NAME_LEN 30//姓名錶的長度
typedef struct//姓名錶
{
char *key; //名字的拼音
int m; //拼音所對應的ASCII和
}NAME;
typedef struct//哈希表
{
char *key; //名字的拼音
int m; //拼音所對應的ASCII總和,即關鍵字
int si; //查找長度
}HASH;
NAME Name[HASH_LEN]; //全局定義姓名錶,最大長度爲50
HASH Hash[HASH_LEN]; //全局定義哈希表,最大長度爲50
int d[30],i,j; //全局定義隨機數,循環用的i、j
void InitName()
{//姓名錶的初始化
Name[0].key="lvsongxian";
Name[1].key="yuanlei";
Name[2].key="daiziwen";
Name[3].key="chenyonghui";
Name[4].key="zhangliang";
Name[5].key="liubei";
Name[6].key="sunshangxiang";
Name[7].key="liyuanfang";
Name[8].key="huge";
Name[9].key="liuyifei";
Name[10].key="anyixuan";
Name[11].key="wangbaoqiang";
Name[12].key="yangyiming";
Name[13].key="hujing";
Name[14].key="guowen";
Name[15].key="xuyang";
Name[16].key="lilu";
Name[17].key="shenjinfeng";
Name[18].key="xuhui";
Name[19].key="huangjing";
Name[20].key="guanyu";
Name[21].key="chenlong";
Name[22].key="huangliang";
Name[23].key="liyan";
Name[24].key="haojian";
Name[25].key="zhangfei";
Name[26].key="shuxiang";
Name[27].key="sunyingjie";
Name[28].key="wangbo";
Name[29].key="zhaoqing";
for (i=0;i<NAME_LEN;i++) //將字符串的各個字符所對應的ASCII碼相加,所得的整數做爲哈希表的關鍵字
{
int s=0;
char *p=Name[i].key;
for (j=0;*(p+j)!='\0';j++)
s+=toascii(*(p+j));
Name[i].m=s;
}
}
void CreateHash()
{//建立哈希表
for(i=0;i<HASH_LEN;i++) //清空哈希表,未經此操作將儲存空數據
{
Hash[i].key="\0";
Hash[i].m =0;
Hash[i].si=0;
}
for(i=0;i<NAME_LEN;i++)
{
int sum=1,j=0,t;
int adr=(Name[i].m)%P; //除留餘數法H(key)=key%P,除數爲P=47
if(Hash[adr].si==0) //如果不衝突,將姓名錶賦值給哈希表
{
Hash[adr].m =Name[i].m;
Hash[adr].key=Name[i].key;
Hash[adr].si=1;
}
else //如果衝突
{
t=adr; //線性探測法處理衝突
for(;Hash[adr].si!=0&&adr<HASH_LEN;adr++)//從衝突下一個位置開始探測
{
sum=sum+1;//每次查找,查找次數+1
if(adr==HASH_LEN-1)//如果找到最後一個仍然沒有位置
{
for(;Hash[adr].si!=0&&adr<t;adr++)//從第一個開始探測
sum=sum+1;//每次查找,查找次數+1
if(adr==t) printf("哈希表已滿\n");//如果找到上次的位置仍然沒有,則輸出哈希表已滿
}
}
Hash[adr].m =Name[i].m; //將姓名錶複製給哈希表對應的位置上
Hash[adr].key=Name[i].key;
Hash[adr].si=sum;
}
}
}
void DisplayName()//顯示姓名錶
{
printf("\n地址 \t\t 姓名 \t\t 關鍵字\n");
for (i=0;i<NAME_LEN;i++)
printf("%2d %18s \t\t %d \n",i,Name[i].key,Name[i].m);
}
void DisplayHash()// 顯示哈希表
{
float asl=0.0;
printf("\n\n 地址 \t\t 姓名 \t\t 關鍵字 \t 搜索長度\n"); //顯示的格式
for (i=0;i<HASH_LEN;i++)
{
printf("%2d %18s \t\t %d \t\t %d\n",i,Hash[i].key,Hash[i].m,Hash[i].si);
asl+=Hash[i].si;
}
asl/=NAME_LEN;//求得ASL
printf("\n\n平均查找長度:ASL(%d)=%f \n",NAME_LEN,asl);
}
void FindName()//查找
{
char name[20]={0};
int s=0,sum=1,adr;
printf("\n請輸入想要查找的姓名的拼音:");
scanf("%s",name);
getchar();
for (j=0;j<20;j++)//求出姓名的拼音所對應的ASCII作爲關鍵字
s+=toascii(name[j]);
adr=s%P; //除留餘數法
j=0;
if(Hash[adr].m==s&&!strcmp(Hash[adr].key,name)) //分3種情況進行判斷,並輸出查找結果
printf("\n姓名:%s 關鍵字:%d 地址:%d 查找長度爲: 1\n",Hash[adr].key,s,adr);
else if (Hash[adr].m==0)
printf("\n沒有想要查找的人!\n");
else
{
while(1)
{
adr=(adr+d[j++])%HASH_LEN;//僞隨機探測再散列法處理衝突
sum=sum+1; //查找次數加1
if(Hash[adr].m==0)
{
printf("\n沒有想要查找的人!\n");
break;
}
if(Hash[adr].m==s&&!strcmp(Hash[adr].key,name))
{
printf("\n姓名:%s 關鍵字:%d 地址:%d 查找長度爲:%d\n",Hash[adr].key,adr,s,sum);
break;
}
}
}
}
void view()//交互界面
{
printf("=======================================================\n");
printf("= =\n");
printf("= 人名哈希表 =\n");
printf("= =\n");
printf("= A: 打印姓名錶 B: 打印哈希表 =\n");
printf("= =\n");
printf("= C: 查找 D: 退出程序 =\n");
printf("= =\n");
printf("=======================================================\n");
}
int main()//主函數
{
char c;
int a=1;
srand((int)time(0));//以當前時間對應的int值爲隨機序列起點,每次運行程序由於起點不同可以得到不同的隨機數序列
for(i=0;i<30;i++)//用隨機函數求得僞隨機數列d[i](在1到50之間)
d[i]=1+(int)(HASH_LEN*rand()/(RAND_MAX+1.0));
InitName();//調用初始化姓名錶函數
CreateHash();//調用創建哈希表函數
view();//調用交互界面函數
while(a)
{
printf("\n輸入選項:");
scanf("%c",&c);
getchar();
switch(c)//根據選擇進行判斷,直到選擇退出時纔可以退出
{
case 'A':
case 'a': DisplayName(); break;//打印姓名錶
case 'B':
case 'b': DisplayHash(); break;//打印哈希表
case 'C':
case 'c': FindName(); break;//調用查找函數
case 'D':
case 'd': a=0; break;//退出循環,終止程序
default: printf("\n請輸入正確的選擇!\n"); break;
}
}
return 0;
}