散列表(开放定址法)

散列表(开放定址法)

1.线性探测法

将具体的值输入到哈希函数中,映射出的具体的哈希表中的下标索引。当下标索引冲突时。

离散链表法:将重复了的值用链表的方式挂在对应索引的链表下。

线性探测法:一个位置只放一个值,如果发生冲突,接着寻找下一个没有放值的地方。

举个栗子:

哈希函数:Hash(X)=X mod 10

解决冲突的方法:F(i)=i

当发生冲突时:单元h0(X) , h1(X) ,h2(X)…相继试选

hi(X)=(Hash(X)+F(i)) mod Tablesize,且F(0)=0

将{89,18,49,58,69}插入一个散列表,散列函数为:

Hash(X)=X mod 10

在这里插入图片描述

2.平方探测法

平方探测法来解决冲突,和线性探测法的不同是解决冲突的函数;线性探测法是F(i)=i,是线性函数!

平方探测法是F(i)=i^2,是平方函数!!

将{89,18,49,58,69}插入一个散列表,散列函数为:

Hash(X)=X mod 10

在这里插入图片描述

3.数据结构定义
#define MinTableSize (10)    //哈希表最小表长为10
typedef int ElementType;     //将int改名为ElementType
typedef unsigned int Index;  //将unsigned int改名为Index
typedef Index Position;      //将Index改名为Position 
//枚举类型,有三种状态
enum KindOfEntry {
	Legitimate,Empty,Deleted
}; 

//表中每一个格子的结构
struct HashEntry
{
	ElementType Element;   //存储元素值
	enum KindOfEntry Info; //这个单元格的状态
};

typedef struct HashEntry Cell;   //将struct HashEntry改名为Cell
struct HashTbl{                  //哈希表的结构体
	int TableSize;               //哈希表表长
	Cell* TheCells;              //Cell类型的数组
};

typedef struct HashTbl* HashTable;  //哈希表

在这里插入图片描述

4.相关操作
(1)辅助函数:时间复杂度为O(N^1/2)的素数判断法,不多赘述,太简单!
bool IsPrime(int N);
//找一个和N最接近的素数
int NextPrime(int N)
{
	while(!IsPrime(N))
	{
		N++;	
	}
	return N;	
} 


//判断是不是素数
bool IsPrime(int N)
{
	for(int i=2;i*i<N;i++)
	{
		if(N%i==0)
		{
			return false;//不是素数	
		}	
	}
	return true;//是素数	
} 
(2)简单的哈希函数设定
//哈希函数的简单设置
Index Hash(ElementType Key,int TableSize)
{
	return Key%TableSize;
} 
(3)哈希表的初始化

因为这次使用的是开放定值法,所以哈希表数据结构和离散链表法不同。初始化,首先开辟一块空间,结构中包含哈希表长度和一个指向结构体的指针(相当于开辟一个结构体数组)。然后使用辅助函数来确定一个素数,来充当哈希表的表长,之后开辟结构体数组。最后初始化结构体数组中的状态全为Empty!!!

//哈希表的初始化
HashTable InitTable(int TableSize)
{
	if(TableSize<MinTableSize)
	{
		printf("哈希表长太短了,不行!!");
		return 	NULL;
	}
	HashTable H=(struct HashTbl*)malloc(sizeof(struct HashTbl));
	//想给哈希表整一个素数表长,这样不易产生冲突
	H->TableSize=NextPrime(TableSize);//结构体指针类型 
	//开辟结构体数组 
	H->TheCells=(Cell*)malloc(sizeof(struct HashEntry)*H->TableSize);
	//初始化结构体数组
	for(int i=0;i<H->TableSize;i++)
	{
		H->TheCells[i].Info=Empty;
	}
	return H;	
}
(4)在哈希表中给输入值寻找存放位置

