开放定址哈希表的实现(第九章 P259 算法9.17,9.18)

简介

哈希表,也称散列表,是实现字典操作的一种有效的数据结构。尽管最坏情况下,散列表查找一个元素的时间与链表中查找的时间相同,达到了O(n)。然而在实际应用中,散列表查找的性能是极好的。在一些合理的假设下,在散列表中可以查找一个元素的平均时间是O(1)。

哈希表的精髓在于哈希二字上面,也就是数学里面常用到的映射关系。它是通过哈希函数将关键字映射到表中的某个位置上进行存放,以实现快速插入和查询的。

注意,哈希表里一般存放的字典类型数据,即(key, value)的数据,是根据key去存取value。

 

 

常用哈希表的构造方法

  

(一)直接定址法

取关键字或者关键字的某个线性函数值为哈希地址。

比如:H(Key) = key或H(key) = a * key + b,其中a,b为常数

 

(二)数字分析法

事先知道关键字的集合,且每个关键字的位数比哈希表的地址码位数多时,可以从关键字中选出分布比较均匀的若干位,构成哈希地址。

 

(三)平方取中法

当无法确定关键字中哪几位分布比较均匀时,可以先求出关键字的平方值,然后根据可使用空间的大小,选取平方数的中间几位为哈希表的地址。

比如:关键字:1234,关键字的平方:1522756,哈希函数值:227

 

(四)折叠法

按哈希表的地址位数将关键字分成位数相等的几部分(最后一部分较短),然后将这几部分相加,舍弃最高位后的结果就是该关键字的哈希地址。

 

(五)除留余数法

取关键字被某个不大于哈希表长度的数求余,得到的结果作为哈希地址

H(Key) = key % p,其中p小于哈希表长度

 

(六)随机数法

采用一个伪随机函数作为哈希函数,即 H(key) = random(key)
 

 

 

处理冲突

 

通过哈希函数去计算哈希值,难免会有冲突的时候,解决冲突的方法有如下几种:

  • 开放定址法: 依靠数组中的空位解决碰撞冲突
    • 线性探测法:直接检测散列表的下一个位置(即索引值加1),如果仍冲突,继续。
    • 二次探测法:即H + 1 2, H + 22, H + 32.。。
    • 伪随机探测
  • 再哈希法:使用多个哈希函数,第一个冲突时,使用第二个哈希函数,直到不冲突为止
  • 链地址法:将所有哈希地址相同的关键字,都链接在同一个链表中。
  • 公共溢出法:建立一个公共溢出区,所有关键字和基本表中关键字为同义词的记录,不管它们由哈希函数得到的哈希地址是什么,一旦发生冲突,都填入溢出表。

 

 

 

性能分析

 

哈希法中影响关键字比较次数的因素有三个:哈希函数,处理冲突的方法,哈希表的装填因子。

装填因子 a 的定义如下:         a  = 哈希表中元素的个数 / 哈希表的长度            

a 可描述哈希表的装满程度。a 越小,发生冲突的可能性越小; a 越大 ,发生冲突的可能性越大。

 

 

 

案例

 

 

代码

 
开放定址哈希表存储结构:
 

空的哈希表 H:
 
 
 
 
typedef int Status; /* Status是函数的类型,其值是函数结果状态代码,如OK等 */
typedef int Boolean; /* Boolean是布尔类型,其值是TRUE或FALSE */

#include<malloc.h> /* malloc()等 */
#include<stdio.h> /* EOF(=^Z或F6),NULL */
#include<process.h> /* exit() */

/* 函数结果状态代码 */
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define INFEASIBLE -1
#define OVERFLOW -2 


 /* 对两个数值型关键字的比较约定为如下的宏定义 */
#define EQ(a,b) ((a)==(b))
#define LT(a,b) ((a)<(b))
#define LQ(a,b) ((a)<=(b))


#define NULLKEY 0 /* 0为无记录标志 */
#define N 10 /* 数据元素个数 */
typedef int KeyType; /* 设关键字域为整型 */
typedef struct
{
	KeyType key;
	int ord;
}ElemType; /* 数据元素类型 */



