设要存储对象的个数为num, 那么我们就用len个内存单元来存储它们(len>=num); 以每个对象ki的关键字为自变量,用一个函数h(ki)来映射出ki的内存地址,也就是ki的下标,将ki对象的元素内容全部存入这个地址中就行了。这个就是Hash的基本思路。
Hash为什么这么想呢?换言之,为什么要用一个函数来映射出它们的地址单元呢?
This is a good question.明白了这个问题,Hash不再是问题。下面我就通俗易懂地向你来解答一下这个问题。
现在我要存储4个元素 13 7 14 11
显然,我们可以用数组来存。也就是:a[1] = 13; a[2] = 7; a[3] = 14; a[4] = 11;
当然,我们也可以用Hash来存。下面给出一个简单的Hash存储:
先来确定那个函数。我们就用h(ki) = ki%5;(这个函数不用纠结,我们现在的目的是了解为什么要有这么一个函数)。
对于第一个元素 h(13) = 13%5 = 3; 也就是说13的下标为3;即Hash[3] = 13;
对于第二个元素 h(7) = 7 % 5 = 2; 也就是说7的下标为2; 即Hash[2] = 7;
同理,Hash[4] = 14; Hash[1] = 11;
好了,存现在是存好了。但是,这并没有体现出Hash的妙处,也没有回答刚才的问题。下面就来揭开它神秘的面纱吧。
现在我要你查找11这个元素是否存在。你会怎么做呢?当然,对于数组来说,那是相当的简单,一个for循环就可以了。
也就是说我们要找4次。
下面我们来用Hash找一下。
首先,我们将要找的元素11代入刚才的函数中来映射出它所在的地址单元。也就是h(11) = 11%5 = 1 了。下面我们来比较一下Hash[1]?=11, 这个问题就很简单了。也就是说我们就找了1次。这个就是Hash的妙处了。至此,刚才的问题也就得到了解答。至此,你也就彻底的明白了Hash了。
Hash冲突的处理
已知一组关键字为(26,36,41,38,44,15,68,12,06,51),用除余法构造散列函数,用线性探查法解决冲突构造这组关键字的散列表。
解答:
为了减少冲突,通常令装填因子α
由除余法的散列函数计算出的上述关键字序列的散列地址为(0,10,2,12,5,2,3,12,6,12)。
前5个关键字插入时,其相应的地址均为开放地址,故将它们直接插入T[0],T[10),T[2],T[12]和T[5]中。
当插入第6个关键字15时,其散列地址2(即h(15)=15%13=2)已被关键字41(15和41互为同义词)占用。
故探查h1=(2+1)%13=3,此地址开放,所以将15放入T[3]中。
当插入第7个关键字68时,其散列地址3已被非同义词15先占用,故将其插入到T[4]中。
当插入第8个关键字12时,散列地址12已被同义词38占用,故探查hl=(12+1)%13=0,而T[0]亦被26占用,再探查h2=(12+2)%13=1,此地址开放,可将12插入其中。
类似地,第9个关键字06直接插入T[6]中;而最后一个关键字51插人时,因探查的地址12,0,1,…,6均非空,故51插入T[7]中。
散列技术既是一种存储方法,也是一种查找方法。
哈希查找是一种秒杀查找,时间复杂度为O(1),只要用key通过哈希函数就直接计算出key的存储位置。
散列技术是在记录关键字的存储位置和关键字本身建立了一种对应关系-------就是一种函数关系f
比如:f(key)=a*key+b (a,b为常数) ,存储的数据是key,存储的位置是f(key),这个f(key)可以是某个数组的下标。
散列函数:这种函数关系如何确定呢?---2个原则:计算简单(计算复杂导致效率低下)、散列地址分布均匀(有效利用空间,同时通过减小冲突而减小耗费时间)
几种散列函数参考:
(1)直接定址法:f(key)=a*key+b (a,b为常数)
(2)数字分析法 :用于数字较长且有特点。比如类似电话号码处理。11位手机号码-数字比较有特点--基本就最后四位是最经常变化的(前三位是运营商,中间四位是地区,后四位是用户编号)。
(3)平方取中法:比如存储关键字1234,平方为1522756,再抽取中间三位227作为存储地址。
(4)折叠法:比如存储9876543210,可将其分为四组987|654|321|0,四组求和987+654+3210=962作为存储地址
(5)取余数法:f(key)=key%p (p≤m,m为散列表的长度)
(6)随机数法:f(key)=random(key)
散列冲突:当存储两个不同的数据时,通过散列函数却计算得到相同的存储位置。f(key1)=f(key2)----散列冲突
解决散列冲突:散列冲突是很糟糕的事情,必须要解决,否则数据无法正常存储和查找。
解决方法参考如下:
(1)开放定址法---线性探测 :fi(key)=(f(key)+di)%m (di=1 ,2 , 3,....)
(2)开放定址法---二次探测:fi(key)=(f(key)+di)%m (di=1 ^2,-1^2,2^2,-2^2 , 3^2,....)
(3)开放定址法---随机探测:fi(key)=(f(key)+di)%m (di是随机数)
(4)再散列函数:就是事先准备多个散列函数,当冲突时更换散列函数再计算。
(5)链地址法:
(4)公共溢出区法:建立多个散列表。其中一个为基本表,另外为溢出表。当冲突时,将数据存储到溢出表中。
创建哈希表思路:
1、确定哈希表的长度,根据长度建立哈希存储空间(比如定义数组)
2、确定散列函数。
3、有了空间可散列函数就要开始存数据了,取数据的关键字key,计算key的存储地址
4、如果有冲突就转到相应的冲突解决方法。
哈希查找思路:
1、给定key值和查找的对象存储空间(比如给以数组或链表)
2、通过散列函数计算key值对应的存储地址
3、同时判别是否冲突,冲突则通过更换地址继续查找,若找完散列表所有地址也没有找到key,则返回查找失败。
散列实现:存储方法:取余数法 + 解决散列冲突:开放定址法---线性探测法
- #include"stdio.h"
- #include"assert.h"
- #include"math.h"
- #include"stdlib.h"
- #define SUCCESS 1
- #define UNSUCCESS 0
- #define HASHBIAOSIZE 15 //决定散列表大小的参数
- #define M 12 //
- #define NULLKEY -65535
- //-----------------------------------------------------------------------------------//
- typedef struct HashTable{
- int* element;//
- int count;//
- }HashTable;
- int m=0;
- void InitHashTable(HashTable *H);
- void InsertHashKey(HashTable* H,int key);
- int Hash_f(int key);
- int SearchHashKey(HashTable* H,int key);
- //-----------------------------------------------------------------------------------//
- void main(){
- int a[]={1,12,5,4,6,8,7,45,21,13,18,35};
- HashTable H;
- InitHashTable(&H);
- for(int i=0;i<M;i++){
- InsertHashKey(&H,a[i]);}
- for(int j=0;j<M;j++){
- if(-1==SearchHashKey(&H,a[j])) printf("没查找到\n");
- else printf("查找元素存储的标号 %d\n",SearchHashKey(&H,a[j]));
- }
- }
- //-----------------------------------------------------------------------------------//
- void InitHashTable(HashTable *H)
- {
- m=HASHBIAOSIZE;
- H->element=(int*)malloc(m*sizeof(int));
- H->count=HASHBIAOSIZE;
- for(int i=0;i<m;i++){
- H->element[i]=NULLKEY;
- }
- }
- //-----------------------------------------------------------------------------------//
- //存储元素
- //这里采用取余数法
- void InsertHashKey(HashTable* H,int key){
- int addr=Hash_f(key);
- while(H->element[addr]!=NULLKEY){ //冲突了
- addr=(addr+1)%M;//解决冲突
- }
- H->element[addr]=key;
- }
- //-----------------------------------------------------------------------------------//
- //查找元素
- int SearchHashKey(HashTable* H,int key){
- int addr=Hash_f(key);
- while(H->element[addr]!=key){
- addr=(addr+1)%M;
- if(H->element[addr]==NULLKEY || addr==Hash_f(key)){return -1;}//UNSUCCESS
- }
- return addr;//查找成功,返回存储位置
- }
- //-----------------------------------------------------------------------------------//
- int Hash_f(int key){
- return key%M;
- }
- //-----------------------------------------------------------------------------------//