1. 基本概念
哈希表(Hash Table)又叫散列表,是根据关键码值(Key Value)而直接进行访问的数据结构。哈希表通过把关键码值映射到表中的一个位置来访问记录,以加快查找的速度。这个映射函数叫做哈希函数,存放记录的数组叫做哈希表。
当两个不同的关键码值映射到同一个位置时就产生了哈希冲突,需要解决冲突问题,不同的解决方法就是哈希表的不同实现方法之一。
2. 哈希冲突处理方法
2.1. 开放地址法
开放地址哈希表中,当哈希函数发现冲突时,会尝试在其他的单元格中寻找空的单元格进行存储。最普遍的开放地址哈希函数包括线性寻址和二次探测,其中线性寻址顾名思义就是按线性的顺序去寻找空的位置,而二次探测则是按照一定的探测序列去寻找空的位置。
线性探测:
int hash(int key)
{
return key % size;
}
void insert(int key)
{
int h = hash(key);
while(hashTable[h] != NULL && hashTable[h] != "DELETED")
{
h = (h + 1) % size;
}
hashTable[h] = newNode;
}
二次探测:
int hash(int key)
{
return key % size;
}
void insert(int key)
{
int h = hash(key);
for(int i = 0; i < size; i++)
{
int pos = (h + i * i) % size;
if(hashTable[pos] == NULL || hashTable[pos] == "DELETED")
{
hashTable[pos] = newNode;
break;
}
}
}
开放地址哈希表的优点在于不需要一开始就分配完整的空间,但是当数据量特别大的时候容易产生大量的冲突,严重影响效率。
2.2. 链地址法
链地址哈希表中,当哈希函数发现冲突时,会将新的数据存放到同一个位置的链表中,因此也叫做拉链法。链表可以用数组实现也可以用指针实现。
链地址哈希表:
const int MAXN = 1000000;
const int MOD = 1000003;
struct ListNode
{
int val;
ListNode* next;
ListNode(int x) : val(x), next(NULL) {}
};
ListNode* hashTable[MOD];
int hash(int key)
{
return key % MOD;
}
void insert(int key)
{
int h = hash(key);
ListNode* p = hashTable[h];
while(p != NULL && p->val != key)
{
p = p->next;
}
if(p == NULL)
{
ListNode* newHead = new ListNode(key);
newHead->next = hashTable[h];
hashTable[h] = newHead;
}
}
链地址哈希表的优点在于可以解决冲突问题,但是当链表的长度过长时,搜索效率将会降低,严重影响效率。
2.3. 其他方法
还有一些其他的冲突解决方法,比如说:
双散列
建立公共溢出区
但是这些方法不如开放地址法或链地址法来得常用和实用。总体而言,哈希表的冲突处理方法在不同的场景和需求下会有不同的选择。