哈希表笔记

Hash Table:

Hash,一般翻译做“散列”。哈希表是根据设定的哈希函数H(key)和处理冲突方法将一组关键字映射到一个有限的地址区间上,并以关键字在地址区间中的象作为记录在表中的存储位置。

  • Hash Table Design Decision
    1. Hash Function:
      To map a large key space into a smaller domain
      Searching fast or less collision
    2. Hash Scheme:
      To handle collision after hashing
      Less memory or additional instructions
  • 哈希表对字符串处理很有帮助,比如当比较两个字符串是否相同,就比较字符串的映射是否相同。哈希表的用途是将难以处理的Key转换成以处理的Value,减小查找的复杂度,对于绝大多数插入、删除、查找,哈希表的复杂度都是O(1),处理海量数据时,比如数据库系统管理时,hash是很好的形式,但囿于内存,解决冲突成为哈希设计的重要因素。另外哈希表不适用于查找最大最小值之类的范围查找。
  • 哈希表的应用:统计问题、字符串处理

装填因子:

填入表中的元素个数 / 散列表的长度,一般用 α \alpha α表示

Hash Collision(哈希碰撞/冲突):

  • 不同的Key映射到相同的哈希地址

探测指标:

ASLs:平均成功查找次数
例如:查找某一个Key时,它发生的冲突,是6次,那么它的查找次数是7次,对哈希表中的每一个Key都进行计算,它们的成功查找次数均值就是ASLs

  • ASLu:平均不成功查找次数
    例如:查找某一个Key,当它遇到空值之前仍未探测到相应Value,则它的查找次数就是它第一次遇到空值时累计查找的次数

Hash Function:

开放地址法:
  • 用数组存储

  • 遇到冲突顺着原来的哈希地址向左或右查找直到找到空闲地址,然后插入,假如计算出的地址超出了表长,那么从表尾继续从0开始往后探测,一般公式:
    h a s h 1 i ( K e y ) = ( h a s h 1 i ( K e y ) + d i ) % q hash1_i(Key)=(hash1_i(Key)+d_i) \% q hash1i(Key)=(hash1i(Key)+di)%q

  1. 线性探测: d i = i d_i=i di=i
    容易造成冲突聚集,但一旦有空位一定可以探测得到
    p ( A S L u ) = 1 2 ∗ [ 1 + 1 ( 1 − α ) 2 ] p(ASLu)=\frac{1}{2}*[1+\frac{1}{(1-\alpha)^2}] p(ASLu)=21[1+(1α)21]
    p ( A S L s ) = 1 2 ∗ [ 1 + 1 ( 1 − α ) ] p(ASLs)=\frac{1}{2}*[1+\frac{1}{(1-\alpha)}] p(ASLs)=21[1+(1α)1]
    α = 0.5 \alpha=0.5 α=0.5 A S L u = 2.5 ASLu=2.5 ASLu=2.5 A S L s = 1.5 ASLs=1.5 ASLs=1.5

  2. 平方探测: d i = ± i 2 d_i=±i^2 di=±i2
    冲突聚集较少,但不是所有空位都能探测得到,对于表长 4 k + 3 4k+3 4k+3形式得素数是所有空位都可以探测得到。可以知道,当 d i = ± i 2 d_i=±i^2 di=±i2时若有空位探测不到的情况
    p ( A S L u ) = 1 ( 1 − α ) p(ASLu)=\frac{1}{(1-\alpha)} p(ASLu)=(1α)1
    p ( A S L s ) = − 1 α ∗ l n ( 1 − α ) p(ASLs)=-\frac{1}{\alpha}*ln(1-\alpha) p(ASLs)=α1ln(1α)

  3. 双散列: d i = i ∗ h a s h 2 ( K e y ) di=i*hash2(Key) di=ihash2(Key)

    其中q是大于表长的素数,选素数的原因是减少冲突。

  • 懒惰删除:设计时还需要一个数组记录某一个元素是否被删除
分离链接法:
  • 用数组+链表存储

  • 类似于邻接表,开发定址法在空间不够用时就无法插入,但是拉链法是每次插入都生成一个新的存储空间

性能分析:

  1. 多数时候查找效率O(1),但当散列表十分分布不均匀,比如所有value都用同一个索引,使得哈希表变成一张链表,那么效率会降到O(n)
  2. p ( A S L u ) = α + 1 e α p(ASLu)=\alpha+\frac{1}{e^\alpha} p(ASLu)=α+eα1
    p ( A S L s ) = 1 + 1 α p(ASLs)=1+\frac{1}{\alpha} p(ASLs)=1+α1
  3. 不需要懒惰删除,空间不会被浪费
