查找--- 哈希表( 散列表)

本文介绍了哈希表的基本概念,包括桶数组和散列函数。详细阐述了不同类型的散列函数,如除法散列法、MAD方法、余数法、折叠法、基数转换法和数据重排法。接着讨论了冲突处理,如开放定址法(线性探测、二次探测、再哈希)和链地址法(拉链法),并比较了它们的优缺点。文章还提到了装填因子的概念及其在冲突处理中的作用。最后,提供了两个示例程序展示了除法散列构造哈希函数以及使用开放定址和链地址法处理冲突的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

哈希表优点:把数据的存储和查找消耗的时间大大降低,几乎可以看作是常数时间,而代价是消耗比较多的内存

散列表由两部分组成:桶数组,散列函数

1.桶数组

散列表的桶数组是一个大小为N的数组A,其中A的每个单元可看作一只“桶”,整数N表示数组的容量,如果关键字为整数,且

均匀地分散在范围[0,N-1]中,这个桶数组就是所需要的数组。关键字为K的元素e被简单地插入到桶A[k]中。

2.散列函数

散列函数将字典中的每个关键字k映射到区间[0,N-1]内的一个整数,其中N是这个散列表的桶数组的容量。这个方法的主要思想是利用散列函数值h(k)作为桶数组A的下标,而不是使用关键字k,即将数据项(k,e)存储在桶A[h(k)]中。  对于散列函数性能的评估有两个行为组成:一个是将关键字映射到一个整数上,称为散列编码;一个是将散列编码映射到桶数组下标内的一个整数上,称为压缩映射

    关键字——>散列编码——>压缩映射到桶数组区间

3.压缩映射

1)除法散列法

       h(k)=|k| mod N;

   如果N为素数,则该方法有助于"扩散"散列值的分布;如果N不是素数,关键字分布中的模式在散列编码分布中出现重复的可能性更大,因而会有冲突。

2)MAD方法(乘加与除方法)

   h(k)=|ak+b| mod N;其中N是素数,a,b是在确定压缩函数时随机选取的非负整数,满足a mod N 不等于0.

 

3)余数法:先估计整个哈希表中的表项目数目大小。然后用这个估计值作为除数去除每个原始值,得到商和余数。用余数作为哈希值。因为这种方法产生冲突的可能性相当大,因此任何搜索算法都应该能够判断冲突是否发生并提出取代算法。 

4)折叠法:这种方法是针对原始值为数字时使用,将原始值分为若干部分,然后将各部分叠加,得到的最后四个数字(或者取其他位数的数字都可以)来作为哈希值。 

5)基数转换法:当原始值是数字时,可以将原始值的数制基数转为一个不同的数字。例如,可以将十进制的原始值转为十六进制的哈希值。为了使哈希值的长度相同,可以省略高位数字。 

6)数据重排法:这种方法只是简单的将原始值中的数据打乱排序。比如可以将第三位到第六位的数字逆序排列,然后利用重排后的数字作为哈希值。

4.冲突处理模式

装填因子:一个良好的散列函数将字典中的n个数据存储在容量为N的桶数组中,并期望每个桶的大小为n/N,则为散列表的装填因子。数据个数与桶数组容量的比,通常装填因子小于1.

1)开放定址法

      当冲突发生时,使用某种探查(亦称探测)技术在散列表中形成一个探查(测)序列。沿此序列逐个单元地查找,直到找到给定的关键字,或者碰到一个开放的地址(即该地址单元为空)为止(若要插入,在探查到开放的地址,则可将待插入的新结点存人该地址单元)。查找时探查到开放的地址则表明表中无待查的关键字,即查找失败。

注意:

①用开放定址法建立散列表时,建表前须将表中所有单元(更严格地说,是指单元中存储的关键字)置空。

②空单元的表示与具体的应用相关

    Hi=(H(key)+di) MOD m, i=1,2,3,……k(k<=m-1);

  H(key)为哈希函数;m为哈希表的长度;di为增量序列;增量序列有3种取法:

第一种:线性探测再散列       di=1,2,3,4……m-1;

       使用线性探测散列会产生聚焦或堆积(散列地址不同的结点争夺同一个后继散列地址的现像叫聚焦或堆积)为了减少堆积的发生,不能像线性探查法那样探查一个顺序的地址序列(相当于顺序查找),而应使探查序列跳跃式地散列在整个散列表中

第二种:二次探测再散列       di=1^2,-(1^2),2^2,-(2^2),……k^2,-(k^2)

