您的位置:首页 > 其它

散列表(哈希表)

2014-02-27 11:04 441 查看
散列技术:散列技术是在记录的存储位置和它的关键字之间建立一个确定的对应关系f,使得每个关键字key对于一个存储位置f(key)。查找时,根据这个确定的对应关系找到给定值key的映射f(key),若查找集合中存在这个记录,则必定在f(key)的位置上。通过查找关键字不需要比较就可以获得需要的记录的存储位置。

散列函数:这里我们把这种对应关系f称为散列函数,又称为哈希函数。

散列表(哈希表):采用散列函数将记录存储在一块连续的存储空间中,这块连续存储空间称为散列表或哈希表。

整个散列过程分为两步:

1、在存储时,通过散列函数计算记录的散列地址,并按此散列地址存储该记录。

2、当查找记录时,通过同样的散列函数计算记录的散列地址,按此散列地址访问该记录。

所以说,散列技术既是一种存储方法,也是一种查找方法。

散列技术的记录之间不存在什么逻辑关系,它只与关键字有关,因此,散列主要是面向查找的存储结构。散列技术最适合的求解问题是查找跟给定值相等的记录。

设计一个简单、均匀,存储利用率高的散列函数是散列技术中最关键的问题。

两个关键字key1!=key2,但是却有f(key1)=f(key2),这种现象我们称为冲突,并把key1和key2称为这个散列函数的同义词

散列函数的设计原则:

1、简单:散列函数的计算时间不应该超过其他查找技术与关键字比较的时间。

2、散列地址分布均匀:这样可以保证存储空间的有效利用,并减少为处理冲突而耗费的时间。

散列函数的构造方法:

1、直接定址法:取关键字的某个线性函数值作为散列地址。f(key) = a x key + b(a、b是常数)。适合查找表较小且连续的情况。虽简单但不常用。

2、数字分析法:抽取方法是使用关键字的一部分来计算散列存储位置的方法,还可以对抽取出来的数字再进行反转,右环位移,左环位移,甚至前两数与后两数叠加等方法。适合处理关键字位数较大的情况,如果事先知道关键字的分布且关键字的若干位分布均匀,就可以考虑用这个方法。

3、平方取中法:取关键字的平方数的中间几位数字作为散列地址。适合不知道关键字的分布,而位数又不是很大的情况。

4、折叠法:将关键字从左到右分割成位数相等的几部分(最后一部分位数不够时可以短些),然后将这几部分叠加求和,并按散列表表长,取后几位作为散列地址。适合事先不知道关键字的分布,关键字位数比较多的情况。

5、除留余数法:对于散列表长为m的散列函数公式为:f(key) = key mod p (p <= m)。mod是取模,求余数的意思。这个方法不仅可以对关键字直接取模,也可以在折叠,平方取中后再取模。通常p为小于或等于(最好接近于m)的最小质数或不包含小于20质因子的合数。

6、随机数法:f(key) = random(key),适合关键字长度不等的情况。

选择散列函数的考虑因素:1、计算散列地址所需的时间;2、关键字的长度;3、散列表的大小;4、关键字的分布情况;5、记录查找的频率。

处理散列冲突的方法:

1、开发地址法:就是一旦发生了冲突,就去寻找下一个空的散列地址,只要散列表足够大,空的散列地址总能找到,并将记录存入。

线性探测法:fi(key) = (f(key) + di) mod m (di = 1, 2, 3, ……, m -1)

二次探测法:fi(key) = (f(key) + di) mod m (di = 1, -1, 4, -4, ……,q平方, -q平方 )(q <= m/2)

随机探测法:fi(key) = (f(key) + di) mod m (di 是一个随机数列)

堆积:两个关键字本来不是同义词却需要争夺一个地址的情况

2、再散列函数法:fi(key) = RHi(key)(i = 1, 2, 3, ……, k)。这种方法能够使得关键字不产生聚集,但也相应地增加了计算的时间。

3、链地址法:将所有关键字为同义词的记录存储在一个单链表中,我们称这种表为同义词子表。在散列表中只存储所有同义词子表的 头指针。提供了绝不会出现找不到地址的故障,但也带来查找时需要遍历单链表的性能损耗。

4、公共溢出区法:为所有冲突的关键字建立了一个公共的溢出区来存放,把它们存储到溢出表中。查找时,对给定值通过散列函数计算出散列地址后,先于基本表的相应位置进行比对,如果相等,则查找成功;如果不相等,则到溢出表去进行顺序查找。如果相对于基本表而言,有冲突的数据很少的情况下,公共溢出区的结构对于查找性能来说还是非常高的。

散列查找性能分析:

1、散列函数是否均匀:不同的散列函数对于同一组随机的关键字,产生冲突的可能性是相同的,因此可以不考虑它对平均查找长度的影响。

2、处理冲突的方法:相同的关键字,相同的散列函数,但处理冲突的方法不同,会使得平均查找长度不同。

3、散列表的装填因子:装填因子=填入表中记录的个数/散列表长度。当填入表中的记录越多,装填因子就越大,产生冲突的可能性就越大。

散列表的平均查找长度取决于装填因子,而不是取决于查找集合中的记录个数。通常都是将散列表的空间设置得比查找集合大,此时虽然浪费了一定的空间,但换来的查找效率大大提升,总的来说,还是非常值得的。

#define SUCCESS 1
#define UNSUCCESS 0
#define HASHSIZE 12
#define NULLKEY -32768
typedef struct
{
int *elem;
int count; //散列表长度
} HashTable;
int m = 0;

//初始化散列表
Status InitHashTable(HashTable *H)
{
int i;
m = HASHSIZE;
H->count = m;
H->elem = (int *)malloc(m * sizeof(int));
for (i = 0; i < m; i++)
H->elem[i] = NULLKEY;
return OK;
}

//散列函数
int Hash(int key)
{
return key % m;
}

//把记录插入散列表中
void InsertHash(HashTable *H, int key)
{
int addr = Hash(key);
while (H->elem[addr] != NULLKEY)
addr = (addr + 1) % m; //开放地址法的线性探测法
H->elem[addr] = key;
}

//散列表查找
Status SearchHash(HashTable H, int key, int *addr)
{
*addr = Hash(key);
while (H.elem[*addr] != key)
{
*addr = (*addr + 1) % m;
if (H.elem[*addr] == NULLKEY || *addr == Hash(key))
return UNSUCCESS;
}
return SUCCESS;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  散列表 哈希表