提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
前言
记录下上学期的数据结构实验
本电话号码查询系统基于两种散列方法和两种解决冲突的方法实现
提示:以下是本篇文章正文内容,下面案例可供参考
一、问题描述
设计散列表,实现电话号码查找系统。设电话号码簿长度为n(0≤n≤10000),系统应该实现如下工作:
⑴ 电话号码簿保存在磁盘文件中,每一条电话号码记录包含数据项:编号(唯一),用户名,通信地址,电话号码(手机号)
⑵ 创建散列表:系统运行时,读取磁盘文件的电话号码,构建散列表,用于查询。要求:自选散列函数(至少2种),自选解决冲突的方法(至少2种),分别以电话号码和用户名为关键字,建立散列表。
⑶ 查询:根据输入的用户名,查找并显示给定用户的信息。
⑷ 性能分析:
① 计算并输出不同散列函数、不同解决冲突方法的平均查找长度。
② 通过改变散列因子、改变哈希函数等方式,改善平均查找长度:通过数据表、柱形图、折线图等方式,记录实验数据的变化情况,对影响平均查找长度变化的原因进行分析。
二、问题描述
(1)选用的散列函数
①除留余数法
分析:
ⅰ.以电话号码为关键字时,将字符串类型的电话号码转换成long型数据,除以表长,剩下的余数作为其在散列表中的地址,即pos值。
ⅱ.以姓名为关键字时,将字符串类型的姓名的每一位上的字母转换成ascii码,此时还是内容为数字的字符串,再将字符串转换成long型数据,除以表长,剩下的余数作为其在散列表中的地址,即pos值。
②折叠法
分析:
ⅰ.以电话号码为关键字时,将字符串类型的电话号码,切割成四组数据,每组数据的个数为3 3 3 2,转化成int型数据,取第一组数据和第三组数据、第二组和第四组的逆置数相加,得到的数据取后四位数,作为其在散列表中的地址,即pos值。
ⅱ.以姓名为关键字时,将字符串类型的姓名的每一位上的字母转换成ascii码,此时还是内容为数字的字符串,再切割成四组数据,每组数据的个数为3 3 3 2,转化成int型数据,取第一组数据和第三组数据、第二组和第四组的逆置数相加,得到的数据取后四位数,作为其在散列表中的地址,即pos值。
(2)散列因子
散列表的散列因子定义为:α= 填入表中的元素个数/散列表的长度。α是散列表装满程度的标志因子。由于表长是定值,α与元素个数成正比,所以,α越大,填入表中的元素较多,产生冲突的可能性就越大;α越小,填入表中的元素较少,产生冲突的可能性就越小。
为了探究不同散列因子对平均查找长度ASL的影响,在利用线性探测法解决冲突时,本实验中
拟取用 α 的值为 0.85、0.75、0.65、0.55;利用拉链法解决冲突时,本实验中 α 拟选用 1、1.2、
1.4、1.6。
(3)解决冲突的方法
①线性探测法
该方法的基本思想是,当关键字key的哈希地址p出现冲突时,顺序查看表中下一单元,以p为基础产生另一个哈希地址p1,如果p1仍然冲突,再以p为基础产生p2,……,直到找到一个不冲突的哈希地址pi,将相应元素存入其中。冲突发生时,顺序查看表中下一单元,,直到找出一个空单元或者查遍全表。
缺点:容易造成元素聚集,降低查找效率
②拉链法
该方法基本思想是将所有的哈希地址为i的元素构成一个同义词链的单链表,并将单链表的头指针存在哈希表的第i个单元中。
优点:避免了动态调整的开销
三、实验结果及分析
(1)实验数据描述
1.数据集规模
本实验拟采用 10000 组联系人记录。每一行记录一位联系人的编号、姓名、地址、电话号码。
文件存储于FinalDataSet_10000.txt。文件存储格式如图 1 所示:
2.数据集来源
本次实验的数据全部随机生成。
数据内容:
编号:1-10000,按顺序输出即可;
姓名:三个英文字母,字符串。随机生成0-25的int型数据,再通过循环从char型字母表数组中利用下标读出并存储到data数组的姓名域中去;
地址:长度不等,字符串。这里使用的城市数据仅为20组,每组城市数据存储在Country结构体中,结构体中有city[],用于存放每组城市名称,所有的城市数据存储在Country类型的数组中。随机生成0-20的int型数据,再通过循环从Country类型的数组中利用下标读出并存储到data数组的地址域中去。
电话号码:11位0-9的数据,字符串。根据一般电话号码的规律,首位都是1,因此其他10位是随机生成的。随机生成0-9的int型数据,再通过循环从char型数字表数组中利用下标读出并存储到data数组的电话号码域中去;
3.磁盘文件存储格式:.txt格式。
(2)实验结果
1.以电话号码为关键字
①哈希函数为除留余数法,解决冲突的方法为线性探测法,查找成功,实验结果如图2、3所示。
②哈希函数为除留余数法,解决冲突的方法为拉链法,查找失败,实验结果如图4所示。
2.以姓名为关键字
①哈希函数为除留余数法,解决冲突的方法为线性探测法,查找成功,实验结果如图5、6所示。
②哈希函数为除留余数法,解决冲突的方法为拉链法,查找失败,实验结果如图7所示。
(3)性能分析
①分析填充因子和冲突方法与 ASL 的关系
由表1和图8-11可见,在采用相同的解决冲突的方法时,ASL随散列因子增大而变大。当解决冲突方法为线性探测法时,查找失败比查找成功的ASL大,且增幅也随散列因子增大而变大。但当解决冲突方法为拉链法时,查找成功比查找失败的ASL大,且增幅并不随散列因子增大而改变。
②分析数据规模与 ASL 的关系
在散列因子α为0.75的情况下,进行了实验,实验数据如图12所示。显然由图可知,在相同的散列因子的情况下,随着数据规模的增大,ASL并没有明显的变化,数据基本都浮动在2.5上下。我认为数据规模与ASL之间没有直接的关系。
四、实验总结
在测试了不同的解决冲突办法、不同的散列因子和不同的数据规模对ASL的影响后,我得到了以下的结论:
a.当散列因子小于1时,解决冲突的方法可以选择线性探测法。ASL随着散列因子α的增大而增大,且增幅随之变大。因此,当解决冲突方法为线性探测法时,要慎重选择散列因子α。散列因子α过大,平均查找长度ASL过大,查找效果差;散列因子α过小,平均查找长度ASL虽然会较小,但是需要的存储空间随之变大了,因此在设计解决冲突方法为线性探测法的散列表时,要选择合适的散列因子α。
b.当散列因子大于1时,解决冲突的方法可以选择拉链法法。ASL随着散列因子α的增大而增大,但增幅并不随散列因子α的增大而改变,而是几乎不变。因此,适当选择拉链法的散列因子,可以表现出良好的查找性能。
c.由图表分析可得,解决冲突方式为拉链法受散列因子α的影响较小,解决冲突方式为线性探测法受散列因子α的影响较大。因此,拉链法更为稳定,性能更好。
d.数据规模较小时,解决冲突方式采用线性探测法、拉链法,性能差别都不是很大,均能表现出良好的查找性能,但是当数据规模变大的时候,采用线性探测法解决冲突的方法会使冲突增多,此时采用拉链法可以表现出更好的查找性能。
e.在实际操作的过程中,特别是在处理以姓名为关键字的时候,我发现有很多人的名字是重复的,这种情况在现实生活中也会存在,这也是冲突的其中一种方式,因此针对这种情况,我分不同的解决冲突方法进行讨论。
①线性探测法。当发现关键字重复时,再次通过线性探测,在与之重复的关键字周围寻找一个空表,将其填入,即可解决冲突,但要注意,要使用一个标志flag来记录某关键字重复的次数,借助该标志flag在查询关键字时方便找到所有重复的元素。
②拉链法。当发现关键字重复时,直接将该节点插入散列表的与重复关键字相同的位置后的头结点,即可解决冲突,但这个方法也需要使用一个标志flag来记录某关键字重复的次数,用来方便找到所有重复的元素。
五、源代码
(1)随机生成电话号码系统代码
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <string.h>
#define N 10000 //元素最大个数
typedef struct {
int no; //编号
char name[3];//名字
char address[10];//地址
char tel[12];//电话号码
} NODE;
typedef struct {
char data[10];
} Country;
Country country[10];
void creatfile(NODE data[], int *n);//创建磁盘文件f:\resource.dat
int isTelRepeated(char tel[]);
void initCountry();
int main() {
NODE DATA[N];
int n;
creatfile(DATA, &n);
return 0;
}
void creatfile(NODE data[], int *n) {
FILE *fp;
int i, key, flag;
int temp_n;
char temp_tel[11];
unsigned seed;
*n = N;
char num[10] = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
char alphabet[26] = {
'a', 'b', 'c', 'd', 'e', 'f', 'g',
'h', 'i', 'j', 'k', 'l', 'm', 'n',
'o', 'p', 'q', 'r', 's', 't',
'u', 'v', 'w', 'x', 'y', 'z'};//字母表
/*char ALPHABET[26] = {'A', 'B', 'C', 'D', 'E', 'F', 'G',
'H', 'I', 'J', 'K', 'L', 'M', 'N',
'O', 'P', 'Q', 'R', 'S', 'T',
'U', 'V', 'W', 'X', 'Y', 'Z'};//字母表*/
initCountry();
if ((fp = fopen("/Users/xiaoyee/Desktop/数据结构实验作业/实验报告/电话号码查询系统/1/TelDataSet_10000.txt", "w")) == NULL) {
printf("can't open the file!\n");
exit(0);
}
seed = time(NULL);
srand(seed); //设置随机种子
for (i = 0; i < *n;i++) {
for (int k = 0; k < 3; k++)