第三种:再哈希法     

   Hash(key),ReHash(key)是两个哈希函数,m为哈希表长度。    先用第一个函数对关键码计算哈希地址,一旦地址冲突,再用第二个函数ReHash(key)确定移动的步长因子,最后,通过步长因子序列由探测函数寻找空的哈希地址。

2)链地址法(拉链法)

将所有关键字为同义词的记录存储在同一线性链表中,若选定的散列表长度为m,则可将散列表定义为一个由m个头指针组成的指针数组T[0……m-1]。凡是散列地址为i的结点,均可以插入到以T[i]为头指针的单链表中。T中各分量的初值均为空指针。装填因子可以大于1,通常取小于1.

链地址优点:

(1)处理冲突简单,且无堆积现像,平均查找长度短。

(2)由于链地址中链表上的结点空间是动态申请的,因而它更适合于造表前无法确定表长的情况

(3)开放定址法为减少冲突,要求装填因子较小,因而在结点规模较大时会浪费很多空间;而链地址法装填因子可取大于1,且结点数较大时,链地址中增加的指针域可不计,因而节省空间。

(4)链地址法构造的散列表中,删除结点操作易于实现

而对开放地址法构造的散列表,删除结点不能简单地将被删结点的空间置为空,否则将截断在它之后填人散列表的同义词结点的查找路径。这是因为各种开放地址法中,空地址单元(即开放地址)都是查找失败的条件。因此在用开放地址法处理冲突的散列表上执行删除操作,只能在被删结点上做删除标记,而不能真正删除结点。

链地址缺点:

指针需要额外的空间,故当结点规模小时,开放定地址法较为节省空间;而若将节省的指针空间用来扩大散列表的规模,可使装填因子变小,这又减少了开放定地址的冲突,从而提高了平均查找速度。

     程序1:除法散列构造哈希函数;

                  开放定址,线性探测散列处理冲突。

//除法散列构造哈希函数;开放定址,线性探测散列处理冲突
#include <iostream>

using namespace std;

//哈希表结构体
typedef struct HashTable  
{
    int *elem;     //存储元素的数组
    int count;     //哈希表中元素个数
	int sizeindex; //哈希表容量
}HashTable;

///函数声明///
int HashFunc(int key);
int HashCollision(int k,int d=1);
bool SearchHash(HashTable hash,int key,int &p);
int InsertHash(HashTable &hash,int e);
void PrintHash(HashTable hash);
bool DeleteHash(HashTable &hash,int e);
/

int main()
{  
	int b_delete=false;
	bool b_search=false;
	int search;
	int hashsize;
	int input;  // 接收输入
    HashTable hash;
	int m=0;     //用来判断哈希表是否已经满了
	cout<<"请输入哈希表的容量: ";
	cin>>hashsize;
	int* p=new int[hashsize];
	hash.elem =p;
	hash.count =0;
	hash.sizeindex =hashsize;
	int i=0;
    while(hash.elem[i]!=0&&i<hash.sizeindex )
	{
		hash.elem[i]=0;
		++i;
	}

	while(1)
	{   system("cls"); 
	    system("color 2e");     //设置屏幕前景和背景颜色
     cout<<"             ******************************************"<<endl;
	 cout<<" ╱◥██◣  *1.插入\t2.查找\t3.显示\t4.删除\t5.退出*"<<endl;
     cout<<"|田|田田│ ******************************************"<<endl;
     cout<<"╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬<哈希表vipper.zhang2011.7.13>"<<endl;
     cout<<"请输入您所需的操作功能:\t";
     cin>>input;
	 cout<<endl;
		switch(input)
		{
		case 1:
			cout<<"请输入要插入的关键字,0结束输入:";
			cin>>input;
			cout<<endl;
			while(input!=0)
			{
			 m=InsertHash(hash,input);
			 if(m==2)
			      break;
			 cout<<"请输入要插入的关键字,0结束输入:";
			 cin>>input;cout<<endl;
			}
		    PrintHash(hash);
            system("pause");
			break;
		case 2:
			cout<<"输入你想查找的关键字:";
			cin>>input;cout<<endl;
            b_search=SearchHash(hash,input,search);
			if(b_search)
				cout<<"关键字"<<input<<"存在"<<endl;
			else
				cout<<"关键字"<<input<<"不存在"<<endl;
			 system("pause");
             break;
		case 3:
			PrintHash(hash);
			system("pause");
			break;
		case 4:
            cout<<"输入你要删除的关键字:";
			cin>>input;cout<<endl;
			b_delete=DeleteHash(hash,input);
            if(b_delete)
				cout<<"删除成功!"<<endl;

			system("pause");
			break;
		case 5:
			cout<<"再见。"<<endl;
			break;
		default:
			cout<<"输入错误,请重新输入:"<<endl;
			break;
		}
		if(input==5)
			break;
	}
	system("pause");
	return 0;
}




