ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

散列查找-处理冲突的办法

2021-06-14 11:00:23  阅读:218  来源: 互联网

标签:Key TableSize int NewPos 查找 冲突 key 散列


以下内容来自陈越姥姥《数据结构(第2版)》,笔记仅供自己参考。

开放定址法

所谓开放定址法,就是一旦产生冲突,即该地址已经存放了其他元素时,就去寻找另一个空的散列地址。在没有装满的散列表中,空的散列地址总能找到。

懒惰删除:增加1个“删除标记”,而并不是真正删除它。因为查找操作时,找到空地址代表查找失败,但事实上,也许是散列到这里的数据对象已经绕过这里存在了别处。

一般来说,发生了第i次冲突,我们试探的下一个地址将增加di。即h1(key)=(h(key) + di) mod TableSize。根据di的选取方式不同,可得到以下4种解决冲突方法。

  1. 线性探测法

上述公式中的di=i,就成为线性探测法,即线性探测法以增量序列1,2,……,(TableSize-1)循环试探下一个存储地址。

做插入操作时,需要找到1个空位置,或知道散列表已满为止;做查找操作时,探测1个比较一次关键词,直到找到特定的数据对象或探测到1个空位置表示查找失败为止。

一次聚集(Primary Clustering):很多元素在相邻的散列地址上“堆积”起来的现象,大大降低查找效率。

  1. 平方探测法

平方探测法是以增量序列12,-12,22,-22,……,q2,-q2循环试探下一个存储地址,q<=[TableSize/2]。如果散列表的长度TableSize是某个4k+3形式的素数时,平方探测法就能探查到整个散列表空间。

二次聚集(Secendary Clustering):散列到同一地址的数据对象们将探测相同的备选单元。

开放定址法的类型声明:

#define MAXTABLESIZE 100000//允许开辟的最大散列表长度
typedef int ElementType;//关键词类型用int
typedef int Index;//散列地址类型用int
typedef Index Position;//数据所在位置用int
typedef enum{Legitimate,Empty,Deleted}EntryType;
//散列单元状态类型,分别对应有合法元素、空单元、有已删除元素

typedef struct HashEntry Cell;//散列表单元的类型
struct HashEntry {
	ElementType Data;//存放元素
	EntryType Info;//单元状态
};

typedef struct TblNode *HashTable;//散列表类型
struct TblNode {//散列表结点定义
	int TableSize;//表的最大长度
	Cell *Cells;//存放散列单元数据的数组
};

开放定址法的初始化函数:

int NextPrime(int N)
{//返回大于N且不超过MAXTABLESIZE的最小素数
	int i;
	int p=(N%2)?N+2:N+1;//从大于N的下一个奇数开始
	while(p<=MAXTABLESIZE) {
		for(i=(int)sqrt(p);i>2;i--) 
			if(!(p%i)) break;//p不是素数
		if(i==2) break;//for循环正常结束,说明p是素数
		else p+=2;
	}
	return p;
}

HashTable CreateTable(int TableSize)
{
	HashTable H;
	int i;
	H=(HashTable)malloc(sizeof(struct TblNode));
	H->TableSize=NextPrime(TableSize);
	//用户传入的Table Size不一定是素数,保证散列表最大长度是素数
	H->Cells=(Cell*)malloc(H->TableSize*sizeof(Cell));
	for(int i=0;i<H->TableSize;i++)
		H->Cells[i].Info=Empty;
	return H;
}

平方探测法的查找函数:

Position Find(HashTable H,ElementType Key)
{
	Position CurrentPos,NewPos;
	int CNum=0;//CNum记录冲突次数
	NewPos=CurrentPos=Hash(Key,H->TableSize);//初始散列位置
	//当该位置的单元非空,并且不是要找的元素时,发生冲突
	//假设这里的关键词key是int型
	while(H->Cells[NewPos].Info!=Empty&&H->Cells[NewPos].Data!=Key) {
		if(++CNum%2) {//如果是奇数次冲突
			NewPos=CurrentPos+(CNum+1)*(CNum+1)/4;
			if(NewPos >= H->TableSize)//调整为合法地址
				NewPos=NewPos % H->TableSize;
		}
		else {//如果是偶数次冲突
		NewPos=CurrentPos-CNum*CNum/4;
		while(NewPos < 0)//调整为合法地址
			NewPos+=H->TableSize;
			}
	}
	return NewPos;
	//此时的NewPos要么是key的位置,要么是1个空单元的位置
}

