8.哈希表

本文详细介绍了哈希表的概念,包括哈希函数的构造方法如直接寻址法和除留余数法,以及处理冲突的策略,如开放地址法(线性探测、二次探测、随机探测)和链地址法。文中还提到了再哈希法和建立公共溢出区作为解决冲突的手段,并提供了一个使用BKDR哈希算法和链地址法处理冲突的示例代码。
摘要由CSDN通过智能技术生成

哈希表

哈希表又叫散列表,关键值通过哈希函数映射到数组上,查找时通过关键值直接访问数组。哈希函数指的是关键值和存储位置建立的对应关系。

哈希函数指的是关键值和存储位置建立的对应关系,查找时只要根据这个关系就能找到目标位置。一般我们只要通过一次查找就能找到目标位置,但有些关键字需要多次比较和查找才能找到,这是为什么呢?因为哈希表里,可能存在关键字不同但是哈希地址相同的情况,也就是产生了冲突。一般情况下,冲突是不可避免的,因为关键字集合往往比哈希地址集合大很多。要提高哈希表的查找效率,关键在于合理的构造哈希函数和优秀的解决冲突的方法。

哈希函数的构造方法

直接寻址法,即取关键字的值或者关键字的某个函数变换值,线性的映射到存储地址上。如果关键字的数量和跨度不是很大,直接寻址法是最简单最有效的构造方法了,并且还可以避免冲突。但是如果关键字的数量和跨度很大的话,这种方法就用不了了,例如有 n 个关键字,值最小的为 0,最大的为 1010,这种情况下就不能用直接寻址法了,因为没有足够的空间可用来存储。

除留余数法,我们将关键字对整数 p 取的余数直接做为存储地址,整数 p 一般取小于等于哈希表长度 size 的最大质数,如果关键字不是整数,比如是一个字符串,可以先将其做个转换,然后再对 p 取余。选择优秀的 p 可以减少冲突的发生。

其他的构造方法还有分析数字法,随机数法等等。设计哈希函数没有统一的方法,同一个哈希函数不一定能适用所有问题,其产生的影响也是不一样的。但哈希函数的设计又是至关重要的,设计哈希函数时要达到两个要求:

  • 计算简单,计算复杂的哈希函数会增加查询的时间;
  • 关键字尽可能地均分到存储地址上,这样可以减少冲突。
处理冲突的方法
开放地址法

如果发生冲突,那么就使用某种策略寻找下一存储地址,直到找到一个不冲突的地址或者找到关键字,否则一直按这种策略继续寻找。如果冲突次数达到了上限则终止程序,表示关键字不存在哈希表里。一般常见的策略有这么几种:

  1. 线性探测法,如果当前的冲突位置为d,那么接下来探测地址为d+1d+2d+3等,也就是从冲突地址往后面一个一个探测。
  2. 线性补偿探测法,它形成的探测地址为d+md+2*md+3m等,与线性探测法不同,这里的查找单位不是1,而是m,为了能遍历到哈希表里所有位置,我们设置m和表长size互质。
  3. 随机探测法,这种方法和前两种方法类似,这里的查找单位不是一个固定值,而是一个随机序列。
  4. 二次探测法,它形成的探测地址为 d+12、d-12、d+22,d-22等,这种方法在冲突位置左右跳跃着寻找探测地址。

线性探测法容易形成“堆聚”的情况,即很多记录就连在一块,而且一旦形成“堆聚”,记录会越来越多。另外,开放地址法都有一个缺点,删除操作显得十分复杂,我们不能直接删除关键字所在的记录,否则在查找删除位置后面的元素时,可能会出现找不到的情况,因为删除位置上已经成了空地址,查找到这里会终止查找。

链地址法

该方法将所有哈希地址相同的结点构成一个单链表,单链表的头结点存在哈希数组里。链地址法常出现在经常插入和删除的情况下。