///
// 哈希函数: H(key)=key%mod    mod=13
// Input:key - 关键字
// Output: 返回关键字所在哈希表的地址
///
int HashFunc(int key)
{
   return (abs(key)%13); 
}

///
// 哈希冲突函数 H(key)=(H(key)+d)%mod   mod=13
// Input: k - 由哈希函数求得的冲突值
//        d - 线性探测散列 处理冲突  d=1
// Output:返回新的关键字所在哈希表地址
///
int HashCollision(int k,int d)
{
    return (k+d)%13;
}

///
//在开放定址哈希表中搜索关键字
// Input: hash - 待搜索的哈希表
//        key  - 要搜索的关键字
//        p    - 查找成功时,指示关键字在表中的位置,否则,指示插入位置
// Output: bool- 指示查找是否成功
///
bool SearchHash(HashTable hash,int key,int &p)
{
	p=HashFunc(key);
	while(hash.elem [p]!=0&&hash.elem [p]!=key)
	{
      p=HashCollision(p);
	}

	if(hash.elem[p]==key)  
		return true;
	else
		return false;
}

///
// 在哈希表中插入元素
// Input: hash - 哈希表
//        e    - 要插入的元素
// Output:bool - 返回是否插入成功 
///
int InsertHash(HashTable &hash,int e)
{
	int p=0;
	if(SearchHash(hash,e,p)){
		cout<<"元素 "<<e<<"已经存在了"<<endl;
	    return 0;
	}
	else if(!SearchHash(hash,e,p)&&hash.count <hash.sizeindex) 
	{  
		
		hash.elem [p]=e;
		hash.count ++;
		return 1;
	}
	else
		cout<<"哈希表已经满了"<<endl;
	        return 2;
	return 0;
}

///
//显示哈希表元素

// Input: hash - 哈希表 
///
void PrintHash(HashTable hash)
{
    int i=0;
	do{   
		if(hash.elem[i]!=0)
		   cout<<hash.elem[i]<<"\t";
		++i;
	}while(i<hash.sizeindex);
}

///
// 删除哈希表中元素
// Input: hash - 哈希表
//        e    - 要删除的元素
// Output: 返回删除是否成功
///
bool DeleteHash(HashTable &hash,int e)
{
	if(hash.count==0)
	{
		cout<<"哈希表已经空了"<<endl;
	    return false;
	}
	int p=0;
	if(!SearchHash(hash,e,p)){
	   cout<<"哈希表中没有这个元素"<<endl;
	   return false;
	}
	else{
		hash.elem[p]=0;
	    hash.count--;
		return true;
	}
	
}

程序2:除法散列构造哈希函数;

              链地址法解决冲突。

注:我的程序写的有问题,会有内存泄露的情况,没解决好,有人解决好这个问题请告诉我O(∩_∩)O

//除法散列构造哈希函数;链地址法处理冲突
#include <iostream>

using namespace std;
const int chainLength=13;
//哈希表结构体 
typedef struct HashNode{
    HashNode *next;  //后继指针
    int key;         //结点的值
}HashNode;


///函数声明/// 
int HashFunc(int key);
void HashCollision(HashNode* &pNode,HashNode* newNode);
bool SearchHash(HashNode* *hNode,int key);
void InsertHash(HashNode* *hNode,int e);
void PrintHash(HashNode* *hNode,int len);
bool DeleteHash(HashNode* *hNode,int e);


