日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

哈希(hash)表查找速度为什么那么快?快在哪里了?

發(fā)布時間:2023/12/20 编程问答 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 哈希(hash)表查找速度为什么那么快?快在哪里了? 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

先看數(shù)組存儲數(shù)據(jù)是怎么樣的。

現(xiàn)在有一個數(shù)組,它里面每個單元存儲的是數(shù)據(jù)的地址

這叫指針數(shù)組吧,假設(shè)它有100個單元

我們稱他為p[100]

現(xiàn)在我想把一百個數(shù)據(jù)(地址)放到里面

我們想把某個數(shù)據(jù)放到p的第幾個單元完全是由

我們決定的,可以說想怎么放就怎么放

是一種亂放,既然是亂放,那么查找起來就比較耗時。

?

哈希表是怎么存儲數(shù)據(jù)的呢?

哈希表同樣是一個指針數(shù)組。

同樣需要存儲100個數(shù)據(jù),需要的就不是100個單元了,因為哈希表要把某個數(shù)據(jù)存放在某個單元不是隨機的一個過程,而是算出來的,這個算法叫哈希函數(shù)

比如要存儲一個數(shù)據(jù)對

張三 1882356

李四 ?23456789

王五 ?58856456

張三經(jīng)過哈希函數(shù)算出來的值是138,那么哈希表最少需要138個單元,因為張三對應(yīng)的數(shù)據(jù)1882356要存儲在指針數(shù)組的p[138]的位置上。

李四經(jīng)過哈希函數(shù)算出來的值是500,那么2356789這個數(shù)據(jù)就要存放在p[500]這個位置上。

所以你明白了,"數(shù)據(jù)的地址放在指針數(shù)組的哪個單元格是算出來的,是有跡可尋的,并不是想放在哪里就放在哪里"

那查找的時候就好查找了,你要查找張三對應(yīng)的數(shù)據(jù),

直接把張三用哈希函數(shù)算一下,得到138,哦 張三的數(shù)據(jù)就在p[138]這個位置上,所以一下就找到了。

?

哈希表是一個key- value的數(shù)據(jù)對,p是一個指針數(shù)組,用來存放value的地址,那么key和value的關(guān)系是下面這樣的。

p[f(key)]=&value

?

可以看出哈希表有時候會浪費很大空間的,比如上面張三 李四 王五那個例子 如果按照哈希表存儲要定義一個500個大小的指針數(shù)組。怎么解決這個問題呢?我再看看書。

? ? ? ?

HASH的理性認識

8/03 為什么會有哈希表,人們存儲數(shù)據(jù)時,想找到一種查找,插入和刪除、更新復(fù)雜度都不是很大的方法,于是哈希表應(yīng)運而生。

? ? ?

  • ?數(shù)組存儲空間是連續(xù)的,占用內(nèi)存嚴重,占用空間很大,但數(shù)組的二分查找時間復(fù)雜度小。
  • ? ?尋址容易,插入和刪除困難。
  • ?鏈表存儲區(qū)間離散,占用內(nèi)存小,空間復(fù)雜度小,但是時間復(fù)雜度很大。? ? ?
  • ? ?尋址困難,插入和刪除容易。
  • ?哈希表
  • ? 尋址容易 插入和刪除也容易的數(shù)據(jù)結(jié)構(gòu)

hash table的實現(xiàn),最常用的是拉鏈法,可以理解為“鏈表的數(shù)組

?

從上圖我們可以發(fā)現(xiàn)哈希表是由數(shù)組+鏈表組成的,一個長度為16的數(shù)組中,每個元素存儲的是一個鏈表的頭結(jié)點。那么這些元素是按照什么樣的規(guī)則存儲到數(shù)組中呢。一般情況是通過hash(key)%len獲得,也就是元素的key的哈希值對數(shù)組長度取模得到。比如上述哈希表中,12%16=12,28%16=12,108%16=12,140%16=12。所以12、28、108以及140都存儲在數(shù)組下標為12的位置。

HashMap的存取

????HashMap的功能是通過“鍵(key)”能夠快速的找到“值”。下面我們分析下HashMap存數(shù)據(jù)的基本流程:

????1、 當調(diào)用put(key,value)時,首先獲取key的hashcode,int hash = key.hashCode();

????2、 再把hash通過一下運算得到一個int h.

hash ^= (hash >>> 20) ^ (hash >>> 12);??// >>>是無符號的右移運算, 高位直接補零,低位移除。>>表示帶符號的右移運算, 高位如果符號位為正補零,符號位負補一,低位直接移除

hash = hash ^?(hash >>> 20) ^ (hash >>> 12); ^代表異或運算

int h = hash ^ (hash >>> 7) ^ (hash >>> 4);

為什么要經(jīng)過這樣的運算呢?這就是HashMap的高明之處。先看個例子,一個十進制數(shù)32768(二進制1000 0000 0000 0000),經(jīng)過上述公式運算之后的結(jié)果是35080(二進制1000 1001 0000 1000)。看出來了嗎?或許這樣還看不出什么,再舉個數(shù)字61440(二進制1111 0000 0000 0000),運算結(jié)果是65263(二進制1111 1110 1110 1111),現(xiàn)在應(yīng)該很明顯了,它的目的是讓“1”變的均勻一點,散列的本意就是要盡量均勻分布。那這樣有什么意義呢?看第3步。

