散列是一种用于以常数平均时间执行插入、删除和查找的技术。理想的散列表数据结构是一个包含有关键字的具有固定大小的数组。典型情况下,一个关键字就是一个带有相关值的字符串。
通常把表的大小记作TableSize。每个关键字被映射到从0到TableSize-1这个范围中的某个数,并且被放到适当的单元中。这个映射就叫做散列函数。
散列函数
如果输入的关键字是整数,则一般方法是直接返回“Key mod TableSize”的结果,除非Key具有某些不理想的性质。例如,若表的大小是10而关键字都以0为个位,则此时上述标准的散列函数就是一个不好的选择。为了避免上面那种情况,通常保证表的大小是素数。
下面是常用方法:
1.直接寻址法:取关键字或关键字的某个线性函数值为散列地址。即H(key)=key或H(key) = a·key + b,其中a和b为常数(这种散列函数叫做自身函数)。若其中H(key)中已经有值了,就往下一个找,直到H(key)中没有值了,就放进去。
2.数字分析法:分析一组数据,比如一组员工的出生年月日,这时我们发现出生年月日的前几位数字大体相同,这样的话,出现冲突的几率就会很大,但是我们发现年月日的后几位表示月份和具体日期的数字差别很大,如果用后面的数字来构成散列地址,则冲突的几率会明显降低。因此数字分析法就是找出数字的规律,尽可能利用这些数据来构造冲突几率较低的散列地址。
3.平方取中法:当无法确定关键字中哪几位分布较均匀时,可以先求出关键字的平方值,然后按需要取平方值的中间几位作为哈希地址。这是因为:平方后中间几位和关键字中每一位都相关,故不同关键字会以较高的概率产生不同的哈希地址。
4.折叠法:将关键字分割成位数相同的几部分,最后一部分位数可以不同,然后取这几部分的叠加和(去除进位)作为散列地址。数位叠加可以有移位叠加和间界叠加两种方法。移位叠加是将分割后的每一部分的最低位对齐,然后相加;间界叠加是从一端向另一端沿分割界来回折叠,然后对齐相加。
5.随机数法:选择一随机函数,取关键字的随机值作为散列地址,通常用于关键字长度不同的场合。
6.除留余数法:取关键字被某个不大于散列表表长m的数p除后所得的余数为散列地址。即 H(key) = key MOD p,p<=m。不仅可以对关键字直接取模,也可在折叠、平方取中等运算之后取模。对p的选择很重要,一般取素数或m,若p选的不好,容易产生同义词。
冲突解决
如果当一个元素被插入时另一个元素已经存在(散列值相同),那么就产生一个冲突,这个冲突需要消除。
解决冲突的方法有:分离连接法、开放定址法(平方探测)、线性探测、二次散列。一、分离链接法--拉链法
分离链接法是将散列到同一个值的所有元素保留到一个表中。
#include<iostream>
using namespace std;
typedef struct listNode {
const char *value;
struct listNode* next;
}ListNode;
typedef struct hashTable {
int tableSize;
ListNode **theLists;
}HashTable;
unsigned int Hash(const char* key,int tableSize);
HashTable* InitHashTable(int tableSize);
ListNode* Find(const char *key,HashTable *H);
void Insert(const char *key,HashTable *H);
unsigned int
Hash(const char *key,int tableSize)
{
unsigned int hashVal = 0;
while (*key != '\0')
hashVal = (hashVal << 5) + *key++;
return hashVal % tableSize;
}
HashTable*
InitHashTable(int tableSize)
{
int i;
HashTable *H;
H = (HashTable*)malloc(sizeof(struct hashTable));
if (H == NULL)
printf("out of space for H!");
H->theLists = (ListNode**)malloc(sizeof(struct listNode)*tableSize);
if (H->theLists == NULL)
printf("out of space for H->theLists");
for (i = 0; i < H->tableSize; i++) {
H->theLists[i] = (ListNode*)malloc(sizeof(struct listNode));
if (H->theLists[i] == NULL)
printf("out of space");
else
H->theLists[i]->next = NULL;
}
return H;
}
ListNode*
Find(const char *key, HashTable *H)
{
ListNode *p;
ListNode *L;
L = H->theLists[Hash(key,H->tableSize)];
p = L->next;
while (p != NULL && p->value != key)
p = p->next;
return p;
}
void
Insert(const char *key, HashTable *H)
{
ListNode* p, *temp;
ListNode *L;
p = Find(key,H);
if (p == NULL)
{
temp = (ListNode*)malloc(sizeof(ListNode));
if (temp == NULL)
printf("out of space");
else {
L=H->theLists[Hash(key,H->tableSize)];
temp->next = L->next;
temp->value = key;
L->next = temp;
}
}
}二、开放定址--平方探测法
分离链接法的缺点是需要指针,由于需要给新单元分配地址需要时间,导致算法速度有些慢。开放定址散列法是一种不用链表解决冲突的方法。
#include<iostream>
using namespace std;
#define MINSIZE 100
static int MinTableSize = MINSIZE;
enum EntryKind {Legitimate,Empty,Deleted};
struct HashEntry
{
char* value;
enum EntryKind info;
};
typedef struct HashTb
{
int TableSize;
struct HashEntry *theCells;
}HashTable;
int NextPrime(int TableSize);
void handlError(char *msg);
unsigned int Hash(char *key,int TableSize);
HashTable* InitializeTable(int TableSize);
unsigned int Find(char*,HashTable *H);
void Insert(char*,HashTable *H);
HashTable* Rehash(HashTable *H);
unsigned int
Hash(char *key, int TableSize)
{
unsigned int hashVal=0;
while (*key != '\0')
hashVal = (hashVal << 5) + *key++;
return hashVal % TableSize;
}
HashTable*
InitializeTable(int TableSize)
{
HashTable *H;
int i;
if (TableSize < MinTableSize)
{
handlError("Table size is too small");
}
H = (HashTable*)malloc(sizeof(HashTable));
if (H == NULL)
handlError("malloc for H is error");
H->TableSize = NextPrime(TableSize);
H->theCells = (struct HashEntry*)malloc(sizeof(struct HashEntry)*H->TableSize);
if (H->theCells == NULL)
handlError("malloc for H->theCells is error");
for (i = 0; i < H->TableSize; i++)
H->theCells[i].info = Empty;
return H;
}
unsigned int
Find(char* key, HashTable *H)
{
unsigned int Pos;
int collisionNum;
collisionNum = 0;
Pos = Hash(key,H->TableSize);
while (H->theCells[Pos].info != Empty && H->theCells[Pos].value != key)
{
Pos = Pos + 2 * ++collisionNum - 1;
if (Pos >= H->TableSize)
Pos -= H->TableSize;
}
return Pos;
}
void
Insert(char* key, HashTable *H)
{
unsigned int Pos;
Pos = Find(key,H);
if (H->theCells[Pos].info != Legitimate)
{
H->theCells[Pos].info = Legitimate;
H->theCells[Pos].value = key;
}
}
HashTable*
Rehash(HashTable *H) //再散列
{
int i, oldSize;
HashEntry *oldCells;
oldCells = H->theCells;
oldSize = H->TableSize;
H = InitializeTable(2*oldSize);
for (i = 0; i < oldSize; i++)
if (oldCells[i].info == Legitimate)
Insert(oldCells[i].value,H);
free(oldCells);
return H;
}
void
handlError(char *msg)
{
fputs(msg,stderr);
fputc('\n',stderr);
exit(1);
}

本文介绍了散列技术的基本原理,包括散列表的概念、散列函数的设计方法及其应用场景。详细阐述了六种常用的散列函数实现方式,并探讨了如何解决散列过程中产生的冲突问题。
5344

被折叠的 条评论
为什么被折叠?



