散列表

   1.直接寻址法:当关键字的全域U很小时,直接寻址法是一种简单而有效的技术。设集合U={0,1,2...m-1},我们用一个数组或叫直接寻址表记为T[0...m-1],数组每一个位置对应U中的一个关键字。T[k]存放关键字k,若集合中没有k关键字则T[k]=NIL;

     直接寻址法缺点非常明显:如果全域U非常大,接寻址表T可能就存不下。反言之,如果T中实际存储关键字太少,也可能造成大量空间的浪费。

   2.散列法:在直接寻址法,关键字放在T[k]中,而在散列方式下关键字k放在T[h(k)]中:即利用散列函数(hash function)h,通过关键字k计算出下标位置。函数h将关键字的全域U映射到散列表(hash table)T[0...m-1]上;一般m比|U|小得多,于是会出现不同关键字映射到了同一位置,这种情况称为冲突。

   3.散列函数构造规则:一个好的散列函数应该满足以下条件:

           1)计算简单,以便提高转换速度;

           2)关键字对应的地址空间分布均匀,以尽量减少冲突。即对关键字集合中的任何一个关键字,经散列映射到集合中任何一地址的概率基本相等的。尽量随机。

   4.散列函数构造方法

           1)数字关键字的散列函数构造:

                    a.直接定址法,取关键字为某个线性函数值为散列地址。哈希函数:h(key)=a*key+b;相当于直接寻址法,对大量的数据不适用。

                    b.除留余数法:通过key除p的余数将关键字映射到表中相应的位置:哈希函数:h(key)=key%p;设散列表的长度为tableSiza则选取的p最好是p<=tableSize最大的一个素数。用素数求余分布在整个地址空间上的可能性比较大。

                    c.数字分析法:如果关键字位数较多,在特定情况系有的位数容易重复,而有的位数比较随机,比如11位的手机号,前三位和中间四位容易重复,而末尾的四位则比较随机,于是可以选择末尾4为作为散列地址.。如果11位手机号码是字符串,在C和C++中有将字符串转换为整数函数:h=atoi(key+7),key+7表示指针向后移动7位,即保留四位。         

            2)字符串关键字的散列函数构造:基本思想是将关键字转换为自然数,然后再根据自然数的方法来构造。如一个字符串可以表示为适当基数的整数,在ASCII中,若以128为基数,则标示符pt可以表示为112*128+116=14452,以10为基数表示为112*10+116=1236。不管什么方法,我们的原则是将关键字转换为自然数。

    5.解决冲突的办法:链接法和开放寻址法。

        1)链接法解决冲突:链接法就是把散列到同一个位置的元素都放在一个链表中。链表法虽然有效的解决了冲突,但是却需要额外的空间。采用链式法在最坏情况下性能很差:当所有的n个关键字都散列到了同一个位置,此时,最坏查找时间是O(n)。装填因子:给定一个可以存放n个元素,具有m个散列位置的散列表T,定义装填因子a=n/m即为平均每个位置储存的元素个数。如果设散列函数的计算时间是O(1),那么这种链接法平均查找时间为O(1+a),平均删除时间也是O(1+a),因为先要找到才可以删除。由于每次插入元素都在表头,所以插入时间为O(1);

         2)开放寻址法解决冲突:开放寻址法所有的元素都放在散列表里面。当查找某个元素时,要系统的检查所有表项,直到找到所需的元素。由于开放寻址法没有元素放在表外,因此在开放寻址中,散列表可能被填满,固有装填因子a<=1.为了使用开放寻址来插入一个元素,需要连续的检查散列表,或者叫探查,直到找到一个空位置来存放此元素。为了加入探测,我们把散列函数扩充为二元:h(k,i)=(h1(k)+d(i)) mod m   (0<=i<m),m是散列表长度。一般来说d(i)的选取不同可以得到解决冲突的不同方案。有三种常用的开放寻址法的探测技术:线性探查,二次探查,双重探查。

       a.线性探查技术;线性探查散列函数为h(k,i)=(h1(k)+i) mod m,i=0,1,2...m-1.给定关键值k,首先查看位置T[h1(k,0)],i依次增大,直到探测到空位置或者i==m-1为止。线性探测方法容易实现,但是它存在一个问题:“一次群集”。随着被占用的位置不断增多,平均查找的时间也不段增多,连续被占用的位置越来越长,因而平均查找时间也会增大。

      b.二次探查:二次探查公式h(k,i)=(h1(k)+c*i^2+d*i) mod m.i=0...m-1.该偏移量以二次的方式,这种探测效果要比一次探测好的多。如果存在h(k1,0)==h(k2,0),那么关键值k1和k2探测的序列相同,于是也可能导致一种轻度的聚集“二次聚集”。一般的用i^2来探查叫做平方探查法。

      c.双重探查法:双重散列是开放寻址的最好方法之一,双重散列函数:

                      h(k,i)=(h1(k)+i*h2(k)) mod m. i=0,1,2...m-1.这次的探测不像线性探测或者二次探测,这里探测序列以两种不同的方式依赖于关键字k,效果为最佳。关于h2和m选取的关系h2(k)必须与表的大小m互质,这样才可以查找到整个表。方法一:取m=2^p,并设计一个总产生奇数的h2(k)。方法二:选取m为某一素数,并设计一个总是小于m的正整数函数h2(k),这样h2(k)就与m互素了。双重散列是一种”比较理想的“均匀散列。

  下面代码假设关键字是自然数,采用的是双重探查技术。将表的大小设置为一个素数。

