浅谈哈希表


##为什么要用hash?

​ 在讲哈希表之前,我们来说说链表和数组的优缺点:

链表:

​ 优点是链表长度可变,可以方便的进行链表的插入和删除

​ 缺点是由于不是申请的一块连续地址空间,所以无法快速定位元素,只能通过遍历链表的方式获取其中元素,需要O(n)时间复杂度

数组:

​ 优点是可以快速定位元素

​ 缺点是数组长度固定,难以扩充大小。且数组中元素进行增删操作需要O(n)时间复杂度

​ 我们发现,链表和数组的优缺点正好互补,那么我们可不可以用一种新的数据结构来代替链表和数组,使其继承它们各自的优点呢?

​ 于是,哈希表出现了。它的长度可变,可以快速定位,能够方便的进行增删改查,时间复杂度几乎为O(1)

哈希表与哈希函数

​ 哈希表:是可以根据key值通过某种映射关系而直接访问的数据结构,用于存放记录

​ 哈希函数:上述映射关系即为哈希函数

​ 因此,在将哈希表之前,我们先来说一下哈希函数

哈希函数

​ 哈希函数就像高中所学的函数f(),代表一种映射关系,它可以把字符串,数字等等映射为一个无符号整型,以便快速找到存储该元素信息的位置

​ 注意!我们在自定义哈希函数时,要根据关键字key的长度,表长,关键字分布是否均匀等条件来相应的进行自定义!!

​ 哈希函数的映射关系有很多,比较常用的有一下几种:

直接定址法

​ 一般形式为f(x) = a * x + b,a,b为自定义常数,比如:

unsigned val = 0;
for (int i = 0; i < strlen(s); i++) {
    val = val * 33 + s[i];
}

平方取中法

​ 线球关键值的平方值,通过平方扩大差异,而后取中间数位作为最终存储地址,比如:

unsigned val = 0;
for (int i = 0; i < strlen(s); i++) {
    val = val * 33 + s[i];
}
val = val * val / p % q;		//p,q为常数

除留余数法

​ 一般形式为:f(x) = x % p,其中p为不超过表长的质数(因为这样可以减少地址重复)

随机数法

​ 一般形式为:f(x)=random(x)

哈希冲突

​ 由上边的哈希函数我们可以看出,可能多个不同的key值通过哈希函数最后映射得到的值为同一个数,这样会导致哈希冲突。我们有一下两个常用的方法解决哈希冲突:

开放定址法

​ 如果遇到哈希冲突,我们就找hash表中剩余的空间,然后将其插入(比如将值插入到下一位)

链地址法

​ 上个方法有一个局限性,就是当表的内容已经填满时就无法进行插入,而该方法可以解决这个问题。这也是哈希表最常用的方法。

​ 该方法如果遇到哈希冲突,他就会在原地址新建一个空间,然后以链表节点的形式插入到该空间。

哈希表的实现

​ 我们以链地址法为例,代码如下:

//哈希函数
unsigned int hash(char *key) {
    unsigned int val = 0;
    for (int i = 0; i < strlen(key); i++) {
        val = val * 33 + key[i];
    }
    return val % MAXN;
}

//定义节点
struct Node {
    char *key;
    char *val;
    Node *next;
}

//定义哈希表
struct Hash {
    Node *head[MAXN];
    int len;
    Hash() {
     	for (int i = 0; i < MAXN; i++) {
            this -> head[i] = NULL;
     	}
     	this -> len = 0;
    }
}

//查
Node* query(Hash *h, char *key) {
    unsigned inx = hash(key);
    Node *p = h -> head[inx];
    while(p != NULL) {
        if(!strcmp(p -> key, key)) {
            return p;
        }
        p = p -> next;
    }
    return NULL;
}

//增或改
void insert(Hash *h, char *key, char *val) {
  	Node *p = query(h, key);
    if (p == NULL) {
        unsigned inx = hash(key);
        p = (Node *)malloc(sizeof(Node));
        p -> key = key;
        p -> next = h -> head[inx];
        head[inx] = p;
    }
    p -> val = val;
}

//删
bool del(Hash *h, char *key) {
    Node *p = query(h, key);
    if (p == NULL) return false;
    unsigned inx = hash(key);
    if (!strcmp(h -> head[inx] -> key, key)) {
        h -> head[inx] = p -> next;
        free(p);
        return true;
    }
    Node *q = h -> head[inx];
    while(q -> next != NULL) {
        if (!strcmp(q -> next -> key, key)) {
            q -> next = p -> next;
            free(p);
        }
        q = q -> next;
    }
    return true;
}

转载请注明出处!!!

如果有写的不对或者不全面的地方 可通过主页的联系方式进行指正,谢谢

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值