哈希表学习

本文探讨了哈希表在数据结构中的应用,尤其是数组和链表结合的哈希表设计。哈希表通过哈希函数将键映射到数组位置,遇到哈希冲突时,通常采用开放寻址法和拉链法来解决。开放寻址法通过寻找下一个空位解决冲突,而拉链法则通过链表连接相同位置的元素。当链表过长时,可以转换为树形结构以优化。文章还提到了负载因子和扩容策略,并通过代码示例展示了两种不同的哈希表实现方式。
摘要由CSDN通过智能技术生成

数组+链表的哈希表学习。

个人理解,简单来说是通过数组,将数组中的每一个位置都被指针指向链表中的某一个存在,需要注意的在哈希表当中,存在很多的特殊情况,比如说,在哈希表当中,需要使用到哈希函数,哈希函数的使用,可能得到的相应的key值相同,这个时候,如果使用链表不断地对上一个进行next的连接那么假如存在足够多的样例,那么就会出现一个很麻烦的情况,即在一个位置存在过长的链表,那么考虑到空间的复杂程度,和内存占用等情况,可以说是并不方便的特殊情况,这个时候需要做的就是将这个过长的链表进行分段,比如说将1*10^9拆分成为10^5*10^4,从而减少可能出现的特殊情况。

言归正传,在哈希表当中,需要的是一个key,一个数组,和以备不时之需所需要的链表。

首先学习可以知道,哈希表和散列表说的是同样的一个东西,在哈希表当中最经常遇到的问题是是哈希冲突,而处理哈希冲突的最好的办法还是开放寻址法还有一个是拉链法,听名字可以知道开放寻址法是一个很简单的思路,即对于每一个位置都进行这样的一步。重要是在使用哈希函数将这个初始的需要计算的哪个部分。在不同的计算方式下存在相同的结果的可能依然存在。那么不同的处理方式会有不一样的结果,

开放寻址法

比若说我开始两个都计算结果是1,那么只能让最开始计算出结果的放在1的位置。而第二个就只能将那个存在后面有空间的位置,我们就看看2的位置,如果没有被占用,那就放到这里呗,当然,也有可能2的位置也被占用了,那咱就继续往下找,看看3的位置,一次类推,直到找到空位置。

这个我在学习中有一些疑问,假如多次出现了计算结果都是1的情况,那么这个排列就很容易失去意义的感觉。所以我可不可以在使用该方法时进行多次计算,在多种哈希函数后进行排列,在第一轮中首次出现的占在该位置,剩下的从新计算,重点是这种方法在随用随存储中可能并不方便,所以可能需要利用链表对其进行暂时的存储。

需要注意的是选择数组和链表的结合更重要的特点是在时间的优势,在空间上并不一定会比传统的数组空间小,甚至可能在相同的数据需要更多的内存才可以正常将其使用,但这也是没有办法的事情,毕竟大部分的数据结构都是需要在空间和时间上进行一定程度上的取舍。

在其中key即为哈希函数,可以对哈希表中的特殊元素进行转换。

在哈希表当中,他经常存放的并不是存数值,然后他更多的是存放一个键值对,即一个值对应一个值或者说是比如a对应b,那么a就是key,b是value,哈希表存放的就是这样的键值对,在哈希表中是通过哈希函数将一个值映射到另外一个值的,所以在哈希表中,a映射到b,a就叫做键值,而b呢?就叫做a的哈希值,也就是hash值。而在这里经常可以看到Entry,这个说法其实实质上就是键值对,只不过是为了和其他的数据结构中的键值对。

在不同的键值对也有可能通过同一个哈希函数变换成为相同位置的成分。那么便会产生哈希冲突,在面对哈希冲突的时候有两种做法,第一种是开放寻址,另外一种是采用拉链法。

开放寻址简单的来说,最基本的就是既然当前位置被占用了,我们就看看该位置的后一个位置是否可用,也就是1的位置被占用了,我们就看看2的位置,如果没有被占用,那就放到这里呗,当然,也有可能2的位置也被占用了,那咱就继续往下找,看看3的位置,一次类推,直到找到空位置。

这个方法在java中常用,比如说,threadlocal即利用了开放寻址法。

而拉链法在我看来是将链表更大程度的利用,在拉链法当中,需要的是两个指向,第一个是正常的指向,即指向的原本需要给到的数据,另外要在存放一个next,用来直接指向新出现的冲突。

也就是保存的这个位置的内存地址,如果还有冲突,那就把又冲突的那个Entry放在一个新位置上,然后新的Entry中的next指向它,这样就形成了一个链表。

这样存在链表可能过长,就会导致哈希表中分支链变的太过于复杂。那在一般情况下会在长度小于6的时候保持链表的存在,但在假如超过了8,即需要将其改变,形成树的形式。

在这里还存在一个问题,在学习的过程中,他说道之所以小于等于6而不是7的主要原因是,中间有个7作为一个差值,来避免频繁的进行树和链表的转换,因为转换频繁也是影响性能的。但存在的这个7是保持原状还是什么情况并不清楚,后续再学习。

关于在哈希表使用的过程中需要扩容是常见的情况,在开始设置哈希表的时候不可能给与太多的内存空间,在这个过程中需要的是检测负载因子的大小,比如说我可能会将其设置在0.7,即在内存使用超过了70%的情况下。可以通过对数组的扩大二倍然后再对其重新填入即可。

当然在好的哈希表中会减少哈希冲突的存在,假如存在过于简单哈希表,可能导致对你程序的多轮哈希冲突,会显著的增加反应时间

举个例子,在luogu的p3370

#include<bits/stdc++.h>
#define az 30001
using namesazace std;
int n,ans;
string s,hash[az];
int hashmath(string x)//哈希函数
{
    int ans=0;
    for (int i=0;i<x.size();i++)
    {
        ans=(ans+x[i])%az;
    }
    return ans%az;
}
int locate(string x)//寻找插入位置
{
    int wz=hashmath(x);
    int i=0;
    while (i<az && hash[(wz+i)%az]!=x && hash[(wz+i)%az]!="")
      i++;
    return (wz+i)%az;
}
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>s;
        int wz=locate(s);
        if(hash[wz]!=s){
          hash[wz]=s;
          ans++;
        }//有新字符串
    }
   sout<<ans<<endl;
}

当然也可以这样解决

#include<bits/stdc++.h>
using namespace std;
#include<map>
#include<string>
int main()
{
	map<string,int> m;
	int ans=0;
	int N;
	cin>>N;
	while(N--)
	{
		string s;
		cin>>s;
		if(m[s]==0) {ans++;m[s]=1;}
	}
	cout<<ans;
	
} 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值