Hash算法

    在其他各种结构线性表、树等数据结构中。记录在结构中的位置是随机的,和记录keyword之间不存在确定的关系,因此。在结构中查找记录时需进行一系列和keyword的“比較”的基础上。在顺序查找时。比較的结果为“==”与“!=”两种可能;在折半查找、二叉排序树查找和B-树查找时,比較的结果为“<”、"="和“>”3种可能。查找的效率依赖于查找过程中所进行的比較次数。

  理想的情况是希望不经过比較。一次存取便能得到所查记录,那就必须在记录的存储位置和它的keyword之间建立一个确定的相应关系f。使每一个keyword和结构中一个唯一的存储位置相相应。因而在查找时,仅仅要依据这个相应关系f找到给定值K的像f(k)。

若结构中存在keyword和K相等的记录,则必然在f(k)的存储位置上,由此。不须要进行比較便可直接取得所查记录。

在此,我们称这个相应的关系f为哈希(Hash)函数。按这个思想建立的表为哈希表。

     哈希表是种数据结构。它能够提供高速的插入操作和查找操作。

第一次接触哈希表时。它的长处多得让人难以置信。不论哈希表中有多少数据,插入和删除(有时包含側除)仅仅须要接近常量的时间即0(1)的时间级。

实际上。这仅仅须要几条机器指令。对哈希表的使用者一一人来说。这是一瞬间的事。哈希表运算得很快,在计算机程序中,假设须要在一秒种内查找上千条记录通常使用哈希表(比如拼写检查器)哈希表的速度明显比树快,树的操作通常须要O(N)的时间级。哈希表不仅速度快,编程实现也相对easy。

哈希表也有一些缺点它是基与数组的。数组创建后难于扩展某些哈希表被基本填满时。性能下降得很严重,所以程序虽必须要清楚表中将要存储多少数据(或者准备好定期地把数据转移到更大的哈希表中。这是个费时的过程)。

并且。也没有一种简便的方法能够以不论什么一种顺序〔比如从小到大)遍历表中的数据项。假设须要这样的能力,就仅仅能选择其它数据结构。

然而假设不须要有序遍历数据。井且能够提前预測数据量的大小。

那么哈希表在速度和易用性方面是无与伦比的。

散列函数能使对一个数据序列的訪问过程更加迅速有效,通过散列函数,数据元素将被更快地定位:


  我们能够举一个哈希表的最简单的样例。如果要建立一张全国34个地区的各民族人口统计表,每一个地区为一个记录,记录的各数据项为:

编号

地区名

总人口

汉族

回族

...

显然,能够用一个一维数组C(1...30)来存放这张表,当中C[i]是编号为i的地区的人口情况。编号i便为记录的keyword,由它唯一确定记录的存储位置c[i]。比如:如果北京市的编号为1,则若要查看北京市的各族人口,仅仅要取出c[1]的记录就可以。

假如把这个数组看成是哈希表,则哈希函数f(key) = key。

然而,非常多情况下的哈希函数并不如此简单。

可仍以此为例。为了查看方便以地区名作为keyword。

如果。非常多情况下的哈希函数并不如此简单。

可仍以此为例。为了查看方便应以地区名作为keyword。如果地区名以汉语拼音的字符表示,则不能简单地取哈希函数f(key)=key,而是首先要将它们转化为数字,有时候还要作些简单的处理。

比如我们能够有这种哈希函数:(1)取keyword中第一个字母在字母表中的序号作为哈希函数。

比如:BEIJING的哈希函数值为字母“B”在字母表中的序号,等于02。或者(2)先求keyword的第一个和最后一个字母在字母表中的序号之和,然后推断这个和值,若比30(表长)大,则减去30.比如:TIANJIN的两个字母“T”和“N”的序号之和为34,故取04为哈希函数值;或(3)先求每一个汉字的第一个拼音字母的ASCxx码(英文字母同样)之和的八进制形式,然后将这个八进制数看成是十进制数再除以30取余数,若余数为零则加上30而为哈希函数值。

比如:HENAN的头两个拼音字母为“H”和“N”。它们的ASCxx码之和为(226)8,以(226)8除以(30)10得余数为16,则16为HENAN的哈希函数值,即记录在数组中的下标值。上述人口统计表中部分keyword在这3种不同的哈希函数情况下的哈希函数值如表下标所列:

简单的哈希函数演示样例

 

Key

BEIJING(北京)

TIANJING(天津)

HEBEI(河北)

SHANXI(山西)

SHANHAI(上海)

SHANGDONG(山东)

HENAN(河南)

SICHUAN(四川)

f1(key)

02

20

08

19

19

19

08

19

f2(key)

09

04

17

28

28

26

22

03

f3(key)

04

26

02

13

23

17

16

16

