其他分享
首页 > 其他分享> > 散列查找-处理冲突的办法

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

作者:互联网

以下内容来自陈越姥姥《数据结构(第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