散列函数构造经常要考虑:
1.散列表的长度
2.关键字的长度
3.关键字的分布情况
4.计算散列函数所需的时间
5.记录的查找频率
一个“好”的散列函数应遵循一下两条原则
(1)函数计算要简单,每一关键字只能有一个散列地址与之对应
(2)函数的值域需在表长范围内,计算出的散列地址的分布应均匀,尽可能减少冲突
构造散列函数的几种常用分析法:数字分析法、平方取中法、折叠法、除留余数法
处理冲突的方法:
1.开放地址法,Hi = ( H(key) + di )% m,i=1,2,3,4,5,6,......(线性探测法、二次探测法)
2.链地址法(把具有相同散列地址的记录放在同一个单链表中)
提示:线性探测法在处理冲突的过程中容易产生二次聚集,使得散列地址不相同的记录又产生新的冲突;而链地址法处理冲突不会发生类似的情况,因为散列地址不同的记录在不同的链表中,所以链地址法的平均查找长度小于开放地址法。
线性探测法简单例子(用学生姓名当关键字,用哈希表存储学生信息):
#include<iostream>
using namespace std;
#define OK 1
#define SUCCESS 1
#define FAIL 0
#define HASHSIZE 12 //哈希表大小
#define NULLKEY '#'
struct Node {//元素结点
char name[100];//学生姓名
float high;//身高
float weight;//体重
};
struct HashTable {//哈希表
Node *elem;//声明元素结点
int count;//结点数量
};
int m;
//初始化
bool InitHashTable(HashTable *H) {
int i;
H->count = HASHSIZE;
m = HASHSIZE;
H->elem = new Node[HASHSIZE];//建立元素对象
//初始化元素
for (i = 0; i < H->count; i++)
H->elem[i].name[0] = NULLKEY;//把名字初始化为 #
return OK;
}
//获取对应的地址
int Hash(int key) {//散列函数
return key%HASHSIZE; //除留余数法
}
//插入元素到哈希表
void InsertHash(HashTable *H) {
char key[10]; //关键字(姓名)
cin >> key;
int addr = Hash(key[0]);//获取哈希地址
while (H->elem[addr].name[0]!= NULLKEY)//如果该地址已存在元素,则利用公式找到下一个地址,然后进行存储
addr = (addr + 1) % m;
//对元素进行赋值
strcpy_s(H->elem[addr].name, key);
cin >> H->elem[addr].high;
cin >> H->elem[addr].weight;
}
//利用关键字在哈希表搜索
bool SearchHash(HashTable *H, int &addr) {
char key[10];
cin >> key; //输入关键字
addr = Hash(key[0]);//利用散列公式获取关键字对应的地址
while (strcmp(key, H->elem[addr].name) != 0) {//如果该地址没有存放当前搜索的关键字,
addr = (addr + 1) % m;//则利用冲突公式找到下一个地址,再进行判断
if (addr == Hash(key[0]) || H->elem[addr].name[0] == '#') {//如果遍历直到返回到原来的地址,或者找到该地址存储的关键字为空,则查找失败
return FAIL;
}
}
return SUCCESS;
}
int main() {
HashTable *h = new HashTable;
InitHashTable(h);
int num, addr;
cin >> num;//插入元素数量
while (num--)InsertHash(h);
cin >> num;//查找次数
while (num--) {
if (SearchHash(h, addr))
cout << h->elem[addr].high << " " << h->elem[addr].weight << endl;
else
cout << "Not Found!" << endl;
}
return 0;
}
链地址法:
#include<iostream>
using namespace std;
#define NULLKEY '#'
#define TABLESIZE 49
#define SUCCESS 1
#define FAIL 0
struct Node {
char name[100];
int age;
Node *next;//指向相同散列地址的下一个结点
};
struct HashTable {
int count;
Node *elem;
};
int m;
void InitHashTable(HashTable *H) {
m = TABLESIZE;
int i;
H->elem = new Node[TABLESIZE];
for (i = 0; i < m; i++) {
H->elem[i].name[0] = '#';// #代表还没有放进数据
H->elem[i].next = NULL;
}
}
//散列函数
int getAddr(int key) {
return key - 95;
}
void InsertHash(HashTable *H) {
char key[20];
int addr;
cin >> key;
addr = getAddr(key[0]);
Node *p, *t;
if (H->elem[addr].name[0] == '#') {//如果该地址没有放数据
strcpy_s(H->elem[addr].name, key);
cin >> H->elem[addr].age;
//创建一个还没有放进数据的结点
t = new Node;
t->name[0] = '#';
H->elem[addr].next = t;
}
else {
//遍历到最后一个结点(没有放数据的结点)
p = H->elem + addr;
while (p->name[0] != '#')p = p->next;
//把数据放进这个结点
strcpy_s(p->name, key);
cin >> p->age;
//创建一个还没有放进数据的结点
t = new Node;
t->name[0] = '#';
p->next = t;
}
}
bool SearchHash(HashTable *H,Node *&p) {
char key[20];
int addr;
cin >> key;
addr = getAddr(key[0]);
if (strcmp(H->elem[addr].name, key) != 0) {//如果该地址放的数据和搜索的关键字不一样
p = H->elem[addr].next;//p指向下一个地址
while (strcmp(p->name, key) != 0 && p->name[0] != '#')//如果关键字匹配不成功 和 还没有链表尾巴,
p = p->next;//则p指向下一个结点
}
else {
p = H->elem + addr;
}
if (p->name[0] == '#')return FAIL;//如果p指向链表的尾巴,则搜索失败,返回0
return SUCCESS;
}
int main() {
HashTable *h = new HashTable;
InitHashTable(h);
int count;
cin >> count;
while (count--)InsertHash(h);
cin >> count;
while (count--) {
Node *addrPoint = NULL;
if (SearchHash(h, addrPoint)) {
cout << addrPoint->age << endl;
}
else cout << "Not found!" << endl;
}
return 0;
}