【C# 集合】Hash哈希函數 |散列函數|摘要算法


希函數定義

哈希函數(英語:Hash function)又稱散列函數散列函數摘要算法、單向散列函數散列函數把消息或數據壓縮成摘要,使得數據量變小,將數據的格式固定下來。該函數將數據打亂混合,重新創建一個(哈希函數返回的值)稱為指紋、哈希值哈希代碼摘要散列值hash values,hash codes,hash sums,或hashes)的指紋。散列值通常用一個短的隨機字母和數字組成的字符串來代表。[1]好的散列函數在輸入域中很少出現散列沖突

完美哈希函數 就是指沒有沖突的哈希函數。

如今,散列算法也被用來加密存在數據庫中的密碼(password)字符串,由於散列算法所計算出來的散列值(Hash Value)具有不可逆(無法逆向演算回原本的數值)的性質,因此可有效的保護密碼。

使用哈希函數為哈希表編制索引稱為哈希分散存儲尋址

哈希函數的特性

  • 單向散列函數(one-wayhash function),也就是通俗叫的哈希函數
  • 第一個特點:輸入可以任意長度,輸出是固定長度
  • 第二個特點:計算hash值的速度比較快
  • 第三個特點,沖突特性(Collision resistance):找出任意兩個不同的輸入值 x、y,使得 H (x)= H (y)是困難的。(這里稱為「困難」的原因,是消息空間是無窮的,而哈希值空間是有限的,因此一定會存在碰撞,只是對尋找碰撞的算力需要有難度上的約束以哈希函數 SHAI (輸出為 160-bit)為例:其輸出空間為(0,2^160),假設輸出范圍一萬億個哈希值,發生碰撞的概率僅為 3×10-25。被碰撞的概率太低,幾乎不可能。
  • 第四個特點:隱藏性(Hiding)或者叫做單向性(one-way):哈希函數的單向性意味着,給定一個哈希值,我們無法(很難)逆向計算出其原像輸入。
  • 第五點:謎題友好(puzzlefriendly)

 HashTable

定義

哈希表時保存數據的表。通過哈希函數使得數據和存儲位置之間建立一一對應的映射關系。在查找時,通過哈希函數可以直接找到該元素。

HashTable負載因子

負載因子 = 填入表中的元素個數 / 哈希表的長度

負載因子是哈希表裝滿的標志因子,由於表長是定值,負載因子與填入標志元素的個數成正比,所以負載因子越大,填入表中的元素個數越多,產生沖突的可能性越大,反之,負載因子越小,填入表中的元素個數越少,產生沖突的可能性越小。
        對於閉散列,負載因子是一個很重要的因素,因該嚴格控制在07~0.8左右。超過0.8,CPU緩存命中率降低。所以,在閉散列中,一般負載因子超過0.7就會進行擴容處理。
為什么在散列表中不在負載因子等於1時擴容?
        因為當哈希表快滿了的時候,插入數據,沖突的概率很大,然后需要查找插入位置。會導致效率降低。

 

HashTable中避免哈希函數沖突的方法

哈希函數的目標是盡量減少沖突,但實際應用中沖突是無法避免的,所以在HashTable 中哈希函數沖突發生時,必須有相應的解決方案。而發生沖突的可能性又跟以下兩個因素有關:

(1)       裝填因子α(用於判斷何時擴容hashtable .net core 是0.72 java是0.75):所謂裝填因子是指合希表中已存入的記錄數n與哈希地址空間大小m的比值,即 α=n / m ,α越小,沖突發生的可能性就越小;α越大(最大可取1),沖突發生的可能性就越大。這很容易理解,因為α越小,哈希表中空閑單元的比例就越大,所以待插入記錄同已插入的記錄發生沖突的可能性就越小;反之,α越大,哈希表中空閑單元的比例就越小,所以待插入記錄同已插入記錄沖突的可能性就越大;另一方面,α越小,存儲窨的利用率就越低;反之,存儲窨的利用率就越高。為了既兼顧減少沖突的發生,又兼顧提高存儲空間的利用率,通常把α控制在0.6~0.9的范圍之內,C#的HashTable類把α的最大值定為0.72。

(2)       與所采用的哈希函數有關。若哈希函數選擇得當,就可使哈希地址盡可能均勻地分布在哈希地址空間上,從而減少沖突的發生;否則,就可能使哈希地址集中於某些區域,從而加大沖突發生的可能性。

HashTable中哈希函數沖突解決方法

沖突解決技術可分為兩大類:開散列法(又稱為鏈地址法)和閉散列法(又稱為開放地址法)。哈希表是用數組實現的一片連續的地址空間,兩種沖突解決技術的區別在於發生沖突的元素是存儲在這片數組的空間之外還是空間之內:

(1)       開散列法也叫鏈地址法、拉鏈法(JAVA采用這種方式)發生沖突的元素存儲於數組空間之外。可以把“開”字理解為需要另外“開辟”空間存儲發生沖突的元素。

開散列又叫鏈地址法(開鏈法),哈希表中的數組是一個指針數組。數據是以鏈表的形式保存,數組的元素指向鏈表的頭節點。

        首先,數據通過哈希函數計算出保存位置,計算出來相同位置的數據歸於同一個集合中,每一個子集和稱為一個桶,每一個桶中的元素通過鏈表連接起來,鏈表的頭結點保存在哈希表中。

        將哈希沖突的數據一鏈表的方式保存在一個位置。不會占用其它數據的位置。

 

 

 

 開散列增容:

        開散列增容看的也是負載因子。
        桶的數量是一定的,因為數組的數量一定。隨着元素的不斷插入,桶中元素的數量會不斷增多,極端情況下,可能會導致一個桶中數量鏈表結點非常多,在查找元素時,會影響哈希表的效率。
        因此在一定情況下要對哈希表進行增容。該條件怎么確認呢?最好的情況下,是每一個桶正好一個結點,在插入數據會發生哈希沖突,。
        因當插入元素個數正好等於桶的個數時,即負載因子等於1時,可以給哈希表增容。
        增容時,會按照哈希函數重新改變位置,減少沖突。

 

(2)       閉散列法/開放定址法發生沖突的元素存儲於數組空間之內。可以把“閉”字理解為所有元素,不管是否有沖突,都“關閉”於數組之中。閉散列法又稱開放地址法,意指數組空間對所有元素,不管是否沖突都是開放的。

線性探測

 插入
    通過哈希函數獲取插入位置
    如果該位置沒有元素,直接插入新元素。如果有元素,發生哈希沖突,在沖突位置順序往后找下一個空位置,插入新元素。

 

     刪除

        通過線性探測插入元素,我們知道哈希沖突的元素,一定會保存在保存位置的連續且不為空的位置,意思就是找哈希沖突的數據時,往哈希沖突位置往后找到為空位置截至。所以刪除數據時,不能隨便刪除數據。如下:

 

  因此線性探測采用標記的偽刪除來刪除一個元素,就是哈希表中保存的是一個結構體,結構以里有一個變量保存數據,一個變量了代表當前位置的狀態。

//狀態
enum State{    
    EXIT,//存在元素
    DELETE,//該位置為刪除狀態
    EMPTY,//該位置為空,不存在元素
};
//保存的元素類型
struct Ele{
    T _data;//數據
    State _state = EMPTY;//狀態
};
 【來源:https://python.iitter.com/other/68944.html,轉載請注明】

線性探測缺點:一旦發生哈希沖突,所有沖突的數據都會連在一起保存,容易產生數據堆積,此時插入一數據時,可能一段位置全被占用了,一直要找空位置,導致效率降低。

 二次探測

  針對線性探測導致沖突數據堆積的缺點,二次探測找空位置的方法是:

Hi = (H0 + i * i) % capacity,H0是一開始數據保存的位置,也就是沖突位置,Hi查找的空位置。這樣查找可以使得沖突數據位置的錯開的。

 

 

哈希函數算法「家族」

從哈希(Hash)函數這一概念誕生至今,已經提出了幾十種哈希算法

,每類算法對應不同的參數又會形成不同的算法實現。眾多的哈希函數如同一個江湖,其中 MD 家族和 SHA 家族是「哈希江湖」中最具聲望的兩大家族。

國際: MD4、MD5、SHA-1、SHA-256、SHA-3。(MD 系列、SHA-1 已被破解)

國內:國產自主研發的商用密碼哈希算法,即 SM3。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM