哈希表是一種搜索結構,當數據量大時,哈希搜索的效率高,平均時間複雜度O(1)。
【哈希查找】:
(1)在插入時,根據待插入元素的關鍵碼,以此函數計算出該元素的存儲位置並按此位置進行存放。
(2)在搜索時,對元素的關鍵碼進行同樣的計算,把求得的函數值當作元素的存儲位置,在結構中按此位置取元素比較,若關鍵碼相等,則搜索成功。
該方式即散列方法(Hash Method),在散列方法中使用的轉換函數叫着散列函數(Hash function),構造出來的結構叫散列表(Hash Table)。用該方法進行搜索不必進行多次關鍵碼的比較,因此搜索的速度比較快。
【哈希衝突】:
對於兩個數據元素的關鍵字Ki和Kj(i != j),有Ki != Kj ( i != j) ,但HashFun( Ki ) ==HashFun( Kj ) ,將該種現象稱爲哈希衝突或哈希碰撞。
哈希表元素的插入:
【散列函數】:
常見的求哈希值的方法:
1.直接定址法:
取關鍵字的某個線性函數爲散列地址:Hash(Key)= A*Key + B。
優點:簡單、均勻
缺點:需要事先知道關鍵字的分佈情況
(適合查找比較小且連續的情況)
2.除留餘數法:
設散列表中允許的地址數爲m,取一個不大於m,但最接近或者等於m的質數,按照哈希函數:Hash( key ) = key % p ( p <= m) ,將關鍵碼轉換成哈希地址。
3.平方取中法
4.摺疊法
5.隨機數法
6.數學分析法
【散列衝突處理方法】:
閉散列法:
在元素插入時遇到哈希衝突,我們可選擇線性探查法處理衝突,還可以選擇二次探查法處理衝突。
這裏我們分析下線性探查法:
給出一組元素,它們的關鍵碼爲:37,25,14,36,49,68,57,11,散列表爲HT[12],表的大小 m=12 ,假設採用Hash(key)= key % p ;(p=11)11是最接近m的質數,就有:
添加元素時,使用散列函數確定元素的插入位置,如果此空間有值:
1.該值是所要插入元素的關鍵碼,不進行插入。
2.產生衝突,依次查看其後的下一個桶,如果發現空位置插入新元素
注意:
散列表的載荷因子:a = 插入元素個數 / 散列表的長度
a是散列表裝滿程度的標誌因子。對於開放地址法,載荷因子非常重要,應嚴格限制在 0.7~0.8 以下。超過 0.8 ,查表時的CPU緩存按照指數曲線上升。
【代碼實現】:
1.用vector開闢出一段空間來存放元素。
2.每個節點都有三種狀態 EXIST(存在) 、DELETE(刪除)、EMPTY(空) ,初始化時每個節點的狀態設置爲EMPTY。
【Hash.h】
#include<iostream>
#include<vector>
#include<assert.h>
#include<utility>
#include<cstring>
using namespace std;
//定義仿函數
template<class K>
struct _HashFunc
{
size_t operator()(const K& key)
{
return key;
}
};
//特化string的版本
template<>
struct _HashFunc<string>
{
static size_t BKDRHash(const char* str)
{
size_t seed = 131; // 31 131 1313 13131 131313
size_t hash = 0;
while (*str)
{
hash = hash*seed + (*str++);
}
return (hash & 0x7fffffff);
}
size_t operator()(const string& key)
{
return BKDRHash(key.c_str()); //c_str()返回的是一個const char* 類型的字符串
}
};
enum Status
{
EXIST,
DELETE,
EMPTY
};
template<class K,class V>
struct HashTableNode
{
//HashTableNode<K,V>* _pNode;
K _key;
V _value;
Status _status;
HashTableNode(const K& key=K(),const V& value=V())
:_key(key)
, _value(value)
, _status(EMPTY)
{ }
};
//素數表,表內爲哈希表的容量,素數降低哈希衝突
const int _PrimeSize = 28;
static const unsigned long _PrimeList[_PrimeSize] =
{
53ul, 97ul, 193ul, 389ul, 769ul,1543ul, 3079ul, 6151ul,
12289ul, 24593ul,49157ul, 98317ul, 196613ul, 393241ul,
786433ul,1572869ul, 3145739ul, 6291469ul, 12582917ul,
25165843ul,50331653ul, 100663319ul, 201326611ul,
402653189ul,805306457ul,1610612741ul, 3221225473ul,
4294967291ul
};
template<class K,class V,class HashFunc=_HashFunc<K>>
class HashTable
{
typedef HashTableNode<K, V> Node;
public:
HashTable()
{}
HashTable(size_t size)
{
assert(size > 0);
_v.resize(size);
_size = 0;
}
//將K值轉換成哈希值
size_t _HashTableFunc(const K& key)
{
HashFunc hf; //定義一個HashFunc的變量hf
size_t hash = hf(key); //用變量hf調用HashFunc的仿函數,返回對應的整型
return hash% _v.size(); //算出哈希值,並返回
}
pair<Node*, bool> Insert(const K& key, const V& value)
{
//檢查是否需要擴容
CheckCapacity();
//對K值進行取餘,判斷插入位置
size_t index = _HashTableFunc(key);
//如果存在,則循環着繼續找
while (_v[index]._status !=EMPTY)
{
index++;
if (index == _v.size())
index = 0;
}
_v[index]._key = key;
_v[index]._value = value;
_v[index]._status = EXIST;
_size++;
return make_pair<Node*, bool>(&_v[index], true);
}
Node* find(const K& key) //查找位置
{
size_t index = _HashTableFunc(key);
//如果存在,則繼續尋找
while (_v[index]._status == EXIST)
{
//若相等,判斷狀態是否是刪除
//若刪除,則沒找到,返回空
//若沒刪除,則返回該位置的地址
if (_v[index]._key == key)
{
if (_v[index]._status == DELETE)
return NULL;
return &_v[index];
}
index++;
if (index == _size)
index = 0;
}
return NULL;
}
void Delete(const K& key)
{
//刪除僅需要將狀態修改
Node* delNode = find(key);
if (delNode)
delNode->_status = DELETE;
}
private:
//交換兩個哈希表
void Swap(HashTable<K, V>& h)
{
swap(_v, h._v);
swap(_size, h._size);
}
void CheckCapacity()
{
//如果_v爲空,則擴容到11
if (_v.empty())
{
_v.resize(11);
}
//如果超過比例係數,則需要擴容
if (_size * 10 / _v.size() >= 7)
{
size_t index = 0;
while (_PrimeList[index] < _v.size())
{
index++;
}
size_t newSize = _PrimeList[index];
HashTable<K, V> newh(newSize); //新近一個哈希表
for (size_t i = 0; i < _v.size(); i++) //將舊的哈希表中的元素重新插入到新的哈希表
{
if (_v[i]._status==EXIST)
newh.Insert(_v[i]._key, _v[i]._value);
}
//交換兩個哈希表
Swap(newh);
}
}
private:
vector<Node> _v;
size_t _size;
};
【測試存入整型數據】
void test()
{
int arr[] = { 3, 7, 12, 23, 45, 67, 13, 43 };
int size = sizeof(arr) / sizeof(arr[0]);
HashTable<int, int> h1(11);
for (int i = 0; i < size; i++)
{
h1.Insert(arr[i], arr[i]+ 3);
}
}
【測試以字符串爲關鍵碼存入】
void test2()
{
HashTable<string, string> h2(11);
h2.Insert("abs", "1111");
h2.Insert("222","2222");
h2.Insert("223", "2223");
h2.Insert("224", "2224");
h2.Insert("225", "2225");
}
【test.cpp】
#include"Hash.h"
int main()
{
//test();
test2();
return 0;
}