思路:首先用已经给定的哈希函数,得到该值对应的数组索引下标。然后在哈希表中去查找,如果这个值已经被占了而且值与输入值不等,那么就通过冲突函数,重新确定索引下标位置。否则就返回该位置就OK(包含,这个位置没有被占和占了但是值相等!)

//返回可以存放数值的位置
Position Find(ElementType X,HashTable H)
{
	Position p=Hash(X,H->TableSize);
	int CollisionNum=0;
	while(H->TheCells[p].Info!=Empty&&H->TheCells[p].Element!=X)
	{
		p+=2*(++CollisionNum)-1;
		if(p>=H->TableSize)
		{
			p=p-H->TableSize;	
		}	
	}
	return p;	
}
(5)将值插入哈希表中

思路:先通过上面的Find函数,找到空位置,然后将值放到对应位置,并且设定该位置为Legitimate。

//在哈希表中插入值
void Insert(ElementType X,HashTable &H)
{
	Position p=Find(X,H);
	H->TheCells[p].Element=X;
	H->TheCells[p].Info=Legitimate;
}
(6)重构哈希表

当填装因子达到一定值时,就应该重新构造一个哈希表以使用!重构哈希表,首先用变量将原来表中的数据保留下俩,其次在原来表的基础上重新初始化表(设定新的表长),然后将旧表中数据重新插入新表中。最后释放旧表空间就OK!

//重构一个哈希表, 重构的意思是,在原来的基础上进行!!! 
HashTable ReHash(HashTable H)
{
	int OldSize=H->TableSize;
	Cell* OldCell=H->TheCells;
	
	H=InitTable(OldSize*2);
	//将原来的元素搬过来
	for(int i=0;i<OldSize;i++)
	{
		if(OldCell[i].Info==Legitimate)
		{
			Insert(OldCell[i].Element,H);	
		}	
	}
	free(OldCell);
	return H; 
	 				
} 
(7)获取表中具体位置元素,太简单了!
//获取一个位置的值 
ElementType Retrieve(Position p,HashTable H)
{
	return H->TheCells[p].Element; 
}
(8)销毁哈希表

鉴于开放定值法的数据结构,先销毁指向Cell类型的指针,然后销毁指向哈希表的指针!

//销毁这个哈希表
void DestroyTable(HashTable H)
{
	free(H->TheCells);
	free(H);	
} 
5.总结

首先说,

哈希函数:哈希函数是自己设定的,用来将输入的值(将要存放的值)映射出一个哈希表的下标索引。

其实就是一个索引转换器,你输入你的值,它返回这个值应该存在哈希表的哪个位置!!

优点:

相比一个普通的表,如果要查找一个值,那就要从头到尾遍历这个表,时间复杂度是O(表长),比较耗时!!!

用上哈希表,当你将一个值存入哈希表中,之后再进行查找,那么直接通过哈希函数映射对应的索引位置,只需要O(1)即可找到!!效率贼高!!

存在的问题:

函数我们都知道,当我输入两个不同的变量,返回同一个函数值的情况,常常发生。当然,哈希函数也有可能发生这样的情况!!!我们称哈希表的这种情况叫做冲突!

那怎么办呢?

下面是解决冲突的两种方法:

(1)离散链表法:

什么意思?就是当不同的值对应同一个表的下标索引时,我们相当于在这个下标索引这儿,拉了一个链表。这样哈希表的同一个索引位置就可以“挂”好多个值了!!!

这样,当存进哈希表中,再次查找这个值时。先通过哈希函数找哈希表的索引,然后再在索引对应的那个链表中挨个查找即可!!!

(2)开放定址法:

开放定址法,我的理解是:当你输入一个值时,通过哈希函数转化出来的索引下标中,已经有值了。那么就是发生冲突了!你可以自己设定一个函数,来重新确定其值在哈希表中的索引位置。

1)线性探测法:冲突函数是线性的,从冲突位置,挨个去查找空的位置

2)平方探测法:冲突函数是二次方的,从冲突位置,隔1、4、9…个位置去找空位置

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值