/* -----------------------------  开放定址哈希表的存储结构    ---------------------------*/

int hashsize[] = { 11,19,29,37 }; /* 哈希表容量递增表,一个合适的素数序列 */
int m = 0; /* 哈希表表长,全局变量 */
typedef struct
{
	ElemType *elem; /* 数据元素存储基址,动态分配数组 */
	int count; /* 当前数据元素个数 */
	int sizeindex; /* hashsize[sizeindex]为当前容量 */
}HashTable;

#define SUCCESS 1
#define UNSUCCESS 0
#define DUPLICATE -1

/* ---------------------------------------------------------------------------------------------*/


/* --------------------------------     哈希函数的基本操作    -------------------------------*/


Status InitHashTable(HashTable *H)
{ /* 操作结果: 构造一个空的哈希表 */
	int i;
	(*H).count = 0; /* 当前元素个数为0 */
	(*H).sizeindex = 0; /* 初始存储容量为hashsize[0] */
	m = hashsize[0];
	(*H).elem = (ElemType*)malloc(m * sizeof(ElemType));
	if (!(*H).elem)
		exit(OVERFLOW); /* 存储分配失败 */
	for (i = 0; i < m; i++)
		(*H).elem[i].key = NULLKEY; /* 未填记录的标志 */
	return OK;
}

void DestroyHashTable(HashTable *H)
{ /* 初始条件: 哈希表H存在。操作结果: 销毁哈希表H */
	free((*H).elem);
	(*H).elem = NULL;
	(*H).count = 0;
	(*H).sizeindex = 0;
}

unsigned Hash(KeyType K)
{ /* 一个简单的哈希函数(m为表长,全局变量) */
	return K % m;
}

void collision(int *p, int d) /* 线性探测再散列 */
{ /* 开放定址法处理冲突 */
	*p = (*p + d) % m;
}

Status SearchHash(HashTable H, KeyType K, int *p, int *c)
{ /* 在开放定址哈希表H中查找关键码为K的元素,若查找成功,以p指示待查数据 */
  /* 元素在表中位置,并返回SUCCESS;否则,以p指示插入位置,并返回UNSUCCESS */
  /* c用以计冲突次数,其初值置零,供建表插入时参考。算法9.17 */
	*p = Hash(K); /* 求得哈希地址 */
	while (H.elem[*p].key != NULLKEY && !EQ(K, H.elem[*p].key))
	{ /* 该位置中填有记录.并且关键字不相等 */
		(*c)++;
		if (*c < m) //注意:这里很重要,课本中是没有这个比较限制的,则程序可能在 while 循环中出不去。
			collision(p, *c); /* 求得下一探查地址p */
		else
			break;
	}
	if EQ(K, H.elem[*p].key)
		return SUCCESS; /* 查找成功,p返回待查数据元素位置 */
	else
		return UNSUCCESS; /* 查找不成功(H.elem[p].key==NULLKEY),p返回的是插入位置 */
}


Status InsertHash(HashTable *, ElemType); /* 对函数的声明 */
void RecreateHashTable(HashTable *H) /* 重建哈希表 */
{ /* 重建哈希表 */
	int i, count = (*H).count;
	ElemType *p, *elem = (ElemType*)malloc(count * sizeof(ElemType));
	p = elem;
	printf("重建哈希表\n");
	for (i = 0; i < m; i++) /* 保存原有的数据到elem中 */
		if (((*H).elem + i)->key != NULLKEY) /* 该单元有数据 */
			*p++ = *((*H).elem + i);
	(*H).count = 0;
	(*H).sizeindex++; /* 增大存储容量 */
	m = hashsize[(*H).sizeindex];
	p = (ElemType*)realloc((*H).elem, m * sizeof(ElemType));
	if (!p)
		exit(OVERFLOW); /* 存储分配失败 */
	(*H).elem = p;
	for (i = 0; i < m; i++)
		(*H).elem[i].key = NULLKEY; /* 未填记录的标志(初始化) */
	for (p = elem; p < elem + count; p++) /* 将原有的数据按照新的表长插入到重建的哈希表中 */
		InsertHash(H, *p);
}