/*hash.h*/
#pragma once
#include<iostream>
#include<iomanip>
using namespace std;
enum EntryType{Empty,Delete,Legitimaxte};
class HashEntry{
public:
	int element;
	enum EntryType Info;
};
class HashTable{
private:
	int  cnum_=0;  //记录冲突次数
	HashEntry *theCells;
	int tableSize_;
	int nextNum_;
public:
	HashTable(int size);
	int getSize(){ return tableSize_; }
	int getElement(int i);
	int getCnum(){ return cnum_; }
	void showHashTable();
	int hashFunction(int key,int i);
	void distroyTable();
	int findElement(int key);
	bool insertElement(int key);
	bool deleteElement(int key);
};

/*hash.cpp*/
#include"hash.h"
HashTable::HashTable(int size){
	int i;
	bool flag ;
	theCells = new HashEntry[size];   //申请表空间
	for (i = 0; i<size; i++)
		theCells[i].Info = Empty;     //初始化
	if (size % 2 == 0)                //如果size是偶数,将它变为奇数
		size--;
	for (tableSize_ =size; tableSize_> 2; tableSize_--){
		flag =true;
		for (i = 3; i*i <=tableSize_; i+=2){
			if (tableSize_%i == 0){
				flag = false;
				break;
			}
		}
		if (flag)
			break;
	}
		/*以上找到素数因子用来作为表长*/
	nextNum_ = tableSize_ -2;    //nextNum与tableSize_互质
}
int HashTable::getElement(int i){
	if (theCells[i].Info == Legitimaxte)
		return theCells[i].element;
	return -1;
}
void HashTable::showHashTable(){
	int i;
	for (i = 0; i < tableSize_; i++){
		switch (theCells[i].Info){
		case 0:cout<<setw(11)<<setfill(' ')<< "Empty" << "   "; break;
		case 1:cout << setw(11)<<setfill(' ')<< "Delete" << "   "; break;
		case 2:cout << setw(11)<<setfill(' ')<< "Legitimaxte" << "   "; break;
		}
		if (theCells[i].Info == Legitimaxte)
			cout << theCells[i].element << endl;
		else
			cout << "NULL" << endl;
	}
}
int HashTable::hashFunction(int key,int i){   //双重散列
	return (key%tableSize_+i*(key%nextNum_+1))%tableSize_;
}
void HashTable::distroyTable(){
	delete[]theCells;
	tableSize_ = 0;
}
bool HashTable::insertElement(int key){
	int i=0;
	while (i < tableSize_&&theCells[hashFunction(key, i)].Info != Empty)
      i++,cnum_++;
	if (i == tableSize_)
		return false;      //插入失败
	theCells[hashFunction(key, i)].element = key;
	theCells[hashFunction(key, i)].Info = Legitimaxte;
	return true;
}
int HashTable::findElement(int key){
	int i = 0,pos;
	while (i < tableSize_&&theCells[hashFunction(key, i)].Info != Empty&&theCells[hashFunction(key, i)].element!= key)
		i++;
	if (i == tableSize_)
		return -1;
	pos = hashFunction(key, i);  //找到啦下标  
	return pos; 
}
bool HashTable::deleteElement(int key){
	int pos = findElement(key);  //找到元素的位置
	if (pos == -1)
		return false;
	theCells[pos].Info = Delete;
	return true;
}
/*源.cpp*/
#include"hash.h"
#include<iostream>
using namespace std;
int main(){
	/*测试*/
	int i;
	int a[10] = { 47, 7, 29, 11, 9, 84, 54, 20, 32, 46 };
	HashTable H(20);
	cout << "表大小: " << H.getSize() << endl;
	for (i = 0; i < 10; i++)
		H.insertElement(a[i]);
	H.showHashTable();
	cout << "总的冲突次数: " << H.getCnum()<< endl;
	cout << "----------------------------------------------------------------------" << endl;
	cout << "插入58后表为:" << endl;
	H.insertElement(58);
	H.showHashTable();
	cout << "总的冲突次数: " << H.getCnum() << endl;
	cout << "----------------------------------------------------------------------" << endl;
	cout << "删除20后表为:" << endl;
	H.deleteElement(20);
	H.showHashTable();
	cout << "----------------------------------------------------------------------" << endl;
	return 0;
}



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值