目录
散列表
散列表(hash table),是把关键值映射到表中到一个位置来存储和访问的。关键值为x,那么pos=f(x),我们把x存储在table[pos]的位置里。这里f(x)就是散列函数(hash function)。
有可能出现f(x)=f(y)的情况,这种叫做冲突。好的哈希函数应当减少冲突。但是冲突有时不可避免,如何解决冲突呢?
分离链接法
将冲突的元素保留在一个链表中,如下
查询时,首先利用hash函数找到在哪个位置,之后遍历相应的链表。
分离链接哈希表的类型声明:
class HashTable{
public:
HashTable(int s){
for(int i=0;i<s;i++){
list<string> l;
table.push_back(l);
}
size =s;
currentSize = 0;
}
bool contains(string x);
void clear();
bool insert(string x);
bool remove(string x);
private:
vector<list<string> > table;
int size ;
int currentSize ;
int hash(string x);
};
哈希函数
int HashTable::hash(string x){
int hashvalue = 0;
for(char ch:x)
hashvalue = 37*hashvalue + ch;
return hashvalue % size;
}
相关操作
//清空
void HashTable::clear(){
for(auto & t:table)
t.clear();
}
//判断包含某个字符串
bool HashTable::contains(string x){
list<string> l = table[hash(x)];
for(list<string>::iterator i= l.begin();i!=l.end();i++)
if(*i==x)
return true;
return false;
}
//删除
bool HashTable::remove(string x){
list<string> &l = table[hash(x)];
list<string>::iterator i = l.begin();
for(i = l.begin();i!=l.end();i++)
if(*i == x)
break;
if(i==l.end())
return false;
l.erase(i);
--currentSize;
return true;
}
//插入
bool HashTable::insert(string x){
list<string> &l = table[hash(x)];
list<string>::iterator i = l.begin();
for(i = l.begin();i!=l.end();i++)
if(*i == x)
return false;
l.push_back(x);
++currentSize;
return true;
}
不用链表的方法
使用链表时,要涉及到分配空间,需要一定时间,于是尝试使用不用链表的方法。当遇到冲突时,探测该位置后第f(i)个位置,如果还有冲突,接着探测,直到没有冲突,然后放入元素。比如:遇到冲突,检测该位置向后第f(1)个位置,如果还有冲突,检测该位置后的第f(2)个位置,直到没有冲突。
线性探测法
f是线性函数,比如f(i) = i,相当于逐个向后探测。例如分别插入32,42,52,它们都是2号位置,遇到冲突,使用f(i)=i探测,如下
平方探测法
线性探测因为是依次挨个向后探测的,所以会连成一片,平方探测法可以解决这个问题。平方探测的冲突函数是二次函数,通常f(i)=i^2. 还是依次插入32,42,52,如下
代码(以平方探测法为例)[1]
class HashTable{
public:
HashTable(int s);
bool contains(string x);
void clear();
bool insert(string x);
bool remove(string x);
private:
//这里用的是懒惰删除法,删除后标注Deleted
enum Nodetype {Active,Empty,Deleted};
//哈希表中每个格存放内容和标注
typedef struct HashNode{
string element;
Nodetype info;
HashNode(string e,Nodetype i){
element = e;
info = i;
}
}HashNode;
vector<HashNode> table;
int size ;//size是表多大
int currentSize ;//当前表有多少个元素
int hash(string x);
int findpos(string x);
bool isActive(int pos);
};
HashTable::HashTable(int s){
size = s;
for(int i=0;i<s;i++)
table.push_back(HashNode("",Empty));
currentSize = 0;
}
bool HashTable::isActive(int pos){
return table[pos].info==Active;
}
//找到第一个为Empty或者内容为x的位置
int HashTable::findpos(string x){
int pos = hash(x);
int offset = 1;
while(table[pos].info!=Empty && table[pos].element!=x){
pos = (pos + (int)pow(offset,2) - (int)pow(offset-1,2)) % size;
offset++;
}
return pos;
}
void HashTable::clear(){
currentSize = 0;
for(auto & t:table)
t.info = Empty;
}
bool HashTable::contains(string x){
return isActive(findpos(x));
}
//删除
bool HashTable::remove(string x){
int pos = findpos(x);
if(isActive(pos)){
table[pos].info = Deleted;
currentSize--;
return true;
}
else
return false;
}
//插入
bool HashTable::insert(string x){
int pos = findpos(x);
//表中已经有x了,return false
if(isActive(pos))
return false;
table[pos].element = x;
table[pos].info = Active;
currentSize++;
return true;
}
//哈希函数
int HashTable::hash(string x){
int hashvalue = 0;
for(char ch:x)
hashvalue = 37*hashvalue + ch;
return hashvalue % size;
}
双散列
前边的f有f(i)=i,有f(i)=i^2,双散列是f(i)=i*hash2(x),就是说当发生冲突时,我们分别探测hash2(x), 2*hash2(x), 3*hash2(x)等位置。常用的hash2函数有hash2(x) = R - (x mod R),R是小于表大小的素数[2]。依然以32,42,52为例。假如选择R = 3,那么hash2(x) = R-(x mod R) = 3 - (x mod 3),
再散列
当哈希表装填的太满时,那么解决冲突时间就会很长,而且可能插入会失败。所以我们进行再散列操作,一种解决方式是新建立一个大约两倍大的表,扫描旧表中的元素,重新装填。判断是否要重新装填,我们可以判断元素数量大于表的一半大小。或者当表到达某一特定的装填因子时再哈希,装填因子=元素数量/表长。
代码
void HashTable::rehash(){
vector<HashNode> temp_table = table;
table.resize(2*temp_table.size());
table.clear();
for( HashNode N:temp_table){
if(N.info == Active)
insert(N.element);
}
}
散列表相关例题
UVA 10887
#include<iostream>
#include<math.h>
#include<cstring>
#include<map>
using namespace std;
int main(){
int a,b;
int cases;
cin>>cases;
int counts = 1;
while(cases--){
map<string,int> m;
cin>>a>>b;
int num=a*b;
char s1[a][20],s2[b][20];
getchar();
for(int i=0;i<a;i++)
fgets(s1[i],20,stdin);
for(int i=0;i<b;i++)
fgets(s2[i],20,stdin);
for(int i=0;i<a;i++){
for(int j=0;j<b;j++){
string str1(s1[i],strlen(s1[i])-1),str2(s2[j],strlen(s2[j])-1);
//在现有的表中,检测到了某个字符串,那么num-1
if(m.find(str1+str2)!=m.end())
num--;
m[str1+str2] = 1;
}
}
printf("Case %d: %d\n",counts++,num);
}
return 0;
}
[1]
[2]