对于线性表这种数据结构,当我们查找指定值的时候,只能通过与表中各个元素进行比较的方法。因为线性表每个位置上存放的值与位置没有关系,这个位置上想放什么值就放什么值。
而散列表(又称哈希表)这种数据结构每个位置上放置的数据的值与位置是有关系的,换句话说,你的数值是A,就只能放到
f(A)
这个位置上,你的数值是B,就只能放到
f(B)
上,这样当我想要知道数值A在不在散列表中的时候,我就计算一下f(A),然后看看f(A)这个位置的值是不是A,如果是A就说明我们找到了散列表中的A。这里的
f()
是元素值与存储位置的对应关系。
不难想到,
f(x)
是构建散列表的关键,不同的
f(x)
会把相同的元素值映射到不同的存储位置。我们称
f(x)
为散列函数。
如何设计
f(x)
呢?最好就是我给了你一堆
n
个数字,和
常用的散列函数构造方法有:
- 直接定址法
把要存放的数值(关键字)作为地址或者是该数值的线性函数作为地址。
f(x)=ax+b.(a、b为常数)
一般用于事先知道了关键字集合,且关键字集合不大、连续性好。 - 除留余数法
就是把要存放的数值除以某个数,把得到的余数作为地址。
f(x)=xmodp;(p为常数)
对p的选择很重要,一般取接近表容量的最小素数。 - 平方取中法
把关键字平方,取平方结果的中间几位作为地址。
比如,给你一个数值 111 ,平方后得到 12321 ,如果取中间 1 位作为地址,则3 为 111 的散列地址。
再给个数值 222 ,平方后得到 49284 ,如果取中间 1 位作为地址,则2 为 111 的散列地址。
想要完美的构造散列函数是不可能的,因此总会出现几个数值抢坑的局面。比如:
常用的处理抢坑(冲突)问题的方法有:
- 开放定址法。比如上图中,我计算出
24
的地址为
0
,然后我到位置
0
处发现位置
0 处存放的是 100 ,这时我就不能把 24 放在位置 0 处,我转而寻找下一个空白的散列地址。我先计算(f(24)+d1)modn (其中 n 是散列表的长度,d1 是序列 d1,d2,d3...dn−1 中的第一个值),看看计算所得的地址处有没有值,如果没有就把24存进去,如果有值,再计算 (f(24)+d2)modn ,如此这般,直到找到的地址处没有值把 24 插入。
序列一般有三种的取值方式,
- 线性探测再散列, d=1,2...n−1
- 二次探测再散列, d=12,−12,22,−22...v2,−v2(v≤n/2)
- 链地址法
将所有具有相同散列地址的不同元素值放到一个单链表中,在散列表中存放这些单链表的头指针。 - 建立一个公共溢出区
- 开放定址法。比如上图中,我计算出
24
的地址为
0
,然后我到位置
0
处发现位置
下面是一个利用开放地址法写的例子:
template<typename DataType>class HashTable
{
public:
HashTable(int capacity = 10)
{
_capacity = capacity;
_datas = new DataType[capacity];
_length = 0;
for (int i = 0;i<capacity;i++)
{
_datas[i]=NULL;
}
}
~HashTable()
{
delete [] _datas;
}
//采用除留余数法f(x)=data%p
int hashFunc(DataType data,int p)
{
return data % p;
}
//返回要查找元素的地址,利用开放定址法来解决冲突
int searchData(DataType data)
{
int place = hashFunc(data,11);
if (_datas[place] == data)//如果该地址处的值等于我要找的值
{
return place;
}
//如果该地址的值不等于我要找的值,利用开放地址法
int nextPlace = (place + 1)%_capacity;
while(nextPlace != place){
if (_datas[nextPlace] == data)//如果这个地方的值等于我们要找的data了
{
return nextPlace;
}
if (_datas[nextPlace] == NULL)//如果这个地方还没有值,那就说明我们要找的data不在里面
{
break;
}
nextPlace = (nextPlace +1)%_capacity;
}
if (nextPlace == place)//如果循环了一圈,既没有找到空白地址,也没有找到存有data值的地址,则说明这个值不在该散列表中,同时也没有地方给这个值用了
{
return -1;
}else{//找到了空白的区域
cout<<"你要的值不在散列表中,我找到了一个空白区域,你可以把值插入进去了"<<endl;
return nextPlace;
}
}
bool insertData(DataType data)
{
int place = searchData(data);
if (place < 0) return false;
//如果要插入的值已经在散列表中,返回false
if (_datas[place] == data)
{
return false;
}
//能到这一步,说明searchData返回的是一个空白区域
_datas[place]=data;
return true;
}
void printDatas(){
for (int i =0;i<_capacity;i++)
{
cout<<_datas[i]<<endl;
}
}
private:
int _capacity;
int _length;
DataType * _datas;
};