相比开放地址法,链地址法有以下优点:

  • 不会出现“堆聚”现象,哈希地址不同的关键字不会发生冲突;
  • 不需要重建哈希表,在链地址法里,因为结点都是动态申请的,所以不会出现哈希表里存满关键字的情况;
  • 相比开放地址法,关键字删除更方便,只需要找到指定结点,删除该结点即可。

开放地址法和链地址法各有千秋,适用于不同情况。当关键字规模少的时候,开放地址法比链地址法更节省空间,因为用链地址法可能会存在哈希数组出现大量空地址的情况,而在关键字规模大的情况下,链地址法就比开放地址法更节省空间,链表产生的指针域可以忽略不计,关键字多,哈希数组里产生的空地址就少了。

再哈希法

多设置几个hash函数,当冲突发生时,多尝试几次不同的函数,直至找到不冲突的位置

建立公共溢出区

单独建立溢出区,当发生冲突时,将溢出元素存放到公共溢出区内,查找时在对应位置找不到的情况下,就去公共溢出区查找。

BKDR哈希算法
/*************************************************************************
	> File Name: 7.hash.c
	> Author: 陈杰
	> Mail: 15193162746@163.com
	> Created Time: 2021年04月03日 星期日 20时17分18秒
  > BKDRHash算法演示,冲突处理方法拉链法
************************************************************************/
#include<stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct Node {
    char *str;
    struct Node *next;
} Node;
typedef struct HashTable{
    Node **data;
    int size;
} HashTable;
/*
* 初始化一个结点
* @param str: 字符串
* @param head: 链表头
* */
Node *init_node(char *str, Node *head) {
    Node *p = (Node *)malloc(sizeof(Node));
    p->str = strdup(str);
    p->next = head;
    return p;
}
/*
* 初始化一个hash表
* @param h: 哈希表容量
* */
HashTable *init_hashtable(int n) {
    HashTable *h = (HashTable *)malloc(sizeof(HashTable));
    h->size = n << 1;
    h->data = (Node **)calloc(h->size, sizeof(Node *));
    return h;
}
/*
* 哈希函数
* @param str: 要计算的字符串
* */
int BKDRHash(char *str) {
    int seed = 31, hash = 0;
    for(int i = 0; str[i]; i++) hash = hash * seed + str[i];
    return hash & 0x7fffffff;
}
/*
* 插入操作
* @param h: 要操作的hash表
* @param str: 字符串
* */
int insert(HashTable *h, char *str) {
    int hash = BKDRHash(str);
    int ind = hash % h->size;
    h->data[ind] = init_node(str, h->data[ind]);
}
/*
* 查找操作
* @param h: 要操作的hash表
* @param str: 字符串
* */
int search(HashTable *h, char *str) {
    int hash = BKDRHash(str);
    int ind = hash % h->size;
    Node *p = h->data[ind];
    while(p && strcmp(p->str, str)) p = p->next;
    return p != NULL;
}
/*
* 清除链表
* @param node: 链表头结点
* */
void clear_node(Node *node) {
    if(node == NULL) return;
    Node *p = node, *q;
    while(p) {
        q =  p->next;
        free(p->str);
        free(p);
        p = q;
    }
    return;
}
/*
* 清理哈希表
* @param h: 要操作的hash表
* */
void clear(HashTable *h) {
    if(h == NULL) return;
    for(int i = 0; i < h->size; i++) clear_node(h->data[i]);
    free(h->data);
    free(h);
}
int main() {
    int op;
    #define MAX_N 100
    char str[MAX_N + 5] = {0};
    HashTable *h = init_hashtable(MAX_N + 5);
    while(~scanf("%d %s", &op, str)) {
        switch(op) {
            case 0: {
                printf("insert %s to HashTable\n\n", str);
                insert(h, str);
            }break;
            case 1: {
                printf("search %s from HashTable  result = %d\n\n", str, search(h, str));
            }break;
        }
    }
    #undef MAX_N
    clear(h);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值