从这个样例可见:

  (1)哈希函数是一个映像,因此哈希函数的设定非常灵活。仅仅要使得不论什么keyword由此所得的哈希函数值都落在表长同意范围之内就可以;

  (2)对不同的keyword可能得到同一哈希地址,即key1。=key2,而f(key1)=f(key2),这样的现象称为冲突(collision)。

具有同样函数值的keyword对该哈希函数来说称做同义词(synonym)。

比如:keywordHEBEI和HENAN不等,但f1(HEBEI)=f1(HENAN),又如:f2(SHANXI)=f2(SHANGHAI);f3(HENAN)=f3(SICHUAN)。

这样的现象给建表造成困难,如在第一种哈希函数情况下。由于山西、上海、山东和四川这4个记录的哈希地址造成困难。如在第一种哈希函数的情况下。由于山西、上海、山东和四川这4个记录的哈希地址均为19,而C[19]仅仅能存放一个记录,那么其它3个记录存放在表中什么位置呢?而且。从上表3个不同的哈希函数的情况能够看出,哈希函数选的合适能够降低这样的冲突现象。特别是在这个样例中。仅仅可能有30个记录,能够细致分析者30个keyword的特性,选择一个切当的哈希函数来避免冲突的发生。

  然而,在普通情况下,冲突仅仅能尽可能地少。而不能全然避免。由于,哈希函数是从keyword集合到地址集合的映像。通常。keyword集合集合比較大。它的元素包括全部可能的keyword。而地址集合元素仅为哈希表中的地址值。如果表长为n,则地址为0到n-1。

比如。在C语言的编译程序中可对源程序中的标识符建立一张哈希表。

在设定哈希函数时考虑的keyword集合应包括全部可能产生的keyword;如果标识符定义为以字母为首的8位字母或数字,则keyword(标识符)的集合大小(-----PS:数字大,打印不了····) 。而在一个源程序中出现的标识符合是有限的,设表长为1000足矣。地址集合中的元素为0~999。因此,在普通情况下,哈希函数是一个压缩映像,这就不可避免产生冲突。

因此。在建造哈希表时不仅要设定一个”好“的哈希函数。并且要设定一种处理冲突的方法。

  综上所述,可例如以下描写叙述哈希表:依据设定的哈希函数H(key)和处理冲突的方法将一组keyword映像到一个有限的连续地址集上。并以keyword在地址集中的”像“作为记录在表中存储位置,这样的表便称为哈希表,这一映像过程称为哈希造表或散列,所得存储位置称哈希地址或者散列地址。


哈希表算法-哈希表的构造方法

1、直接定址法

比如:有一个从1到100岁的人口数字统计表,当中。年龄作为keyword,哈希函数取keyword自身。

           但这样的方法效率不高,时间复杂度是O(1),空间复杂度是O(n),n是keyword的个数

 

哈希表算法哈希表算法

 

2、数字分析法

有学生的生日数据例如以下:

年.月.日

75.10.03
75.11.23
76.03.02
76.07.12
75.04.21
76.02.15
...

经分析,第一位,第二位,第三位反复的可能性大。取这三位造成冲突的机会添加,所以尽量不取前三位,取后三位比較好。

3、平方取中法

取keyword平方后的中间几位为哈希地址

4、折叠法

将keyword切割成位数同样的几部分(最后一部分的位数能够不同),然后取这几部分的叠加和(舍去进位)作为哈希地址,这方法称为折叠法。

比如:每一种西文图书都有一个国际标准图书编号,它是一个10位的十进制数字。若要以它作keyword建立一个哈希表,当馆藏书种类不到10,000时,可採用此法构造一个四位数的哈希函数。假设一本书的编号为0-442-20586-4,则:

 

哈希表算法哈希表算法

 

5、除留余数法

取keyword被某个不大于哈希表表长m的数p除后所得余数为哈希地址。

H(key)=key MOD p (p<=m)

例: p =21




6、随机数法

选择一个随机函数,取keyword的随机函数值为它的哈希地址。即

H(key)=random(key) ,当中random为随机函数。通经常使用于keyword长度不等时採用此法。

5、除留余数法

取keyword被某个不大于哈希表表长m的数p除后所得余数为哈希地址。

H(key)=key MOD p (p<=m)

6、随机数法

选择一个随机函数。取keyword的随机函数值为它的哈希地址,即

H(key)=random(key) ,当中random为随机函数。通经常使用于keyword长度不等时採用此法。

5、除留余数法

取keyword被某个不大于哈希表表长m的数p除后所得余数为哈希地址。

H(key)=key MOD p (p<=m)

6、随机数法

选择一个随机函数,取keyword的随机函数值为它的哈希地址,即

H(key)=random(key) ,当中random为随机函数。

通经常使用于keyword长度不等时採用此法。

哈希表算法-处理冲突的方法


哈希表算法

假设两个同学分别叫 刘丽 刘兰。当增加刘兰时。地址24发生了冲突,我们能够以某种规律使用其他的存储位置。假设选择的一个其他位置仍有冲突,则再选下一个,直到找到没有冲突的位置。