????3、 得到h之后,把h與HashMap的承載量(HashMap的默認承載量length是16,可以自動變長。在構(gòu)造HashMap的時候也可以指定一個長 度。這個承載量就是上圖所描述的數(shù)組的長度。)進行邏輯與運算,即 h & (length-1),這樣得到的結(jié)果就是一個比length小的正數(shù),我們把這個值叫做index。其實這個index就是索引將要插入的值在數(shù)組中的 位置。第2步那個算法的意義就是希望能夠得出均勻的index,這是HashTable的改進,HashTable中的算法只是把key的 hashcode與length相除取余,即hash % length,這樣有可能會造成index分布不均勻。還有一點需要說明,HashMap的鍵可以為null,它的值是放在數(shù)組的第一個位置。

????4、 我們用table[index]表示已經(jīng)找到的元素需要存儲的位置。先判斷該位置上有沒有元素(這個元素是HashMap內(nèi)部定義的一個類Entity, 基本結(jié)構(gòu)它包含三個類,key,value和指向下一個Entity的next),沒有的話就創(chuàng)建一個Entity<K,V>對象,在 table[index]位置上插入,這樣插入結(jié)束;如果有的話,通過鏈表的遍歷方式去逐個遍歷,看看有沒有已經(jīng)存在的key,有的話用新的value替 換老的value;如果沒有,則在table[index]插入該Entity,把原來在table[index]位置上的Entity賦值給新的 Entity的next,這樣插入結(jié)束。

大學(xué)學(xué)過記憶最深刻的是除留余數(shù)發(fā)

假設(shè)哈希表長為m,p為小于等于m的最大素數(shù),則哈希函數(shù)為

h(k)=k??%??p ,其中%為模p取余運算。

例如,已知待散列元素為(18,75,60,43,54,90,46),表長m=10,p=7,則有

????h(18)=18 % 7=4????h(75)=75 % 7=5????h(60)=60 % 7=4???

????h(43)=43 % 7=1????h(54)=54 % 7=5????h(90)=90 % 7=6???

????h(46)=46 % 7=4

此時沖突較多。為減少沖突,可取較大的m值和p值,如m=p=13,結(jié)果如下:

????h(18)=18 % 13=5????h(75)=75 % 13=10????h(60)=60 % 13=8????

????h(43)=43 % 13=4????h(54)=54 % 13=2????h(90)=90 % 13=12???

????h(46)=46 % 13=7

?

解決hash沖突的辦法:

最常用的方法:鏈地址法

我們先復(fù)習(xí)數(shù)據(jù)結(jié)構(gòu)里的一個知識點:在一個長度為n(假設(shè)是10000)的線性表(假設(shè)是ArrayList)里,存放著無序的數(shù)字;如果我們要找一個指定的數(shù)字,就不得不通過從頭到尾依次遍歷來查找,這樣的平均查找次數(shù)是n除以2(這里是5000)。

我們再來觀察Hash表(這里的Hash表純粹是數(shù)據(jù)結(jié)構(gòu)上的概念,和Java無關(guān))。它的平均查找次數(shù)接近于1,代價相當小,關(guān)鍵是在Hash表里,存放在其中的數(shù)據(jù)和它的存儲位置是用Hash函數(shù)關(guān)聯(lián)的。

我們假設(shè)一個Hash函數(shù)是x*x%5,( * & %的優(yōu)先級是從左至右)當然實際情況里不可能用這么簡單的Hash函數(shù),我們這里純粹為了說明方便,而Hash表是一個長度是11的線性表。如果我們要把6放入其中,那么我們首先會對6用Hash函數(shù)計算一下,結(jié)果是1,所以我們就把6放入到索引號是1這個位置。同樣如果我們要放數(shù)字7,經(jīng)過Hash函數(shù)計算,7的結(jié)果是4,那么它將被放入索引是4的這個位置。這個效果如下圖所示。

?

這樣做的好處非常明顯。比如我們要從中找6這個元素,我們可以先通過Hash函數(shù)計算6的索引位置,然后直接從1號索引里找到它了。

不過我們會遇到“Hash值沖突”這個問題。比如經(jīng)過Hash函數(shù)計算后,7和8會有相同的Hash值,對此Java的HashMap對象采用的是”鏈地址法“的解決方案。效果如下圖所示。

?

具體的做法是,為所有Hash值是i的對象建立一個同義詞鏈表。假設(shè)我們在放入8的時候,發(fā)現(xiàn)4號位置已經(jīng)被占,那么就會新建一個鏈表結(jié)點放入8。同樣,如果我們要找8,那么發(fā)現(xiàn)4號索引里不是8,那會沿著鏈表依次查找。

雖然我們還是無法徹底避免Hash值沖突的問題,但是Hash函數(shù)設(shè)計合理,仍能保證同義詞鏈表的長度被控制在一個合理的范圍里。

總結(jié)

以上是生活随笔為你收集整理的哈希(hash)表查找速度为什么那么快?快在哪里了?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。