0x01.什麼是哈希表
散列表(Hash table,也叫哈希表),是根據關鍵碼值(Key value)而直接進行訪問的數據結構。也就是說,它通過把關鍵碼值映射到表中一個位置來訪問記錄,以加快查找的速度。這個映射函數叫做散列函數,存放記錄的數組叫做散列表。
給定表M,存在函數f(key),對任意給定的關鍵字值key,代入函數後若能得到包含該關鍵字的記錄在表中的地址,則稱表M爲哈希(Hash)表,函數f(key)爲哈希(Hash) 函數。
存儲位置=F(關鍵字)
F稱爲散列函數或哈希函數。
採用散列技術獎記錄存儲在一塊連續的存儲空間中,這塊連續的存儲空間稱爲哈希表(或散列表)。
散列技術是一種存儲方法,也是一種查找方法。
0x02.如何構造哈希函數
遵循的原則:
- 計算簡單
- 散列地址分佈均勻
常用方法:
- 隨機數法:選擇一個隨機函數,取關鍵字的隨機函數值爲它的哈希地址。即H(key)=random(key),其中random爲隨機函數。適用於關鍵字長度不等時。
- 除留餘數法:取關鍵字被某個不大於哈希表表長m的數p除後所得餘數爲哈希地址(p爲素數)
- 摺疊法:將關鍵字分割成位數相同的幾部分(最後一部分的位數可不同),然後取這幾部分的疊加和(捨去進位)作爲哈希地址。適用於關鍵字位數比較多,且關鍵字中每一位上數字分佈大致均勻時。
- 平方取中法:取關鍵字平方後的中間幾位爲哈希地址。(較常用的一種)
- 直接定址法:取關鍵字或關鍵字的某個線性函數值爲哈希地址。即H(key)=key 或 H(key)=a*key+b (a,b爲常數)。
- 數字分析法:若關鍵字是以r爲基的數(如:以10爲基的十進制數),並且哈希表中可能出現的關鍵字都是事先知道的,則可取關鍵字的若干數位組成哈希地址。
考慮的因素:
- 記錄的查找頻率
- 哈希表的大小
- 計算哈希函數所需時間
- 關鍵字的分佈情況
- 關鍵字的長度
0x03.處理哈希衝突
何時衝突:
- 當關鍵字值域遠大於哈希表的長度,而且事先並不知道關鍵字的具體取值時。hash衝突就會發 生。
- 當關鍵字的實際取值大於哈希表的長度時,而且表中已裝滿了記錄,如果插入一個新記錄,不僅發生衝突,而且還會發生溢出。
解決辦法:
- 開放定址法:基本思想是:當關鍵字key的哈希地址p=H(key)出現衝突時,以p爲基礎,產生另一個哈希地址p1,如果p1仍然衝突,再以p爲基礎,產生另一個哈希地址p2,…,直到找出一個不衝突的哈希地址pi ,將相應元素存入其中。
- 鏈地址法:基本思想是將所有哈希地址爲 i 的元素構成一個稱爲同義詞鏈的單鏈表,並將單鏈表的頭指針存在哈希表的第i個單元中,因而查找、插入和刪除主要在同義詞鏈中進行。鏈地址法適用於經常進行插入和刪除的情況。
- 再哈希法:當哈希地址Hi=RH1(key)發生衝突時,再計算Hi=RH2(key)……,直到衝突不再產生。這種方法不易產生聚集,但增加了計算時間。
0x04.哈希表應用實例
結構:
#define MAXSIZE 1000
#define NULLADR -65535
typedef struct
{
int* adr;//元素基址
int size;//元素個數
}HashTable;
int TableSize = 0;
建表:
void IniHashTable(HashTable* H)
{
int i;
TableSize = MAXSIZE;
H->size = TableSize;
H->adr = (int*)malloc(TableSize * sizeof(int));
for (i = 0; i < TableSize; i++)
{
H->adr[i] = NULLADR;
}
}
哈希函數:
int Hash(int key)//哈希函數
{
return key % TableSize;//除留取餘法
}
插入元素:
void InsertHash(HashTable* H, int key)
{
int i = Hash(key);
while (H->adr[i] != NULLADR)//開放定址法處理散列衝突
{
i = (i + 1) % TableSize;
}
H->adr[i] = key;
}
查找元素:
int Search(HashTable* H, int key)
{
int i = Hash(key);//求散列地址
while (H->adr[i] != key)//處理衝突
{
i = (i + 1) % TableSize;
}
if (H->adr[i] == NULLADR || i == Hash(key))//循環到了原點,查找失敗
{
return false;
}
return true;
}