数据结构:哈希表的学习和相关算法题的书写


前言

使用哈希表可以进行非常快速的查找操作。但是,哈希表究竟是啥?很多人避而不谈,虽然知道经常用到,很多语言的内置数据结构像python中的字典,java中的HashMap,都是基于哈希表实现,但人们很少关注其底层实现的细节,今天便来学习哈希表的实现和书写相关算法题

一、What is hashing?

先来看一段标准的定义:

散列(hashing)是电脑科学中一种对资料的处理方法,通过某种特定的函数/算法(称为散列函数/算法)将要检索的项与用来检索的索引(称为散列,或者散列值)关联起来,生成一种便于搜索的数据结构(称为散列表)。也译为散列。旧译哈希(误以为是人名而采用了音译)。它也常用作一种资讯安全的实作方法,由一串资料中经过散列算法(Hashingalgorithms)计算出来的资料指纹(data fingerprint),经常用来识别档案与资料是否有被窜改,以保证档案与资料确实是由原创者所提供。 ----Wikipedia

再来说说笔者的理解:
哈希表是通过离散和映射的方式,用一群较小的数,表示一群较大的数,从而节省计算机的存储空间,并以此提升查找速度。

举个最简单的例子:若要在0-10(9)的数轴上寻找10(5)个的整数并对其进行存储,你不可能去开一个从0到10(9)-1个坐标的数轴数组,然后对于10(5)个的整数输入,每输入一个整数,就在其对应数轴数组上+1,这样其实有很多点并未用到,浪费许多存储空间。

不理解离散和映射的朋友可以看以下这篇博客,体会一下用离散点存储大容量数据的点的好处。

从离散化看区间和题目

二、如何实现哈希表中的映射和带来的问题

一般采用Mod取余操作,用一个大区间mod上小区间,从而将一个大区间映射到小区间上;
如0 - 11中随机产生的数,映射到0 - 5,便是11 mod 5

但是同时这样映射会带来一些问题,如冲突问题,如你6 mod 5,映射到的位置为 1, 11 mod 5, 映射到的位置同样为 1;如此便带来了冲突问题。

那如何解决冲突问题,便引来了哈希表的分类。

三、哈希表的分类

依靠哈希表解决映射带来的冲突问题的不同解决方法,我们对哈希表进行分类,分为拉链法实现和寻址法实现。

拉链法

拉链法又称链地址法,是指当元素映射到哈希表的过程中,不同的元素映射到哈希表的同一位置时,这时采用在冲突的位置上引出一个链表,依次将冲突的元素添加到链表上的方法。

具体过程如下:

  • 计算 key 的 hashValue
  • 根据 hashValue 值定位到 table[hashIndex] 。( table[hashIndex] 是一条链表Node)
  • 若 table[hashValue] 为空则直接插入,不然则添加到链表末尾
    在这里插入图片描述
    这里借助一道题目,来演示拉链法的实际应用
    题目如下:
    在这里插入图片描述
//拉链法书写哈希表
#include<iostream>
#include<cstring>
using namespace std;
const int N=100003;
int h[N],e[N],ne[N],idx=0;//h表示哈希表的坐标值指向拉链法的第一个头,e表示值,ne表示下一个坐标值,idx表示当前使用到哪个点
int n;
void insert(int x){
    int index=(x%N+N)%N;//这里排除负数的情况
    e[idx]=x;
    ne[idx]=h[index];
    h[index]=idx++;
}
bool query(int num){
    int index=(num%N+N)%N;
    for (int i=h[index];i!=-1;i=ne[i]){
        if (e[i]==num)
            return true;
    }
    return false;
}
int main(){
    cin>>n;
    memset(h,-1,sizeof h);//设置初始指向为-1
    while(n--){
        char op[2];
        int num;
        scanf("%s%d",op,&num);
        
        if (op[0]=='I'){
            insert(num);
        }else{
            if (query(num)){
                cout<<"Yes"<<endl;
            }else{
                cout<<"No"<<endl;
            }
        }
    }
    return 0;
}

寻址法

这里所说的寻址法一般是指开放寻址法,即先将要插入哈希表的数映射到哈希表的某个位置上,若这个位置存在数,则往后延一个位置,若后一个位置也存在数,则继续延后,若延后到了尽头,则返回到第第一个数,继续搜寻,直到找到合适的位置或寻找完毕退出;

图示
在这里插入图片描述
×表示该位置山有数了,进行后延
√表示找到合适的位置了,进行插入

这里用寻址法解决前文的题目

//寻址法书写哈希表
#include<iostream>
#include<cstring>
using namespace std;
const int N=200003,nullNum=0x3f3f3f3f;
int h[N];
int find(int k){
    int index=(k%N+N)%N;
    while(h[index]!=nullNum && h[index]!=k){
        index++;
        if (index==N) index=0;
    }
    return index;
}
int main(){
    int n;
    cin>>n;
    memset(h,nullNum,sizeof h);
    while(n--){
        char op[2];
        int num;
        scanf("%s%d",op,&num);
        int loc=find(num);
        if (op[0]=='I'){
            h[loc]=num;
        }else{
            if (h[loc]!=nullNum){
                cout<<"Yes"<<endl;
            }else{
                cout<<"No"<<endl;
            }
        }
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值