Status InsertHash(HashTable *H, ElemType e)
{ /* 查找不成功时插入数据元素e到开放定址哈希表H中,并返回OK; */
  /* 若冲突次数过大,则重建哈希表。算法9.18 */
	int c, p;
	c = 0;
	if (SearchHash(*H, e.key, &p, &c)) /* 表中已有与e有相同关键字的元素 */
		return DUPLICATE;
	else if (c < hashsize[(*H).sizeindex] / 2) /* 冲突次数c未达到上限,(c的阀值可调) */
	{ /* 插入e */
		(*H).elem[p] = e;
		++(*H).count;
		return OK;
	}
	else
		RecreateHashTable(H); /* 重建哈希表 */
	return ERROR;
}


void TraverseHash(HashTable H, void(*Vi)(int, ElemType))
{ /* 按哈希地址的顺序遍历哈希表 */
	int i;
	printf("哈希地址0~%d\n", m - 1);
	for (i = 0; i < m; i++)
		if (H.elem[i].key != NULLKEY) /* 有数据 */
			Vi(i, H.elem[i]);
}

Status Find(HashTable H, KeyType K, int *p)
{ /* 在开放定址哈希表H中查找关键码为K的元素,若查找成功,以p指示待查数据 */
  /* 元素在表中位置,并返回SUCCESS;否则,返回UNSUCCESS */
	int c = 0;
	*p = Hash(K); /* 求得哈希地址 */
	while (H.elem[*p].key != NULLKEY && !EQ(K, H.elem[*p].key))
	{ /* 该位置中填有记录.并且关键字不相等 */
		c++;
		if (c < m)
			collision(p, c); /* 求得下一探查地址p */
		else
			return UNSUCCESS; /* 查找不成功(H.elem[p].key==NULLKEY) */
	}
	if EQ(K, H.elem[*p].key)
		return SUCCESS; /* 查找成功,p返回待查数据元素位置 */
	else
		return UNSUCCESS; /* 查找不成功(H.elem[p].key==NULLKEY) */
}


/* --------------------------------------------------------------------------------------------------*/


void print(int p, ElemType r)
{
	printf("address=%d (%d,%d)\n", p, r.key, r.ord);
}

void main()
{
	ElemType r[N] = { {17,1},{60,2},{29,3},{38,4},{1,5},{2,6},{3,7},{4,8},{60,9},{13,10} };
	HashTable h;
	int i, p;
	Status j;
	KeyType k;
	InitHashTable(&h);
	for (i = 0; i < N - 1; i++)
	{ /* 插入前N-1个记录 */
		j = InsertHash(&h, r[i]);
		if (j == DUPLICATE)
			printf("表中已有关键字为%d的记录,无法再插入记录(%d,%d)\n", r[i].key, r[i].key, r[i].ord);
	}
	printf("按哈希地址的顺序遍历哈希表:\n");
	TraverseHash(h, print);
	printf("请输入待查找记录的关键字: ");
	scanf("%d", &k);
	j = Find(h, k, &p);
	if (j == SUCCESS)
		print(p, h.elem[p]);
	else
		printf("没找到\n");
	j = InsertHash(&h, r[i]); /* 插入第N个记录 */
	if (j == ERROR) /* 重建哈希表 */
		j = InsertHash(&h, r[i]); /* 重建哈希表后重新插入第N个记录 */
	printf("按哈希地址的顺序遍历重建后的哈希表:\n");
	TraverseHash(h, print);
	printf("请输入待查找记录的关键字: ");
	scanf("%d", &k);
	j = Find(h, k, &p);
	if (j == SUCCESS)
		print(p, h.elem[p]);
	else
		printf("没找到\n");
	DestroyHashTable(&h);
}

运行结果:

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值