哈希函数:如果输入的关键字是整数,则一般合理的方法是直接返回“key mod TableSize”的结果,除非key碰巧具有某些特殊的性质。在这种情况下,散列函数的选择需要仔细考虑。例如,若表的大小为10而关键字都以0为个位,则此时上述标准的散列函数就是一个不好的选择。为了避免这种情况,好的办法通常是保证表的大小为素数。
剩下的主要编程细节是解决冲突的消除问题。若果当一个元素被插入时,另一个元素已经存在(散列值相同),那么就会产生一个冲突,解决这种冲突的方法有多种,其中最简单的就是:分离链接法和开放地址法。
(一)分离链接法:将散列到同一个值得所有元素都保留着一个表中。为方便起见这些表都有表头。为执行查找操作(Find),使用散列函数确定使用哪个表,通常的方式遍历该表,找到所有查找的元素在该表中的位置。执行插入操作是(Insert),遍历一个相应的表以检查该元素是否已经处在适当的位置(如果插入一个重复元,通常留出一个额外的域,这个域当重复元出现时增加1)。如果这个元素是一个新的元素,那么它或者插入表的前端,或者插入表的尾部(有时候新元素插入到表的前端,不仅是因为方便,而还因为新进插入的元素最有可能最先访问)。
散列函数处理冲突的方法(一):分离链接散列表
#include<iostream>
#define MINTABLESIZE 8
using namespace std;
typedef int ElemType;
typedef struct LNode //声明表的节点(链表)
{
ElemType data;
struct LNode *next;
}LinstNode, *LinkList;
typedef struct HashNode //哈希表的声明
{
int TableSize;
LinkList *TheLists;
}HashNode, *HashTable;
int Hash(const ElemType key, int TableSize) //哈希函数的构造
{
return key % TableSize;
}
int NextPrime(int data) //哈希表长的选择(素数)
{
int prime = data;
while(prime >= data)
{
int i;
for(i = 2; i <= prime / 2; i++)
{
if(prime % i == 0)
break;
}
if(i > prime / 2)
return prime;
prime ++;
}
}
//散列表的初始化
HashTable InitializeHashTable(int TableSize)
{
HashTable H;
if(TableSize < MINTABLESIZE)
{
cout << "Table size is too small!" << endl;
return NULL;
}
H = new HashNode();
H->TableSize = NextPrime(TableSize); //所需要的哈希表长
H->TheLists = new LinkList(); //指向链表指针的指针
for(int i = 0; i < H->TableSize; i++)
{
H->TheLists[i] = new LNode(); //为每一个哈希表元素分配一个链表
H->TheLists[i]->next = NULL; //为每一个链表构建一个头结点
}
return H;
}
//插入元素
void Insert(ElemType key, HashTable H)
{
LNode *position, *Newcell;
LinkList L;
L = NULL;
//设哈希表中没有该元素
Newcell = new LNode();
Newcell->data = key;
L = H->TheLists[Hash(key, H->TableSize)];
Newcell->next = L->next;
L->next = Newcell;
}
void Find(ElemType key, HashTable H) //在哈希表中查找某一元素
{
LNode *position;
LinkList L;
L = H->TheLists[Hash(key, H->TableSize)];
position = L->next;
while(position != NULL && position->data != key)
{
position = position->next;
}
if(position == NULL)
cout << "Not find this value!" << endl;
else
cout << position ->data << endl;
}
int main()
{
HashTable H;
H = InitializeHashTable(9);
for(int i = 0; i < 10; i++)
Insert(i, H);
Find(3, H);
system("pause");
return 0;
}
(二)开放地址散列表
分离链接散列算法的缺点是需要指针,由于给新单元分配地址需要时间,因此这就导致算法的速度多少有些减慢,同时,算法实际上还要求对另一种数据结构的实现。开放地址散列法是另一种不用链表解决冲突的方法。在开放地址散列算法系统中,如果有冲突发生那么就要尝试使用另一种单元直到找到空的单元为止。
1)线性探测解决冲突:
#include<iostream>
#define NULLKEY -32768
using namespace std;
#define TableSize 10
typedef int ElemType;
typedef struct HashNode
{
ElemType *data;
int HashSize;
} HashTable;
int InitializedHashTable(HashTable *H)
{
H->HashSize = TableSize;
H->data = new int [TableSize];
for(int i = 0; i < TableSize; i++)
{
H->data[i] = NULLKEY;
}
return 0;
}
int HashKey(ElemType key)
{
return key % TableSize;
}
void InsertHashValue(HashTable *H, ElemType key)
{
int position;
position = HashKey(key);
while(H->data[position] != NULLKEY)
{
position = (position + 1) % TableSize;
//position = position + 2 * ++collisionNum -1;
}
//if(position >= H->HashSize)
// position -= H->HashSize;
H->data[position] = key;
}
int Find(HashTable *H, ElemType key)
{
int position;
//int collisionNum = 0;
position = HashKey(key);
while(H->data[position] != key)
{
position = (position + 1) % TableSize;
//position = position + 2 * ++collisionNum -1;
if(H->data[position] == NULLKEY || position == HashKey(key))
return -1;
}
cout << H->data[position] << " " << key << endl;
return 0;
}
int main()
{
int position;
HashTable H;
InitializedHashTable(&H);
for(int i = 2; i < 8; i++)
InsertHashValue(&H, i);
position = Find(&H, 7);
system("pause");
return 0;
}
2)平方探测进行查找
int Find(HashTable H, ElemType key) { int CurrentPosition; int CollisionNum = 0; CurrentPosition = Hash(key, H->TableSize); cout << Hash(key, H->TableSize) << endl; while(H->TheCells[CurrentPosition].Info != Empty && H->TheCells[CurrentPosition].data != key) { CollisionNum++; CurrentPosition = CurrentPosition + 2 * ++CollisionNum - 1; } if(CurrentPosition >= H->TableSize) CurrentPosition -= H->TableSize; cout << H->TheCells[CurrentPosition].data << endl; return CurrentPosition; }