數據結構與算法--聊聊散列表查找

前言

在日常的工作中,經常會接觸到散列表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進行分割,三位一組,共四組,分別爲9876543210。然後對四個數字求和得到1962,最終取和的後三位962作爲散列地址。

1.5. 除留餘數發

取關鍵字被某個不大於散列表表長m的數p除後所得的餘數爲散列地址。

fi(key)=key mod p(p<m)f_i(key) = key\ mod\ p (p<m)

2. 散列衝突的解決

2.1 開放定址法

開放定址法就是一旦發生了衝突,就去尋找下一個空的散列地址,只要散列表足夠大,空的散列地址總能找到,並將記錄存⼊。

fi(key)=(f(key)+di) Mod m(di=12,12,22,22,...,q2,q2,q<m/2)f_i(key) = (f(key) +d_i)\ Mod\ m;(d_i=1^2,-1^2,2^2,-2^2,...,q^2,-q^2,q<m/2)

2.2 再散列函數發

事先準備多個散列函數,當一個散列函數衝突時,用另一個散列函數。

fi(key)=RHi(key)(i=1,2,3...,k)f_i(key) = RH_i(key)(i=1,2,3...,k)

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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章