數據結構與算法--聊聊散列表查找
前言
在日常的工作中,經常會接觸到散列表、MD5算法、哈希表等一些技術,這些都是運用了散列技術。
那麼散列表是怎麼查找的呢?怎麼設計一個散列呢?接下來來聊聊散列表設計的常見手段和怎麼解決散列衝突。
1. 散列函數常見手段
算列技術:記錄的存儲位置和它的關鍵字之間建立一個確定的對應關係F,使得關鍵字key
對應一個存儲位置F(key)。查找時,根據這個對應關係,找到key
的映射F(key)
,若查找集合中存在這個記錄,則必定在F(key)
的位置上。
設計散列函數時:
- 存儲簡單,分佈均勻,
- 不適合大範圍的查找
接下來,瞭解一下常見的散列函數設計方法:
1.1 直接定值法:
優點:簡單,均勻,不易產生衝突
確定:需要事先知道數據關鍵字的分佈情況,適合查找表小且連續的情況。
例如下圖,對0-100歲的人口統計,
用散列函數存儲,F(key) = key
,則
假如查找3歲的人口,根據F(3) = 3
,直接找到爲450W
。
假如下圖,要對1980年後每年出生的人統計,
可以設計F(key) = key - 1980
。
直接定址法可以設成一個線性函數,
F(key) = a * key + b
(其中a和b爲常數)。
1.2 數字分析法:
如果關鍵字是一個位數特別多的數字,比如手機號,找出數字的規律,儘可能利用這些數據來構造衝突機率較低的散列地址。
比如一個地區手機號,前面很多位都相同,不同可能是最後4位數,將這部分作爲散列算法的依據。
1.3 平方取中法
取關鍵字平方後的中間幾位作爲散列地址。
先通過求關鍵字的平方值擴大相近數的差別,然後根據表長度取中間的幾位數作爲散列函數值。又因爲一個乘積的中間幾位數和乘數的每一位都相關,所以由此產生的散列地址較爲均勻。
1.4. 摺疊法
摺疊法就是將關鍵字從左到右分割成位數相等的幾部分(注意最後一部分位數不夠可以稍微短些)。 然後將幾部分疊加求和,並按散列表長,取後幾位作爲散列地址
例如:對數字9876543210
進行分割,三位一組,共四組,分別爲987
、654
、321
和0
。然後對四個數字求和得到1962
,最終取和的後三位962
作爲散列地址。
1.5. 除留餘數發
取關鍵字被某個不大於散列表表長m的數p除後所得的餘數爲散列地址。
2. 散列衝突的解決
2.1 開放定址法
開放定址法就是一旦發生了衝突,就去尋找下一個空的散列地址,只要散列表足夠大,空的散列地址總能找到,並將記錄存⼊。
2.2 再散列函數發
事先準備多個散列函數,當一個散列函數衝突時,用另一個散列函數。
RHi
指的是不同的散列函數
2.3. 鏈地址發
將所有的關鍵字爲同義詞的記錄存儲在一個單鏈表中,我們稱這種爲同義詞子表,在散列表中只存儲同義詞子表的頭指針。
例如:
關鍵字集合 { 12,67,56,16,25,37,22,29,15,47,48,34 }
表⻓爲12
, 我們用散列函數 f ( key ) = key mod 12
則如下存儲:
2.4. 公共溢出法
另開一個公共的表存儲衝突的數據。基本表沒有查找到,就在溢出表中查找。空間浪費嚴重
3. 散列表實現
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXSIZE 100 //存儲空間初始分配量
#define SUCCESS 1
#define UNSUCCESS 0
//定義散列表長爲數組的長度
#define HASHSIZE 12
#define NULLKEY -32768
typedef struct
{
//數據元素存儲基址,動態分配數組
int *elem;
//當前數據元素個數
int count;
}HashTable;
int m=0; /* 散列表表長,全局變量 */
//1.初始化散列表
Status InitHashTable(HashTable *H)
{
int i;
//1 設置H.count初始值; 並且開闢m個空間
m = HASHSIZE;
H->count = m;
H->elem = (int *)malloc(m*sizeof(int));
//2 爲H.elem[i] 動態數組中的數據置空(-32768)
for(i = 0; i < m; i++)
H->elem[i] = NULLKEY;
return OK;
}
//2. 散列函數
int Hash(int key)
{
//除留餘數法
return key % m;
}
//3. 插入關鍵字進散列表
void InsertHash(HashTable *H,int key)
{
//1. 求散列地址
int addr = Hash(key);
//2. 如果不爲空,則衝突
while (H->elem[addr] != NULLKEY)
{
//開放定址法的線性探測
addr = (addr+1) % m;
}
//3. 直到有空位後插入關鍵字
H->elem[addr] = key;
}
//4. 散列表查找關鍵字
Status SearchHash(HashTable H,int key,int *addr)
{
//1. 求散列地址
*addr = Hash(key);
//2. 如果不爲空,則衝突
while(H.elem[*addr] != key)
{
//3. 開放定址法的線性探測
*addr = (*addr+1) % m;
//4. H.elem[*addr] 等於初始值或者循環有回到了原點.則表示關鍵字不存在;
if (H.elem[*addr] == NULLKEY || *addr == Hash(key))
//則說明關鍵字不存在
return UNSUCCESS;
}
return SUCCESS;
}