在密码学中的应用:
  • 哈希算法是一种单向密码体制,即它是一个从明文到密文的不可逆的映射,只有加密过程,没有解密过程。同时,哈希函数可以将任意长度的输入经过变化以后得到固定长度的输出。哈希函数的这种单向特征和输出数据长度固定的特征使得它可以生成消息或者数据。
  1. MD4,MD5,SHA-1
  2. time33
    现在几乎所有流行的HashMap都采用了DJB Hash Function,俗称“Times33”算法。
    time33的核心代码:
long long res(char * str ,int len){
	int hash=5381;//统计方法可得此初值的效率最高
	for(int i=0;i<len;i++){
		hash=hash+(hash<<5)+(long long)str[i];
	}
	return  hash;
}

衡量哈希表好坏的标准:

  1. 装填因子,装填因子越小,冲突可能性越小,冲突多,效率低,冲突少,效率高,但是装填因子很小的时候,要浪费很多空间
  2. 散列函数是否均匀
  3. 冲突处理方法

再散列:

  • 当装填因子过大,会导致冲突增加,查找效率下降,此时需要拓展散列表的表长,然后对散列地址进行重新计算,此过程成为再散列。 α \alpha α的一般可行区间:[0.5,0.85]

字符串哈希:

  • 字符串中,哈希函数的一般形式:
    h a s h [ i ] = ( h a s h [ i − 1 ] ∗ p + i d e x [ s [ i ] ] ) % q hash[i]=(hash[i-1]*p+idex[s[i]]) \% q hash[i]=(hash[i1]p+idex[s[i]])%q
    当hash不模q时,求某一段字符串哈希值的公式:
    h a s h [ L − R ] = h a s h [ R ] − h a s h [ L − 1 ] ∗ p hash[L-R]=hash[R]-hash[L-1]*p hash[LR]=hash[R]hash[L1]p^(R-L+1)
  • 对于纯字母字符串,每一位上的字母有26种可能,把这串字符想象成26进制数,p取26,若s的长度是2,若s[0]=‘a’,则 i d e x [ s [ 0 ] ] idex[s[0]] idex[s[0]]=‘a’-‘0’,hash[0]=97,若s[0]=‘b’,则 i d e x [ s [ 0 ] ] idex[s[0]] idex[s[0]]=‘b’-‘0’,hash[1]=97*26+98为了方便位移计算,p一般取大于可选字符数量的2次幂,当然也可以不用位移,实验表明p取131时效果很好
  • 字符串哈希中,hash[i]一般存储的是,从s[0]到s[i]的一串字符,设计时,q取大素数,一般有取99991,13331等等等等
#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const int N=1e5;
const int q=13331;
const int p=131;
int h[N];
int P[N];

int ltor(int l,int r){
    return h[r]-h[l-1]*P[r-l+1];
}

int main()
{
    //char str[10];
    //scanf("%s",str);
    char *str;
    str="abcabcabc";
    int len=strlen(str);

    memset(h,0,sizeof(h));
    memset(P,0,sizeof(P));

    h[0]=0;
    P[0]=1;

    for(int i=1;i<=len;i++){
        h[i]=h[i-1]*p+str[i-1]-'0';
        P[i]=P[i-1]*p;
    }
    printf("%d\n",ltor(1,3));
    printf("%d\n",ltor(4,6));
    printf("%d\n",ltor(7,9));
    return 0;
}

哈希简单应用题:

USACO 2007 March Gold Gold Balanced Lineup
    用数组index记录index[i]表示从index[0]到index[i]的所有和。对于每一个index[i],依次减去index[1]到index[i-1],若差值数组上每一位数字相同,则符合条件。
假如有3种特性,第i只牛到第j只符合条件,则会有
     ( a i + x , b i + x , c i + x ) = ( a j , b j , c j ) (a_i+x,b_i+x,c_i+x)=(a_j,b_j,c_j) (ai+x,bi+x,ci+x)=(aj,bj,cj)
     ( a i − c i , b i , 0 ) = ( a j − c j , b j − c j , 0 ) (a_i-c_i,b_i,0)=(a_j-c_j,b_j-c_j,0) (aici,bi,0)=(ajcj,bjcj,0)
    所以需要储存的是累加后每一位都减去最后一位的结果,然后找出映射值相同的即可。但是因为减去最后一位还得先从最后一位求,为了避免倒着来,就每一位都减去第一位,也是一样的
AC代码:

