哈希表内容的详细整合:
-------------------------------------------
扫盲 :散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。
给定表M,存在函数f(key),对任意给定的关键字值key,代入函数后若能得到包含该关键字的记录在表中的地址,则称表M为哈希(Hash)表,函数f(key)为哈希(Hash) 函数。
4. 折叠法 :将关键字分割成位数相同的几部分,最后一部分位数可以不同,然后取这几部分的叠加和(去除进位)作为散列地址。数位叠加可以有移位叠加和间界叠加两种方法。移位叠加是将分割后的每一部分的最低位对齐,然后相加;间界叠加是从一端向另一端沿分割界来回折叠,然后对齐相加。
冲突处理方法:
-------------------------------------------
扫盲 :散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。
给定表M,存在函数f(key),对任意给定的关键字值key,代入函数后若能得到包含该关键字的记录在表中的地址,则称表M为哈希(Hash)表,函数f(key)为哈希(Hash) 函数。
-
①若关键字为k,则其值存放在f(k)的存储位置上。由此,不需比较便可直接取得所查记录。称这个对应关系f为散列函数,按这个思想建立的表为散列表。
-
②对不同的关键字可能得到同一散列地址,即k1≠k2,而f(k1)=f(k2),这种现象称为碰撞(英语:Collision)。具有相同函数值的关键字对该散列函数来说称做同义词。综上所述,根据散列函数f(k)和处理碰撞的方法将一组关键字映射到一个有限的连续的地址集(区间)上,并以关键字在地址集中的“像”作为记录在表中的存储位置,这种表便称为散列表,这一映射过程称为散列造表或散列,所得的存储位置称散列地址。
-
③若对于关键字集合中的任一个关键字,经散列函数映象到地址集合中任何一个地址的概率是相等的,则称此类散列函数为均匀散列函数(Uniform Hash function),这就是使关键字经过散列函数得到一个“随机的地址”,从而减少碰撞。
哈希表使用
优点:
不论哈希表中有多少数据,查找、插入、删除(有时包括删除)只需要接近常量的时间即0(1)的时间级。实际上,这只需要几条机器指令。
哈希表运算得非常快,在计算机程序中,如果需要在一秒种内查找上千条记录通常使用哈希表(例如拼写检查器)哈希表的速度明显比树快,树的操作通常需 要O(N)的时间级。哈希表不仅速度快,编程实现也相对容易。
如果不需要有序遍历数据,并且可以提前预测数据量的大小。那么哈希表在速度和易用性方面是无与伦比的。
缺点:
缺点它是基与数组的,数组创建后难于扩展某些哈希表被基本填满时,性能下降得非常严重
所以程序虽必须要清楚表中将要存储多少数据(或者准备好定期地把数据转移到更大的哈希表中,这是个费时的过程)
而且,也没有一种简便的方法可以以任何一种顺序〔例如从小到大)遍历表中的数据项。如果需要这种能力
就只能选择其他数据结构。
常用散列函数实现方法:
1. 直接寻址法
:取关键字或关键字的某个线性函数值为散列地址。即H(key)=key或H(key) = a·key + b,其中a和b为常数(这种散列函数叫做自身函数)。若其中H(key)中已经有值了,就往下一个找,直到H(key)中没有值了,就放进去。
2. 数字分析法
:分析一组数据,比如一组员工的出生年月日,这时我们发现出生年月日的前几位数字大体相同,这样的话,出现冲突的几率就会很大,但是我们发现年月日的后几位表示月份和具体日期的数字差别很大,如果用后面的数字来构成散列地址,则冲突的几率会明显降低。因此数字分析法就是找出数字的规律,尽可能利用这些数据来构造冲突几率较低的散列地址
3. 平方取中法
:当无法确定关键字中哪几位分布较均匀时,可以先求出关键字的平方值,然后按需要取平方值的中间几位作为哈希地址。这是因为:平方后中间几位和关键字中每一位都相关,故不同关键字会以较高的概率产生不同的哈希地址。
例:我们把英文字母在字母表中的位置序号作为该英文字母的内部编码。例如K的内部编码为11,E的内部编码为05,Y的内部编码为25,A的内部编码为01, B的内部编码为02。由此组成关键字“KEYA”的内部代码为11052501,同理我们可以得到关键字“KYAB”、“AKEY”、“BKEY”的内部编码。之后对关键字进行平方运算后,取出第7到第9位作为该关键字哈希地址,如下图所示
关键字
|
内部编码
|
内部编码的平方值
|
H(k)关键字的哈希地址
|
KEYA
|
11050201
|
122157778355001
|
778
|
KYAB
|
11250102
|
126564795010404
|
795
|
AKEY
|
01110525
|
001233265775625
|
265
|
BKEY
|
02110525
|
004454315775625
|
315
|
4. 折叠法 :将关键字分割成位数相同的几部分,最后一部分位数可以不同,然后取这几部分的叠加和(去除进位)作为散列地址。数位叠加可以有移位叠加和间界叠加两种方法。移位叠加是将分割后的每一部分的最低位对齐,然后相加;间界叠加是从一端向另一端沿分割界来回折叠,然后对齐相加。
5. 随机数法
:选择一随机函数,取关键字的随机值作为散列地址,通常用于关键字长度不同的场合。
6. 除留余数法
:取关键字被某个不大于散列表表长m的数p除后所得的余数为散列地址。即 H(key) = key MOD p,p<=m。不仅可以对关键字直接取模,也可在折叠、平方取中等运算之后取模。对p的选择很重要,一般取素数或m,若p选的不好,容易产生同义词
1. 开放寻址法(closed hashing):Hi=(H(key) + di) MOD m,i=1,2,…,k(k<=m-1),其中H(key)为散列函数,m为散列表长,di为增量序列,可有下列三种取法:
① di=1,2,3,…,m-1,称线性探测再散列;
② di=1^2,-1^2,2^2,-2^2,⑶^2,…,±(k)^2,(k<=m/2)称二次探测再散列;
③ di=伪随机数序列,称伪随机探测再散列。
2. 再散列法:Hi=RHi(key),i=1,2,…,k RHi均是不同的散列函数,即在同义词产生地址冲突时计算另一个散列函数地址,直到冲突不再发生,这种方法不易产生“聚集”,但增加了计算时间。
3. 链地址法
(拉链法) (open hashing)
4. 建立一个公共溢出区
查找性能分析
散列表的查找过程基本上和造表过程相同。一些关键码可通过散列函数转换的地址直接找到,另一些关键码在散列函数得到的地址上产生了冲突,需要按处理冲突的方法进行查找。在介绍的三种处理冲突的方法中,产生冲突后的查找仍然是给定值与关键码进行比较的过程。所以,对散列表查找效率的量度,依然用平均查找长度来衡量。
查找过程中,关键码的比较次数,取决于产生冲突的多少,产生的冲突少,查找效率就高,产生的冲突多,查找效率就低。因此,影响产生冲突多少的因素,也就是影响查找效率的因素。影响产生冲突多少有以下三个因素:
1. 散列函数是否均匀;
2. 处理冲突的方法;
3. 散列表的装填因子。
散列表的装填因子定义为:α= 填入表中的元素个数 / 散列表的长度
α是散列表装满程度的标志因子。由于表长是定值,α与“填入表中的元素个数”成正比,所以,α越大,填入表中的元素较多,产生冲突的可能性就越大;α越小,填入表中的元素较少,产生冲突的可能性就越小。
实际上,散列表的平均查找长度是装填因子α的函数,只是不同处理冲突的方法有不同的函数。
#define HASHSIZE 32
//待存入表格数据
char *keywords[] = {
"auto", "break", "case", "char", "const", "continue", "default",
"do",
"double", "else", "enum", "extern", "float", "for", "goto",
"if",
"int", "long", "register", "return", "short", "signed", "sizeof",
"static",
"struct", "switch", "typedef", "union", "unsigned", "void", "volatile",
"while"
};
char keybuf[HASHSIZE][10];
static char val_flag[HASHSIZE];//标致已占用存储单元
void ClearFlag()
{
int i;
for (i = 0;i < HASHSIZE;i++)
{
val_flag[i] = (HASHSIZE+1);//清标致位
}
}
//哈希函数,从数据中抽出某个成员用于哈希值的计算
unsigned int hash(char *s)
{
unsigned int hashval;
int i = 0;
for (hashval = 0; *s != '\0'; s++)
hashval = *s + 31 * hashval;
hashval = hashval % HASHSIZE; //计算下标
while ((val_flag[hashval] != (HASHSIZE+1)) && (i<32))
{
i++;
hashval = (hashval + i)%HASHSIZE; //冲突处理,存储单元(下标)偏移
}
if (i<HASHSIZE)
{
printf("\n元素下标(%d): 冲突次数: %d -- ",hashval,i);
val_flag[hashval] = hashval; //表示该单元被占用
return hashval;
}
return -1;
}
int main(void)
{
int i, size, pos;
size = sizeof(keywords) / sizeof(keywords[0]);//计算关键字数量
//将数据存入哈希表
ClearFlag();
for(i = 0;i < size; i++)
strcpy(keybuf[hash(keywords[i])],keywords[i]);
//根据数据结构中某个成员作为索引值,查找对应数据
ClearFlag();
for(i = 0; i < size; i++)
{
pos = hash(keywords[i]);
printf("%-10s: %-3d\n", keybuf[pos], pos);
}
return 0;
}
代码来源:http://blog.chinaunix.net/uid-24951403-id-2212565.html