int main()
{
	int input;
    bool b_search=false;
	bool b_delete=false;

//创建哈希表 
    HashNode* hHash[chainLength];
	for(int i=0;i<chainLength;i++)
	{
		HashNode* p=new HashNode;
		if(!p){
         cout<<"分配内存失误,退出";
		 cout<<endl;
		}
		p->key=0;
		p->next=NULL;
		hHash[i]=p;
	}

	while(1)
	{   system("cls"); 
	    system("color 2e");     //设置屏幕前景和背景颜色
     cout<<"             ******************************************"<<endl;
	 cout<<" ╱◥██◣  *1.插入\t2.查找\t3.显示\t4.删除\t5.退出*"<<endl;
     cout<<"|田|田田│ ******************************************"<<endl;
     cout<<"╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬<哈希表vipper.zhang2011.7.14>"<<endl;
     cout<<"请输入您所需的操作功能:\t";
     cin>>input;
	 cout<<endl;
		switch(input)
		{
		case 1:
			cout<<"请输入你要插入的关键字:";
			cin>>input;cout<<endl;
			while(input!=0)
			{
               InsertHash(hHash,input);
			   cout<<"请输入你要插入的关键字:";
               cin>>input;cout<<endl;
			}
			PrintHash(hHash,chainLength);
            system("pause");
			break;
		case 2:
			 cout<<"请输入你要查找的关键字";
			 cin>>input;cout<<endl;
             b_search=SearchHash(hHash,input);
			 PrintHash(hHash,chainLength);cout<<endl;
			 if(b_search==true)
				 cout<<"你要找的关键字"<<input<<"在哈希表中";
			 else
				 cout<<"哈希表中没有关键字 "<<input;
			 cout<<endl;
			 system("pause");
             break;
		case 3:
			PrintHash(hHash,chainLength);cout<<endl;
			system("pause");
			break;
		case 4:
			cout<<"输入你想删除的关键字:";
			cin>>input;cout<<endl;
            b_delete=DeleteHash(hHash,input);
			if(b_delete)
				cout<<"成功删除关键字 "<<input;
			else
				cout<<"哈希表中没有关键字 "<<input;
			cout<<endl;
			PrintHash(hHash,chainLength);
			system("pause");
			break;
		case 5:
			cout<<"再见。"<<endl;
			break;
		default:
			cout<<"输入错误,请重新输入:"<<endl;
			break;
		}
		if(input==5)
			break;
	}
	system("pause");
	return 0;
}


///  
// 哈希函数: H(key)=key%mod    mod=13  
// Input: key - 关键字 
// Output:返回新的关键字所在哈希表地址  
///
int HashFunc(int key)
{
	return key%13;
}


///  
// 哈希冲突函数  
// Input: pNode - 哈希表中指向特定关键字的指针  
//        newNode -  要插入的新关键字结点  
///  
void HashCollision(HashNode* &pNode,HashNode* newNode)
{

   HashNode* p=pNode;
   while(p->next !=NULL)
	  p=pNode->next ;

   p->next =newNode;
   p=newNode;
   p->next =NULL;
}


///  
//在在链地址法的哈希表中搜索关键字  
// Input:hNode - 待搜索的哈希表  
//        key  - 要搜索的关键字  
// Output: bool- 指示查找是否成功  
/// 
bool SearchHash(HashNode* *hNode ,int key)
{
	
   int m=HashFunc(key);
   HashNode* hhNode=hNode[m];
   if(hhNode->key==key) 
   {	   
	   return true;
   }
   else if(hhNode->key==0&&hhNode->next ==NULL)
   {
	   return false;
   }
   else if(hhNode->key !=0)
   {
	   while(hhNode->next !=NULL)
	   {
		   if(hhNode->key ==key){
			   return true;
		   }
		       hhNode=hhNode->next ;
	   }    
	   return false;
   }
   return false;
	   
}

///  
// 在哈希表中插入元素  
// Input: hNode - 哈希表  
//        e    - 要插入的元素  
///
void InsertHash(HashNode* *hNode,int e)
{ 
    int m=HashFunc(e);	
	bool search=false;
	if(SearchHash(hNode,e))
		return;
	else
	{
	HashNode* p=new HashNode;
	p->key=e;
	p->next =NULL;
	if(hNode[m]->key==0)
	{
		hNode[m]->key=e;
		hNode[m]->next=NULL;
	}
	else
	   HashCollision(hNode[m],p);
	}

}

///  
//显示哈希表元素  
// Input: hNode - 哈希表   
/// 
void PrintHash(HashNode* *hNode,int len)
{
     int i=0;
	 HashNode* p;
	 for(;i< len;i++)
	 {     
		 p=hNode[i];
	
		while(p)
		 {
			 cout<<p->key<<"\t";
			  p=p->next;
		 }
		 cout<<endl;
	 }
}

///  
// 删除哈希表中元素  
// Input: hNode - 哈希表  
//        e     - 要删除的元素  
// Output: 返回删除是否成功  
/// 
bool DeleteHash(HashNode* *hNode,int e)
{  
	int m=HashFunc(e);	
	HashNode* p=hNode[m];
	if(!SearchHash(hNode,e))
		return false;
	else 
	{
		while(p)
		{
			if(p->key==e)
			{
				p->key=-1; //将要删除的元素设置为-1
                return true;
			}
			p=p->next;
		}


	}
      return true;
	
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值