开放定址法与二次探测法构造散列表

关键字集合{12,67,56,16,25,37,22,29,15,47,48,34}。

开放定址法

开放定址法公式 f i ( k e y ) = ( f i 1 ( k e y ) + d i )   m o d   m   ( d i = 1 , 2 , 3 ) f_i(key)=(f_{i-1}(key)+d_i)\space mod\space m\space (d_i=1,2,3)

在这个例子中,我们取 d i = 1 , m = 12 d_i=1,m=12 所以我们遇到冲突的解决方案是
f i ( k e y ) = ( f i 1 ( k e y ) + 1 )   m o d   12 f_i(key)=(f_{i-1}(key)+1)\space mod\space 12

散列函数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,最后的一个空位就在第一次哈希值前面,但是由于我们解决冲突的方案是
f i ( k e y ) = ( f i 1 ( k e y ) + 1 )   m o d   12 f_i(key)=(f_{i-1}(key)+1)\space mod\space 12
使得我们只能向后寻找空闲的位置,而不能向前,那我们能不能改进一下呢?

二次探测法就是解决这个问题的,二次探测法的解决冲突的方案是
f i ( k e y ) = ( f i 1 ( k e y ) + d i )   m o d   m ( d i = 1 2 , 1 2 , 2 2 , 2 2 . . q 2 , q 2 , q m / 2 ) f_i(key)=(f_{i-1}(key)+d_i)\space mod\space m(d_i=1^2,-1^2,2^2,-2^2..q^2,-q^2,q\leq m/2)
也就是我们遇到冲突时,先往右探测1个单位,如果不行,就往左探测一个单位,如果还不行,就往右探测 2 2 2^2 个单位,如果还不行,就往左探测 2 2 2^2 个单位…

二次探测法代码:

#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

参考书籍:《大话数据结构》

发布了9 篇原创文章 · 获赞 2 · 访问量 670

猜你喜欢

转载自blog.csdn.net/qq_41195985/article/details/103596591
今日推荐