平方探测法的插入函数:

bool Insert(HashTable H,ElementType Key) {
	Position Pos=Find(H,key);

	if(H->Cells[Pos].Info != Legitimate) {
		H->Cells[Pos].Info=Legitimate;
		H->Cells[Pos],Data=key;
		return true;
	}
	else {
		printf("键值已存在");
		return false;
	}
}
  1. 双散列探测法
hi(key) = (h(key)+i*h2(key)) mod TableSize;

第二个散列函数h2(key)如果选得不好,结果将会是灾难性的。要求对于任意的key,h2(key)都不能为0。此外,探测增量序列应该保证,所有的散列存储单元都应该被探测到。h2(key) = p-(key mod p)有良好的效果,p是小于TableSize的素数。

  1. 再散列法

开放定址法的装填因子会严重影响查找效率。当装填因子过大时,可以加倍扩大散列表,这样装填因子可以减小一半,就是再散列(Rehashing)。

分离链接法

分离链接法(Separate Chaining)的做法是,将所有关键词为同义词的数据对象通过结点链接存储在同一个单链表中。

分离链接法的结构声明:

#define KEYLENGTH 15
typedef char ElementType[KEYLENGTH+1];
typedef int Index;

//单链表的定义
typedef struct LNode *PtrToLNode;
struct LNode {
	ElementType Data;
	PtrToLNode Next;
};
typedef PtrToLNode Position;
typedef PtrToLNode List;

typedef struct TblNode *HashTable;//散列表类型
struct TblNode {//散列表结点定义
	int TableSize;//表的最大长度
	List Heads;//指向链表头结点的数组
};

分离链接法的初始化函数:

HashTable CreateTable(int TableSize)
{
	HashTable H;
	int i;

	H=(HashTable)malloc(sizeof(struct TblNode));
	H->TableSize=NextPrime(TableSize);
	H->Heads=(List)malloc(sizeof(struct LNode));
	for(i=0;i<H->TableSize;i++) {
		H->Heads[i].Data[0]='\0';
		H->Heads[i].Next=NULL;
	}
	return H;
}

分离链接法的查找函数:

Position Find(HashTable H,ElementType Key) 
{//假设这里的关键字Key是字符型
	Position P;
	Index Pos;
	
	Pos=Hash(Key,H->TableSize);//初始散列位置
	P=H->Heads[Pos].Next;//从该链表的第一个结点开始
	while(P&&strcmp(P->Data,Key)) //当未到表尾,并且Key未找到时
		P=P->Next;

	return P;
}

分离链接法的插入函数:

bool Insert(HashTable H,ElementType Key)
{
	
	P=Find(H,Key);
	if(!P) {
		NewCell=(Position)malloc(sizeof(struct LNode));
		strcpy(NewCell->Data,Key);
		Pos=Hash(Key,H->TableSize);
		//将NewCell插入为H->Heads[Pos]链表的第一个结点
		NewCell->Next=H->Heads[Pos].Next;
		H->Heads[Pos].Next=NewCell;
		return true;
	}
	else {
	printf("键值已存在");
	return false;
	}
}

分离链接法的释放散列表函数:

void DestroyTable(HashTable H)
{
	int i;
	Position P,Tmp;

	//释放每个链表的结点
	for(i=0;i<H->TableSize;i++) {
		P=H->Heads[i].Next;
		while(P) {
			Tmp=P->Next;
			free(P);
			P=Tmp;
		}
	}
	free(H->Heads);//释放头结点数组
	free(H);//释放散列表结点
 }

标签:Key,TableSize,int,NewPos,查找,冲突,key,散列
来源: https://blog.csdn.net/weixin_55746099/article/details/117897516

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有