之前的學習了Hash表的基礎知識(數據結構與算法(Hash表)), 知道了Hash表的核心就是Hash函數,今天來學習完美hash函數。
一、什麼是完美hash函數?
如果存在函數h(x)將集合U映射到集合S並且沒有碰撞, 我們就可以說h(x)是集合U到集合S的完美hash函數。
二、如何實現一個完美hash函數
實現的思路不復雜, 但是有一個前提:在構建hash表之前, 我們要先獲得所有可能的key。然用之前的方法(好像有些地方叫除留餘數法)進行hash分組, 然後用同樣的方法將落在不同槽的數據再做一次映射, 確保這次映射不再有碰撞。
三、c++代碼實例實現完美hash函數
class Hash {
public:
Hash(std::list<int> keys)
:size_(keys.size())
,region_sizes(keys.size())
,offset_table(keys.size())
,a2_array(keys.size()){
// 一級hash
int sum = 10 * size_;
int max_sum = 5 * size_;
while (sum > max_sum) { // 獲得分佈相對均勻的hash
a = rnd.rand();
sum = 0;
for (auto& v : region_sizes) {
v = 0;
}
for (auto key : keys) {
int h = key * a % MAXP % size_;
++region_sizes[h];
sum += region_sizes[h] * region_sizes[h];
}
}
// 二級hash
sum *= 2;
hash_table.resize(sum, MAXP);
for (auto& v : region_sizes) {
v *= 2;
}
// 計數個區間起始位置
offset_table[0] = 0;
for (int i = 1; i < size_; ++i) {
offset_table[i] = offset_table[i-1] + region_sizes[i-1];
}
// 生成二級hash映射係數
for (auto& v : a2_array) {
v = rnd.rand();
}
// 檢查是否有碰撞, 如果有碰撞,重新生成映射係數,直到沒有碰撞爲止
bool isCollision = true;
while (isCollision) {
isCollision = false;
for (auto key : keys) {
int h1 = a * key % MAXP % size_;
int h2 = a2_array[h1] * key % MAXP % region_sizes[h1] + offset_table[h1];
if (hash_table[h2] == MAXP
|| hash_table[h2] == key) {
hash_table[h2] = key;
}
else {
isCollision = true;
a2_array[h1] = 0; // 係數需要重新生成
}
}
if (isCollision) {
for (int i = 0; i < size_; ++i) {
if (a2_array[i] == 0) {
a2_array[i] = rnd.rand();
// 清除對應區的hash表
for (int j = offset_table[i]; j < offset_table[i] + region_sizes[i]; ++j) {
hash_table[j] = MAXP;
}
}
}
}
}
}
int hash(int key) {
int h1 = a * key % MAXP % size_;
return a2_array[h1] * key % MAXP % region_sizes[h1] + offset_table[h1];
}
private:
int a; // 原始映射係數
int size_; // 所以key的數量
std::vector<int> region_sizes; // 一級hash計數及保存最終hash表的各區間長度
std::vector<int> offset_table; // 各區間的起始位置
std::vector<int> a2_array; // 各區間的映射係數
std::vector<int> hash_table; // 最終hash表
HashRand rnd; // 用於生成在區間[1,46337)的隨機數
};
這個實現的內存有些浪費, 有待改進。