关键字集合{12,67,56,16,25,37,22,29,15,47,48,34}。
开放定址法
开放定址法公式
在这个例子中,我们取
所以我们遇到冲突的解决方案是
散列函数f(key)=key mod 12。
计算前5个数
key | m | f(key) |
---|---|---|
12 | 12 | 0 |
67 | 12 | 7 |
56 | 12 | 8 |
16 | 12 | 4 |
25 | 12 | 1 |
前5个都是没有冲突的,直接存入,如下表。
下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
---|---|---|---|---|---|---|---|---|---|---|---|---|
关键字 | 12 | 25 | 16 | 67 | 56 |
key=37时,f(37)=37 mod 12 =1,与25冲突,这时候应用上面的公式
f(37)=(f(37) +1)+mod 12=2;
此时2是空闲的,所以把37存入2中。
下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
---|---|---|---|---|---|---|---|---|---|---|---|---|
关键字 | 12 | 25 | 37 | 16 | 67 | 56 |
接下来的22,29,15,47都是没有冲突的,直接存入
下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
---|---|---|---|---|---|---|---|---|---|---|---|---|
关键字 | 12 | 25 | 37 | 15 | 16 | 29 | 67 | 56 | 22 | 47 |
接下来是48
f(48)=48 mod 12=0;冲突
f(48)=(0+1) mod 12=1;冲突
…
f(48)=(5+1) mod 12=6;没有冲突,填入。
下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
---|---|---|---|---|---|---|---|---|---|---|---|---|
关键字 | 12 | 25 | 37 | 15 | 16 | 29 | 48 | 67 | 56 | 22 | 47 |
接下来是34
f(34)=34 mod 12=10;冲突
…
最后的34经过11次冲突之后填入下标为9的空格中
下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
---|---|---|---|---|---|---|---|---|---|---|---|---|
关键字 | 12 | 25 | 37 | 15 | 16 | 29 | 48 | 67 | 56 | 34 | 22 | 47 |
普通开放定址发代码:
#include <iostream>
#include <cstdlib>
#define HASHSIZE 12/*散列表的大小*/
#define NULLKEY -1/*空白标志*/
using namespace std;
typedef struct
{
int *elem;
int count;
}HashTable;
int m = 12;
bool InitHashTable(HashTable *H)/*初始化*/
{
H->elem = (int*)malloc(HASHSIZE * sizeof(int));
for (int i = 0; i < HASHSIZE; i++) H->elem[i] = NULLKEY;
return true;
}
int Hash(int key)/*哈希函数f(key)=key mod m,这里mod取12*/
{
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;/*找到空闲地址,插入*/
}
bool 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 false;/*如果查找到空闲地址或者又回到了原地,则说明该表中没有该关键字*/
}
return true;
}
int main()
{
HashTable *test=new HashTable;
InitHashTable(test);
int a[12] = {12,67,56,16,25,37,22,29,15,47,48,34};/*要插入的key*/
for (int i = 0; i < 12;i++) InsertHash(test,a[i]);
int addr;
SearchHash(test,48, addr);/*查找48所在的地址*/
cout <<"48的地址是:" <<addr<<endl;
cout << "散列表:" << endl;
for (int i = 0; i < HASHSIZE; i++) cout << test->elem[i]<<" ";/*查找哈希数组中各个元素*/
system("pause");
}
让我们来看下程序输出的结果
和理论计算的一样。
二次探测法
在上面的例子中,我们发现48和34要经过很多次冲突才能找到合适的位置,特别是最后一个34,最后的一个空位就在第一次哈希值前面,但是由于我们解决冲突的方案是
使得我们只能向后寻找空闲的位置,而不能向前,那我们能不能改进一下呢?
二次探测法就是解决这个问题的,二次探测法的解决冲突的方案是
也就是我们遇到冲突时,先往右探测1个单位,如果不行,就往左探测一个单位,如果还不行,就往右探测
个单位,如果还不行,就往左探测
个单位…
二次探测法代码:
#include <iostream>
#include <cstdlib>
#include <math.h>
#define HASHSIZE 12/*散列表的大小*/
#define NULLKEY -1/*空白标志*/
using namespace std;
typedef struct
{
int *elem;
int count;
}HashTable;
int m = 12;
bool InitHashTable(HashTable *H)/*初始化*/
{
H->elem = (int*)malloc(HASHSIZE * sizeof(int));
for (int i = 0; i < HASHSIZE; i++) H->elem[i] = NULLKEY;
return true;
}
int Hash(int key)/*哈希函数f(key)=key mod m,这里mod取12*/
{
return key % m;
}
void InsertHash(HashTable *H, int key)/*开放定址法*/
{
int count = 0;/*记录冲突次数,以便调整左右探测方向,冲突次数为偶数时向右探测,为奇数时向左探测*/
int addr = Hash(key);/*计算哈希值*/
int temp = addr;
while (H->elem[temp] != NULLKEY)
{
if(count%2==0)
temp = (addr + (int)pow((1 + count / 2),2)) % m;/*向右探测*/
else
temp = (addr - (int)pow((1 + count / 2), 2)) % m;/*向左探测*/
count++;
}
H->elem[temp] = key;/*找到空闲地址,插入*/
}
bool SearchHash(HashTable *H, int key, int &addr)/*查找*/
{
int count = 0;
addr = Hash(key);
int temp = addr;
while (H->elem[temp] != key)
{
if (count % 2 == 0)
temp = (addr + (int)pow((1 + count / 2), 2)) % m;/*如果该地址已被占用,则地址加一继续寻找*/
else
temp = (addr - (int)pow((1 + count / 2), 2)) % m;
if (H->elem[temp] == NULLKEY || temp == Hash(key)) return false;/*如果查找到空闲地址或者又回到了原地,则说明该表中没有该关键字*/
count++;
}
addr = temp;
return true;
}
int main()
{
HashTable *test=new HashTable;
InitHashTable(test);
int a[12] = {12,67,56,16,25,37,22,29,15,47,48,34};/*要插入的key*/
for (int i = 0; i < 12;i++) InsertHash(test,a[i]);
int addr;
SearchHash(test,48, addr);/*查找48所在的地址*/
cout <<"48的地址是:" <<addr<<endl;
cout << "散列表:" << endl;
for (int i = 0; i < HASHSIZE; i++) cout << test->elem[i]<<" ";/*查找哈希数组中各个元素*/
system("pause");
}
二次探测法构造的散列表和开放定址法有些不一样,至于二次探测法的散列表结果为什么是这样,可以参考上面开放地址法的过程,这里不再详细写二次探测法散列表的构造过程。
下面是两个散列表的对比
方法 | 下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
开放定址法 | 关键字 | 12 | 25 | 37 | 15 | 16 | 29 | 48 | 67 | 56 | 34 | 22 | 47 |
二次探测法 | 关键字 | 12 | 25 | 37 | 15 | 16 | 29 | 34 | 67 | 56 | 48 | 22 | 47 |
参考书籍:《大话数据结构》