选择其他位置的方法有:

1、开放定址法

Hi=(H(key)+di) MOD m i=1,2,...,k(k<=m-1)

当中m为表长。di为增量序列

假设di值可能为1,2,3,...m-1。称线性探測再散列。

假设di取值可能为1,-1,2,-2,4,-4,9,-9,16,-16,...k*k,-k*k(k<=m/2)称二次探測再散列

假设di取值可能为伪随机数列。

称伪随机探測再散列。

例:在长度为11的哈希表中已填有keyword分别为17,60,29的记录。现有第四个记录,其keyword为38。由哈希函数得到地址为5,若用线性探測再散列。例如以下: 

哈希表算法哈希表算法




2、再哈希法

当发生冲突时。使用第二个、第三个、哈希函数计算地址,直到无冲突时。缺点:计算时间添加。

3、链地址法

将全部keyword为同义词的记录存储在同一线性链表中。

哈希表算法

4、建立一个公共溢出区

如果哈希函数的值域为[0,m-1],则设向量HashTable[0..m-1]为基本表,另外设立存储空间向量OverTable[0..v]用以存储发生冲突的记录。


/*
 * 题目:给定一个全部由字符串组成的字典,字符串全部由大写字母构成。

当中为每一个字符串编写password,编写的 * 方式是对于 n 位字符串,给定一个 n 位数,大写字母与数字的相应方式依照电话键盘的方式: * 2: A,B,C 5: J,K,L 8: T,U,V * 3: D,E,F 6: M,N,O 9: W,X,Y,Z * 4: G,H,I 7: P,Q,R,S * 题目给出一个1--12位的数,找出在字典中出现且password是这个数的全部字符串。

字典中字符串的个数不超过5000。 * * 思路:1.回溯法找出全部可能的字符串 * 2.在字典中查找此字符串是否存在。(字典存储採用哈希表存储) * */ #include<stdio.h> #include<stdlib.h> #include<string.h> #define HASHTABLE_LENGTH 5001 //哈希表长度 #define STRING_LENGTH 13 //单词最大长度 //字符串 typedef struct { char str[STRING_LENGTH]; int length; } HString; HString string= {'',0}; //暂存可能的字符串 HString hashTable[HASHTABLE_LENGTH]; //哈希表 //hash函数,构造哈希表 void createHashTable(char *str) { int i,key,step=1; i=key=0; while(str[i]) { key+=str[i++]-'A'; } key%=HASHTABLE_LENGTH; while(1) { if(hashTable[key].length==0) { hashTable[key].length=strlen(str); strcpy(hashTable[key].str,str); break; } key=(key+step+HASHTABLE_LENGTH)%HASHTABLE_LENGTH; //处理冲突。线性探測再散列 if(step>0) step=-step; else { step=-step; step++; } } } //从文件里读字典 void readString() { int i; char str[STRING_LENGTH]; char ch; FILE *fp; if((fp=fopen("document/dictionary.txt","r"))==NULL) { printf("can not open file! "); exit(0); } i=0; while((ch=getc(fp))!=EOF) { if(ch==' ') //读完一个字符串 { str[i]=''; createHashTable(str); i=0; continue; } str[i++]=ch; } if(fclose(fp)) { printf("can not close file! "); exit(0); } } //在哈希表中查找是否存在该字符串,存在返回1,不存在返回0 int search(char *str) { int i,key,step=1; i=key=0; while(str[i]) { key+=str[i++]-'A'; } key%=HASHTABLE_LENGTH; while(1) { if(hashTable[key].length==0) return 0; if(strcmp(hashTable[key].str,str)==0) { return 1; } key=(key+step+HASHTABLE_LENGTH)%HASHTABLE_LENGTH; //处理冲突,线性探測再散列,二次探測 if(step>0) step=-step; else { step=-step; step++; } } return 0; } //求全部可能的字符串 void getString(char* num) { int i,digit,max; if(*num==0) //递归出口,字符串已到末尾 { string.str[string.length]=''; if(search(string.str))//这个字符串存在于字典中。输出 puts(string.str); return; } digit=*num-'0';//取第一位字符,转成数字 if(digit>=2&&digit<=6) { i=(digit-2)*3+'A'; max=(digit-2)*3+'A'+3; } else if(digit==7) { i='P'; max='P'+4; } else if(digit==8) { i='T'; max='T'+3; } else if(digit==9) { i='W'; max='W'+4; } for(i; i<max; i++) { string.str[string.length++]=i; getString(num+1); //递归 string.length--; } } void main() { char num[STRING_LENGTH]; //因为输入的数字超出了unsigned long的范围,所以用字符串来存储 readString(); //把字典从文件里读入内存 printf("please inputer an number(1--12位,不能有0或1) "); scanf("%s",num); getString(num); }




版权声明:本文博客原创文章,博客,未经同意,不得转载。

原文地址:https://www.cnblogs.com/hrhguanli/p/4620574.html