一、哈希表
哈希表是一种存储结构,它并非适合任何情况,主要适合记录的关键字与存储地址存在某种函数关系的数据。
从头到尾顺序查找,时间复杂度为O(n)。
若学号有序,二分查找,时间复杂度为O(log2n)
二、哈希冲突
对于两个关键字分别为ki和kj(i≠j)的记录,有ki≠kj,但h(ki) = h(kj)。把这种现象叫做哈希冲突(同义词冲突)。
在哈希表存储结构的存储中,哈希冲突是很难避免的
哈希表设计主要需要解决哈希冲突。实际中哈希冲突是难以避免的,主要与3个因素有关:
三、哈希函数
1.哈希函数构造方法
构造哈希函数的目的是使所有元素的哈希地址尽可能均匀分布在m个连续的内存单元上,同时使计算过程尽可能简单以达到尽可能高的时间效率。
根据关键字的结构和分布的不同尽可能构造出不同的哈希函数
a.直接定址法 关键字本身或关键字加上某个常量c作为哈希地址的方法。 h(k)= k + c ;
b.除留余数法 关键字k除以某个不大于哈希表长度m的整数p所得的余数作为哈希地址
h(k) = k mod p (mod为取余运算,p<=m);
2.哈希冲突的解决方法
1.开放地址法
出现哈希冲突时,在哈希表中在找一个新的空闲位置存放元素。 (同义词冲突、非同义词冲突)
两种方式:
线性探测法 平方探测法
a.线性探测法 :从发生冲突的地址开始,依次探测下一个地址。
b.平方探测法.:设发生冲突的地址为do,则平方探测法的探测序列依次为
do + 1^2 、 do - 1^2 、 do + 2^2 、 do - 2^2 ……
四、平均查找长度
1.查找成功的平均查找长度是指查找到哈希表中已有关键字的平均探测次数,实际上,查找到一个关键字所需要的比较次数恰好等于对应的探测次数。
例;假设哈希表的长度m=13,采用除留余数法加线性探测法建立关键字集合(16,74,60,43,54,90,46,31,29,88,77)
下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
关键字 | 77 | 54 | 16 | 43 | 31 | 29 | 46 | 60 | 74 | 88 | 90 | ||
探测次数 | 2 | 1 | 1 | 1 | 1 | 4 | 1 | 1 | 1 | 1 | 1 | ||
ASL成功 | ( 1*9 + 2*1 + 4*1 )/11 = 1.364 |
2.查找不成功的平均查找长度是指在哈希表中查找不到待查的元素,最后找到空位置的探测次数的平均值
例:(16,74,60,43,54,90,46,31,29,88,77)
下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
关键字 | 77 | 54 | 16 | 43 | 31 | 29 | 46 | 60 | 74 | 88 | 90 | ||
探测次数 | 2 | 1 | 1 | 1 | 1 | 4 | 1 | 1 | 1 | 1 | 1 | ||
探测失败次数 | 2 | 1 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 3 |
ASL失败 | ( 2+1+10+9+8+7+6+5+4+3+2+1+3 )/13 = 4.692 |
五、完整代码实现
#include
using namespace std;
#define NULLKEY -1 //定义空关键字值
typedef int KeyType;//关键字类型
//哈希表结构体
typedef struct
{
KeyType key;//关键字域
int count;//探测次数
}HashTable;//哈希表单元域
//哈希表插入元素
//插入及建表算法
void InsertHT(HashTable ha[], int& n, int m, int p, KeyType k)//将关键字k插入哈希表中
{
int i, adr; int cnt;
adr = k % p;
if (ha[adr].key == NULLKEY)
{
ha[adr].key = k;
ha[adr].count = 1;
}
else
{ //发生错误时用线性探测法解决冲突
//cnt记录k发生冲突的次数
cnt = 1;
do
{
adr = (adr + 1) % m;
cnt++;
} while (ha[adr].key != NULLKEY);
//在adr处放置k
ha[adr].key = k;
//设置探测次数
ha[adr].count = cnt;
}
//哈希表的总元素个数加一
n++;
}
//创建哈希表
void CreatHT(HashTable ha[], int& n, int m, int p, KeyType keys[], int total)
{
//由关键字序列keys[]创建哈希表
for (int i = 0; i < m; i++)//哈希表置空的初值
{
ha[i].key = NULLKEY;
ha[i].count = 0;
}
n = 0;//哈希表的元素从0开始递增
for (int i = 0; i < total; i++)
{
InsertHT(ha, n, m, p, keys[i]);//不断调用,不断插入
}
}
//删除哈希表
//删除哈希表中的关键字k
bool DeleteHT(HashTable ha[], int& n, int m, int p, KeyType k)
{
int adr;
//计算哈希函数值k
adr = k % p;
//线性探测法 (删除的位置肯定是不为空 删除的位置等于关键字k)
while (ha[adr].key != NULLKEY && ha[adr].key != k)
adr = (adr + 1) % m;
//如果探测到关键字k
if (ha[adr].key == k)
{
//j为adr的循环后继位置
int j = (adr + 1) % m;
//查找adr位置后面的同义词
while (ha[j].key != NULLKEY && ha[j].key % p == k % p)
{ //将同义词不断前移
//继续试探循环后继位置
ha[(j - 1 + m) % m].key = ha[j].key;
j = (j + 1) % m;
}
//删除最后一个同义词
ha[(j - 1 + m) % m].key = NULLKEY;
ha[(j - 1 + m) % m].count = 0;
//哈希表的元素个数-1
n--;
//查找成功返回true
return true;
}
//查找失败的情况
else
//返回false
return false;
}
//查找算法
void SearchHT(HashTable ha[], int m, int p, KeyType k)
{
int cnt = 1, adr;
adr = k % p;//计算哈希函数值
while (ha[adr].key != NULLKEY && ha[adr].key != k)
{
cnt++;//累计关键字的比较次数
adr = (adr + 1) % m;//线性探测
}
//查找成功
if (ha[adr].key == k)
{
printf("成功:关键字%d,探测%d次\n", k, cnt);
}
//查找失败
else
{
printf("失败:关键字%d,探测%d次\n", k, cnt);
}
}
void ASL(HashTable ha[], int n, int m, int p)//求平均查找长度
{
int i, j;
int succ = 0, unsucc = 0, s;
for (i = 0; i < m; i++)
if (ha[i].key != NULLKEY)
succ += ha[i].count;//累计成功时总的关键字探测次数
printf("成功情况下 ASL(%d)=%g\n", n, succ * 1.0 / n);
for (i = 0; i < p; i++)
{
s = 1; j = i;
while (ha[j].key != NULLKEY)
{
s++;
j = (j + 1) % m;//不断的后移,直到探测到空位置
}
unsucc += s;
}
printf("不成功情况下 ASL(%d)=%g\n", n, unsucc * 1.0 / p);//注意:被除数与选择的数字有关,不一定等于哈希表的长度
}
int main()
{
//所要创建的哈希表的长度
HashTable ha[13];
int n = 0;//哈希表当前所含有关键字的个数
int m = 13;//m是哈希表的长度
int p = 13;//选择的哈希函数中的被除数 -- 除留余数法
KeyType keys[] = { 4,7,3,9,5,1,11,16,21 };//关键字表
CreatHT(ha, n, m, p, keys, 9);//创建哈希表
SearchHT(ha, m, p, 30);//查找关键字
ASL(ha, n, m, p);//求成功和失败平均查找长度
}