#include<iostream>
#include<cstdio>
#include<map>
#include<cstring>
#include<math.h>
#include<algorithm>
using namespace std;
typedef unsigned long long ULL;
const int MAXN=1e5+50;
int N;
int M;
int maxrange;
ULL a[MAXN];
int index[35];
map<ULL ,int> mp;

void input(){
    scanf("%d %d",&N,&M);
    for(int i=1;i<=N;i++)
        scanf("%lld",&a[i]);
}

ULL hashf(){
    ULL p=378551;
    ULL q=63689;
    ULL res=0;
    for(int j=2;j<=M;j++){
        res=res*p+(index[j]-index[1]+N);
        p=p*q;
    }
    return res;
}

void solve(int i){
    ULL res=hashf();
    if(mp[res]){
        maxrange=max(maxrange,i-mp[res]+1);
    }else {
        mp[res]=1+i;
    }
}

void handle(){
	for(int i=0;i<=N;i++){
		for(int k=1;k<=M;k++){
			index[k]+=(a[i]&(1<<(k-1)))>>(k-1);
		}
		solve(i);
	}
}

int main()
{
    memset(index,0,sizeof(index));
    a[0]=0;
    maxrange=0;
    input();
    handle();
    printf("%d\n",maxrange);
    return 0;
}
  • handle()中原本没有用+=,index[k]=index[k]+(a[i]&(1<<(k-1)))>>(k-1);居然和index[k]+=(a[i]&(1<<(k-1)))>>(k-1);不一样,卡死我了,佛了,谜啊
  • 设a=0是因为要使第i组数据自身比较,不要漏了这个数组本身就是符合条件的这种可能
  • 哈希函数从第二位开始算,因为第一位都是0,都是一样的,就可以减少运算

    最开始的想法是,这道题既然用到二进制和十进制,可以想到从数字特征上手找一些捷径。假如从第0只牛牛到第i只牛牛出现过所有特性,并且,每种特性的数量都相同,那么,这个差值会是7的倍数。假如是K种特性,那么就是Kbit位1的倍数。假如用一个数组把这些结果存起来并且用两个for嵌套找出最大值,是超时的,估计是因为取模很耗费时间。
    以后看题一定一定要好好注意限制时间和内存。
超时代码:

#include<iostream>
#include<cstdio>

using namespace std;
typedef long long ll;
const int M=1e5;
ll index[M];

ll getK(int k){
    ll sum=0;
    ll t=1;
    for(int i=0;i<k;i++){
        sum+=t;
        t<<=1;
    }
    return sum;
}

int main()
{
    int N,K;
    scanf("%d %d",&N,&K);
    int i=0;
    ll sum=getK(K);
    while(N--){
        ll temp;
        scanf("%lld",&temp);
        if(i!=0)
            index[i]=(temp+index[i-1])%sum;
        else
            index[i]=temp%sum;
        i++;
    }

    int maxrang=0;
    for(int j=0;j<i;j++){
            if(index[j]==0){
                if(j+1>maxrang){
                    maxrang=j+1;
                    continue;
                }
            }
        for(int k=0;k<j;k++){

            if(index[j]-index[k]==0){
                if(j-k>maxrang){
                    maxrang=j-k;
                    break;
                }
            }
        }
    }
    printf("%d\n",maxrang);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
尚硅谷数据结构韩顺平老师在讲解数据结构时也提到了哈希表哈希表是一种常用的数据结构,它通过哈希函数将关键字映射到一个数组中的位置,以实现快速的查找和插入操作。哈希表的特点是可以在常数时间内进行查找、插入和删除操作,因此在实际应用中被广泛使用。 然而,哈希表也有一些不足之处。首先,哈希表中的数据是没有顺序的,所以不能以一种固定的方式来遍历其中的元素。其次,通常情况下,哈希表中的key是不允许重复的,不能放置相同的key,用于保存不同的元素。 尽管哈希表存在一些不足之处,但是它在实际应用中的性能优势仍然是非常显著的。在处理大量数据时,哈希表可以提供较高的查找效率,使得我们能够更快地找到所需的数据。 因此,尚硅谷数据结构韩顺平老师在讲解数据结构时也强调了哈希表的重要性,帮助学习者了解并掌握这一常用的数据结构。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [韩顺平老师尚硅谷Java数据结构与算法194集笔记](https://download.csdn.net/download/weixin_52184392/32076811)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [【尚硅谷|韩顺平】数据结构和算法](https://blog.csdn.net/ZEZHEN0222/article/details/128624496)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [数据结构与算法-哈希表 | 尚硅谷韩顺平](https://blog.csdn.net/weixin_54232666/article/details/127043618)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值