哈希表的数组实现
哈希表的概念:
哈希,又叫做散列表,是把任意长度的输入(又叫做预映射pre-image)通过散列算法变换成固定长度的输出,该输出就是散列值。这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,所以不可能从散列值来确定唯一的输入值。简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。
哈希两大重点:哈希映射(也就是确定哈希地址的函数):y(x)=F(x);
比如y(x)=x;这是直接取址法;
哈希地址不是真正意义上的地址,是抽象的参照地址
一般用的比较多的构造函数是取余法:
y=x%p
例如:
数据: 1 4 11 19 23
int array [10]
1%10=1;
4%10=4;
11%10=1;
19%10=9;
23%10=3;
这些余数就是要存到数组的下标;
这就是哈希映射的主要内容,但是可以看到11和1的哈希地址发生了重叠,也就是存储的会有问题
这就叫做哈希冲突
发生了哈希冲突,那我们就需要来解决这个哈希冲突,这叫做哈希冲突解决方案:
第一种方法是开放地址法:
看下面这个图:
就是如果发生冲突,就往左或者往右找空余的位置进行插入,这就是开放地址法,也是我们数组实现的主要方式。
一般哈希地址的个数是大于元素个数的,这样方便使用这样的开放地址法
第二种方案是采用数组链表的方式,也就是图内容里面的邻接表法
在冲突的位置上添加链表
看下图了解一下即可:
还有一种是有序链表来实现,叫做跳表
这种办法我单独写个博客,这里我先只用数组哈希
哈希的应用,有一种算法叫做LZW,有兴趣的小伙伴可以去看看,是一种压缩算法,还有,哈希表的key也可以是字符串,需要自己设计字符串的哈希映射,构建函数来自己实现。
下面是我的C语言代码实现整数关键字的哈希表:
首先是hash存储要的需要映射的键值,还有要存储的数据,封装成结构体二元组:
struct pair {
int first;//键值,用来进行映射
char second[20];//数据,这里用字符串演示
};
接下来是hash表的结构体封装:
struct hashtable {
struct pair** arr;//这里为什么用二级指针,是因为这样可以初始化为NULL,如果是
///一级指针,那么数组要初始化,必定需要一个初值,这样映射的键值就是会有
//问题的,设置成NULL,这样好判断,就容易使用取余法和开放地址来构造hash表了
int hashsize;//所存放的数据个数
int div;//取余法的余数
};
接下来是hash的插入:插入之前是不是考虑一下取址问题,还有开放地址的使用方法,也就是找到哈希地址:
int search(struct hashtable* hash, int first) {
int pos = first % hash->div;//先找第一次的哈希地址
int curPos = pos;//开始判断
do {
if (hash->arr[curPos] == NULL || hash->arr[curPos]->first == first) {//当找到空余位置就返回,或者第一键值就是一样的,那么就更改它存贮的数据,STL的map就是这样的操作
return curPos;//返回
}
} while (curPos=(curPos + 1) % hash->div);//当没有找到,就往下走更改curPos的值
return curPos;//转了一圈没有找到返回现在的值,也就是表满的情况
}
插入的函数:
void inserthash(struct hashtable* hash, struct pair data) {
int pos = search(hash, data.first);//找地址
if (hash->arr[pos] == NULL) {//如果这个地址的确是,分配内存,并写入数据
hash->arr[pos] = (struct pair*)malloc(sizeof(struct pair));
memcpy(hash->arr[pos], &data, sizeof(struct pair));
hash->hashsize++;//增大数据
}
else {//冲突处理,也就是这个地址的确是上面转了一圈就没找到合适的位置返回过来
//,的确是满了,判断现在的位置是不是first一样
if (hash->arr[pos]->first == data.first) {
strcpy(hash->arr[pos]->second, data.second);
}
else {
printf("表满了");//表满情况
return;
}
}
}
那么现在的主体函数已经完成了,下面是验证的时刻,我们先做一个打印的函数:
void print(struct hashtable* hash) {
for (int i = 0; i < hash->div; i++) {
if (hash->arr[i] == NULL) {
printf("NULL\n");
}
else {
printf("%d %s\n", hash->arr[i]->first, hash->arr[i]->second);
}
}
}
下面是主函数验证:
int main() {
struct hashtable* hash = createhash(10);
struct pair arr[5] = { {11,"yyx"},{12,"有一线"},{13,"qt"},{19,"999"},{18,"ss"} };
for (int i = 0; i < 5; i++) {
inserthash(hash, arr[i]);
}
print(hash);
inserthash(hash, {1,"sss"});//验证地址冲突的处理
inserthash(hash, { 4,"sss" });
inserthash(hash, { 5,"sss" });
inserthash(hash, { 6,"sss" });
inserthash(hash, { 7,"sss" });
print(hash);
inserthash(hash, { 21,"sss" });//验证表满的处理
print(hash);
return 0;
}
好了,关于数组实现hash表的内容今天分享到这里了,下一篇文章分享跳表结构,也就是链表实现hash表。