国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

【數(shù)據(jù)結(jié)構(gòu)與算法——TypeScript】哈希表

這篇具有很好參考價(jià)值的文章主要介紹了【數(shù)據(jù)結(jié)構(gòu)與算法——TypeScript】哈希表。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。

【數(shù)據(jù)結(jié)構(gòu)與算法——TypeScript】

哈希表(HashTable)

哈希表介紹和特性

  • 哈希表是一種非常重要的數(shù)據(jù)結(jié)構(gòu),但是很多學(xué)習(xí)編程的人一直搞不懂哈希表到底是如何實(shí)現(xiàn)的。

    • 在這一章節(jié)中,我門就一點(diǎn)點(diǎn)來實(shí)現(xiàn)一個(gè)自己的哈希表。
    • 通過實(shí)現(xiàn)來理解哈希表背后的原理和它的優(yōu)勢。
  • 幾乎所有的編程語言都有直接或者間接的應(yīng)用這種數(shù)據(jù)結(jié)構(gòu)。

  • 哈希表通常是基于數(shù)組進(jìn)行實(shí)現(xiàn)的,但是相對(duì)于數(shù)組,它也很多的優(yōu)勢:

    • 口 它可以提供非??焖俚牟迦?甽除-查找操作:
    • 口 無論多少數(shù)據(jù),插入和刪除值都接近常量的時(shí)間:即O(1)的時(shí)間與雜度。實(shí)際上,只需要幾個(gè)機(jī)器指令即可完成;
    • 口 哈希表的速度比樹還要快,基本可以瞬間查找到想要的元素:
    • 口 哈希表相對(duì)于樹來說編碼要容易很多;
  • 哈希表相對(duì)于數(shù)組的一些不足

    • 口 哈希表中的數(shù)據(jù)是沒有順序的,所以不能以一種固定的方式(出如從小到大)來遍力其中的元素(沒有特妹處理情況下)
    • 口 通常情況下,哈希表中的key是不允許重復(fù)的,不能放置相同的key,用于保存不同的元素。
哈希表到底是什么呢?
  • 口 我們只是說了一下它的優(yōu)勢,似乎還是沒有說它到底長什么樣子?
  • 口 這也是哈希表不好理解的地方,不像數(shù)組和鏈表,甚至是樹一樣直接畫出你就知道它的結(jié)構(gòu),甚至是原理了。
  • 口 ????? 它的結(jié)構(gòu)就是數(shù)組,但是它神奇的地方在于對(duì)數(shù)組下標(biāo)值的一種變換, 這種變換我們可以使用哈希函數(shù),通過哈希函數(shù)可以獲取到 HashCode.
  • 口 不著急,我們慢慢來認(rèn)識(shí)它到底是什么。
    【數(shù)據(jù)結(jié)構(gòu)與算法——TypeScript】哈希表,數(shù)據(jù)結(jié)構(gòu)與算法,typescript,散列表,javascript,前端,筆記,數(shù)據(jù)結(jié)構(gòu),算法
?? 我們通過二個(gè)案例,案例需要你挑選某種數(shù)據(jù)結(jié)構(gòu),而你會(huì)發(fā)現(xiàn)最好的 選擇就是哈希表
  • 口 案例一:公司使用一種數(shù)據(jù)結(jié)構(gòu)來保存所有員工;
  • 口 案例二:使用一種數(shù)據(jù)結(jié)構(gòu)存儲(chǔ)單詞信息,比如有50000個(gè)單詞。找到單詞后每個(gè)單詞有自己的翻譯&讀音&應(yīng)用等等:
?? 案例一:公司員工存儲(chǔ)
  • ?? 案例介紹:

    • 口 假如一家公司有1000個(gè)員工,現(xiàn)在我們需要將這些員工的信息使用某種數(shù)據(jù)結(jié)構(gòu)來保存起來
    • 口 你會(huì)采用什么數(shù)據(jù)結(jié)構(gòu)呢?
  • ? 方案一:數(shù)組

    • 口 一種方案是按照順序?qū)⑺械膯T工依次存入一個(gè)長度為1000的數(shù)組中。
    • 口 每個(gè)員工的信息都保存在數(shù)組的某個(gè)位置上。
    • 口 但是我們要查看某個(gè)具體員工的信息怎么辦呢?一個(gè)個(gè)找嗎?不太好找。
    • 數(shù)組最大的優(yōu)勢是什么?通過下標(biāo)值去獲取信息。
    • 口 所以為了可以通過數(shù)組快速定位到某個(gè)員工,最好給員工信息中添加一個(gè)員工編號(hào)(工號(hào)), 而編號(hào)對(duì)應(yīng)的就是員工的下標(biāo)值。
    • 口 當(dāng)查找某個(gè)員工的信息時(shí),通過員工編號(hào)可以快速定位到員工的信息位置,
  • ? 方案二:鏈表

    • 口 鏈表對(duì)應(yīng)插入和刪除數(shù)據(jù)有一定的優(yōu)勢。
    • 口 但是對(duì)于獲取員工的信息,每次都必須從頭遍歷到尾,這種方式顯然不是特別適合我們這里。
  • ? 最終方案:

    • 口 這樣看最終方案似乎就是數(shù)組了, 但是數(shù)組還是有缺點(diǎn),什么缺點(diǎn)呢?
    • 口 但是如果我們只知道員工的姓名, 比如why,但是不知道why的員工編號(hào),你怎么辦呢?
    • 只能線性查找?效率非常的低
    • 能不能有一種辦法,讓why的名字和它的員工編號(hào)產(chǎn)生直接的關(guān)系呢?
      • 也就是通過why這個(gè)名字,我們就能獲取到它的素引值,而再通過索引值我就能獲取到why的信息呢?
      • 口 這樣的方案已經(jīng)存在了,就是使用哈希函數(shù),讓某個(gè)key的信息和索引值對(duì)應(yīng)起來。
?? 案例二:50000個(gè)單詞的存儲(chǔ)
  • ?? 案例介紹:
    • 口 使用一種數(shù)據(jù)結(jié)構(gòu)存儲(chǔ)單詞信息,比如有50000個(gè)單詞.
    • 口 找到單詞后每個(gè)單詞有自己的翻譯&讀音&應(yīng)用等等
  • ? 方案一:數(shù)組?
    • 口 這個(gè)案例更加明顯能感受到數(shù)組的缺陷
    • 口 拿到一個(gè)單詞 lridescent,我想知道這個(gè)單詞的翻譯/讀音/應(yīng)用
    • 口 怎么可以從數(shù)組中查到這個(gè)單詞的位置呢?
    • 口 線性查找?50000次比較?
    • 口 如果你使用數(shù)組來實(shí)現(xiàn)這個(gè)功能,效率會(huì)非常非常低,而且你一定沒有學(xué)習(xí)過數(shù)據(jù)結(jié)構(gòu)。
  • ? 方案二:鏈表?
    • 口 不需要考慮了吧?
  • ? 方案三:有沒有一種方案,可以將單詞轉(zhuǎn)成數(shù)組的下標(biāo)值呢
    • 口 如果單詞轉(zhuǎn)成數(shù)組的下標(biāo),那么以后我們要查找某個(gè)單詞的信息,直接按照下標(biāo)值一步即可訪問到想要的元素
????? 字母轉(zhuǎn)數(shù)字的方案

似乎所有的案例都指向了一目標(biāo):將字符串轉(zhuǎn)成下標(biāo)值
但是,怎樣才能將一個(gè)字符串轉(zhuǎn)成數(shù)組的下標(biāo)值呢?
口 單詞/字符串轉(zhuǎn)下標(biāo)值,其實(shí)就是字母/文字轉(zhuǎn)數(shù)字
口 怎么轉(zhuǎn)?

  • 現(xiàn)在我們需要設(shè)計(jì)一種方案,可以將單詞轉(zhuǎn)成適當(dāng)?shù)南聵?biāo)值:

    • 口 其實(shí)計(jì)算機(jī)中有很多的編碼方案就是用數(shù)字代皆單詞的字符。就是字符編碼。(常見的字符編碼?)
    • 口 比如ASCII編碼:a是97,b是98,依次類推122代表z
    • 口 我們也可以設(shè)計(jì)一個(gè)自己的編碼系統(tǒng),比如a是1,b是2,c是3,依 次類推,z是26。
    • 口 當(dāng)然我們可以加上空格用0代替,就是27個(gè)字符(不考慮大寫問題)
    • 口 但是,有了編碼系統(tǒng)后,一個(gè)單詞如何轉(zhuǎn)成數(shù)字呢?
  • ? 方案一:數(shù)字相加

    • 口 一種轉(zhuǎn)換單詞的簡單方案就是把單詞每個(gè)字符的編碼求和。
    • 口 例如單詞cats轉(zhuǎn)成數(shù)字:3+1+20+19=43.
    • 那么43就作為cats單詞的下標(biāo)存在數(shù)組中。
    • ??【問題】:按照這種方案有一個(gè)很明顯的問題就是很多單詞最終的下標(biāo)可能都是43。
      • 口 比如was/tin/give/tend/moan/tick等等。
      • 口 我們知道數(shù)組中一個(gè)下標(biāo)值位置只能存儲(chǔ)一個(gè)數(shù)據(jù)如果存入后來的數(shù)據(jù),必然會(huì)造成數(shù)據(jù)的獲蓋。
      • 一個(gè)下標(biāo)存儲(chǔ)這么多單詞顯然是不合理的。
      • 口 雖然后面的方案也會(huì)出現(xiàn),但是要盡雖避免。
  • ? 方案二:冪的連乘

    • 口 現(xiàn)在,我們想通過一種算法,讓cats轉(zhuǎn)成數(shù)字后不那么普通。
    • 口 數(shù)字相加的方案就有些過于普通了。
    • 口 有一種方案就是使用冪的連乘,什么是冪的連乘呢?
      • 其實(shí)我們平時(shí)使用的大于10的數(shù)字,可以用一種系的連乘來表示它的唯一性:

        比如:7654 = 7 *103 + 6 * 102 + 5 * 10 + 4

  • 口 我們的單詞也可以使用這種方案來表示:

    • 比如 cats= 3273+1272+20*27+17= 60337
  • 這樣得到的數(shù)字可以基本保證它的唯一性,不會(huì)和別的單詞重復(fù)。

  • ??【問題】:如果一個(gè)單詞是 zzzzzzzzzz(一般英文單詞不會(huì)超過10個(gè)宇符)。那么得到的數(shù)字超過7000000000000。

    • 口 數(shù)組可以表示這么大的下標(biāo)值嗎?
    • 口 而且就算能創(chuàng)建這么大的數(shù)組,事實(shí)上有很多是無效的單詞。
    • 創(chuàng)建這么大的數(shù)組是沒有意義的
  • ?? 兩種方案總結(jié):

    • 第一種方案(將數(shù)字相加求和)產(chǎn)生的數(shù)組下標(biāo)太少
    • 第二種方案(與27的冪相乘求和)產(chǎn)生的數(shù)組下標(biāo)太多

數(shù)據(jù)的哈?;^程

下標(biāo)的壓縮算法
  • 現(xiàn)在需要一種壓縮方法,把冪的連乘方案系統(tǒng)中得到的巨大整數(shù)范圍壓縮到可接受的數(shù)組范圍中。

  • ? 對(duì)于英文詞典,多大的數(shù)組才合適呢?

    • 如果只有50000個(gè)單詞,可能會(huì)定義一個(gè)長度為50000的數(shù)組。
    • 但是實(shí)際情況中,往往需要更大的空間來存儲(chǔ)這些單詞。
    • ?? 因?yàn)槲覀儾荒鼙WC單詞會(huì)映射到每一個(gè)位置。
      • 比如兩倍的大?。?00000。
  • ?? 如何壓縮呢?

    • 現(xiàn)在,就找一種方法,把0到超過7000000000000的范圍,壓縮為從0到100000。
    • 有一種簡單的方法就是使用取余操作符,它的作用是得到一個(gè)數(shù)被另外一個(gè)數(shù)整除后的余數(shù)
  • ? 取余操作的實(shí)現(xiàn):

    • 為了看到這個(gè)方法如何工作,我們先來看一個(gè)小點(diǎn)的數(shù)字范圍壓縮到一個(gè)小點(diǎn)的空間中。
    • ?? 假設(shè)把從0~199的數(shù)字,比如使用largeNumber代表,壓縮為從0到9的數(shù)字,比如使用smallRange代表。
    • ?? 下標(biāo)值的結(jié)果:index = largeNumber % smallRange
    • 當(dāng)一個(gè)數(shù)被10整除時(shí),余數(shù)一定在0~9之間;
    • 比如13%10=3,157%10=7。
    • 當(dāng)然,這中間還是會(huì)有重復(fù),不過重復(fù)的數(shù)量明顯變小了。
    • 因?yàn)槲覀兊臄?shù)組是100000,而只有50000個(gè)單詞。
    • 就好比,你在0~199中間選取5個(gè)數(shù)字,放在這個(gè)長度為10的數(shù)組中,也會(huì)重復(fù),但是重復(fù)的概率非常小。(后面我們會(huì)講到真的發(fā)生重復(fù)了應(yīng)該怎么解決)
哈希表的一些概念
  • 認(rèn)識(shí)了上面的內(nèi)容,相信你應(yīng)該懂了哈希表的原理了,我們來看看幾個(gè)概念:

    • ?? 哈?;?/strong>:將大數(shù)字轉(zhuǎn)化成數(shù)組范圍內(nèi)下標(biāo)的過程,我們就稱之為哈?;?/strong>。
    • ?? 哈希函數(shù):通常我們會(huì)將單詞轉(zhuǎn)成大數(shù)字,大數(shù)字在進(jìn)行哈希化的代碼實(shí)現(xiàn)放在一個(gè)函數(shù)中,這個(gè)函數(shù)我們稱為哈希函數(shù)。
    • ?? 哈希表:最終將數(shù)據(jù)插入到的這個(gè)數(shù)組,對(duì)整個(gè)結(jié)構(gòu)的封裝,我們就稱之為是一個(gè)哈希表
  • 但是,我們還有問題需要解決:

    • 雖然,我們?cè)谝粋€(gè)100000的數(shù)組中,放50000個(gè)單詞已經(jīng)足夠。
    • 但是通過哈希化后的下標(biāo)值依然可能會(huì)重復(fù),如何解決這種重復(fù)的問題呢?

地址沖突解決方案

什么是沖突?
  • ? 盡管50000個(gè)單詞,我們使用了100000個(gè)位置來存儲(chǔ),并且通過一種相對(duì)比較好的哈希函數(shù)來完成。但是依然有可能會(huì)發(fā)生沖突。
    • 比如 melioration 這個(gè)單詞,通過哈希函數(shù)得到它數(shù)組的下標(biāo)值后,發(fā)現(xiàn)那個(gè)位置上已經(jīng)存在一個(gè)單詞demystify
    • 因?yàn)樗?jīng)過哈?;蠛蚼elioration得到的下標(biāo)實(shí)現(xiàn)相同的。
  • ? 這種情況我們成為沖突。
  • ? 雖然我們不希望這種情況發(fā)生,當(dāng)然更希望每個(gè)下標(biāo)對(duì)應(yīng)一個(gè)數(shù)據(jù)項(xiàng),但是通常這是不可能的。
  • ? 沖突不可避免,我們只能解決沖突。
    ??
  • ?? : 就像之前0~199的數(shù)字選取5個(gè)放在長度為10的單元格中
    • 如果我們隨機(jī)選出來的是33,82,11,45,90,那么最終它們的位置會(huì)是3-2-1-5-0,沒有發(fā)生沖突。
    • 但是如果其中有一個(gè)33,還有一個(gè)73呢?還是發(fā)生了沖突。
    • [外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-wMwdr2p6-1691473209048)(/Volumes/web/數(shù)據(jù)結(jié)構(gòu)與算法(ts)/畫圖/哈希表/取余操作.png “發(fā)生沖突”)]
  • ? 我們需要針對(duì) 這種沖突 提出一些解決方案
    • 雖然沖突的可能性比較小,你依然需要考慮到這種情況
    • 以便發(fā)生的時(shí)候進(jìn)行對(duì)應(yīng)的處理代碼。
  • ? 如何解決這種沖突呢?常見的情況有兩種方案。
    • ????? 鏈地址法。
    • ????? 開放地址法。
鏈地址法
  • 鏈地址法是一種比較常見的解決沖突的方案。(也稱為拉鏈法)

  • 其實(shí),如果你理解了為什么產(chǎn)生沖突,看到圖后就可以立馬理解鏈地址法是什么含義了。
    【數(shù)據(jù)結(jié)構(gòu)與算法——TypeScript】哈希表,數(shù)據(jù)結(jié)構(gòu)與算法,typescript,散列表,javascript,前端,筆記,數(shù)據(jù)結(jié)構(gòu),算法

鏈地址法解析
  • ?? 圖片解析:
    • 從圖片中我們可以看出,鏈地址法解決沖突的辦法是每個(gè)數(shù)組單元中存儲(chǔ)的不再是單個(gè)數(shù)據(jù),而是一個(gè)鏈條。
    • 這個(gè)鏈條使用什么數(shù)據(jù)結(jié)構(gòu)呢?常見的是數(shù)組或者鏈表。
    • 比如是鏈表,也就是每個(gè)數(shù)組單元中存儲(chǔ)著一個(gè)鏈表。一旦發(fā)現(xiàn)重復(fù),將重復(fù)的元素插入鏈表的首端或者末端即可
    • 當(dāng)查詢時(shí),先根據(jù)哈?;蟮南聵?biāo)值找到對(duì)應(yīng)的位置,再取出鏈表,依次查詢找尋找的數(shù)據(jù)。
  • ?? 數(shù)組還是鏈表呢?
    • 數(shù)組或者鏈表在這里其實(shí)都可以,效率上也差不多。
    • 因?yàn)楦鶕?jù)哈?;膇ndex找出這個(gè)數(shù)組或者鏈表時(shí),通常就會(huì)使用線性查找,這個(gè)時(shí)候數(shù)組和鏈表的效率是差不多的。
    • 當(dāng)然在某些實(shí)現(xiàn)中,會(huì)將新插入的數(shù)據(jù)放在數(shù)組或者鏈表的最前面,因?yàn)橛X得新插入的數(shù)據(jù)用于取出的可能性更大。
    • 這種情況最好采用鏈表,因?yàn)閿?shù)組在首位插入數(shù)據(jù)是需要所有其他項(xiàng)后移的,鏈表就沒有這樣的問題。
    • 當(dāng)然,我覺得出于這個(gè)也看業(yè)務(wù)需求,不見得新的數(shù)據(jù)就訪問次數(shù)會(huì)更多:比如我們微信新添加的好友,可能是剛認(rèn)識(shí)的,聯(lián)系的頻率不見得比我們的老朋友更多,甚至新加的只是聊一兩句。
    • 所以,這里個(gè)人覺得選擇數(shù)組或者鏈表都是可以的。
開放地址法
  • 開放地址法的主要工作方式是尋找空白的單元格來添加重復(fù)的數(shù)據(jù)。

  • 我們還是通過圖片來了解開放地址法的工作方式。
    【數(shù)據(jù)結(jié)構(gòu)與算法——TypeScript】哈希表,數(shù)據(jù)結(jié)構(gòu)與算法,typescript,散列表,javascript,前端,筆記,數(shù)據(jù)結(jié)構(gòu),算法

開放地址法解析
  • ?? 問題: 新插入32放在什么位置呢?
  • ?? 開放地址解決方法:
    • 新插入的 32 本來應(yīng)該插入到82的位置,但是該位置已經(jīng)包含數(shù)據(jù)
    • 我們發(fā)現(xiàn) 3 和 5 還有 9 的位置是沒有任何內(nèi)容的
    • 這個(gè)時(shí)候就可以尋找到對(duì)應(yīng)的空白位置來放這個(gè)數(shù)據(jù)
    • 但是到底使用哪一個(gè)位置呢? 這就又需要分情況了
  • ?? 圖片解析
    • 從圖片的文字中我們可以了解到
    • ?? 開放地址法其實(shí)就是要尋找空白的位置來放置沖突的數(shù)據(jù)項(xiàng)。
  • 但是探索這個(gè)位置的方式不同,有三種方法:
    • ? 線性探測
    • ? 二次探測
    • ? 再哈希法
線性探測
  • ? 線性探測非常好理解:線性的查找空白的單元。

  • ? 插入的32

    • 經(jīng)過哈希化得到的index=2,但是在插入的時(shí)候,發(fā)現(xiàn)該位置已經(jīng)有了82。怎么辦呢?
    • 線性探測就是從index位置+1開始一點(diǎn)點(diǎn)查找合適的位置來放置32,什么是合適的位置呢?
    • 空的位置就是合適的位置,在我們上面的例子中就是index=3的位置,這個(gè)時(shí)候32就會(huì)放在該位置。
  • ? 查詢32呢?

    • 查詢32和插入32比較相似。
    • 首先經(jīng)過哈?;玫絠ndex=2,比如2的位置結(jié)果和查詢的數(shù)值是否相同,相同那么就直接返回。
    • 不相同呢?線性查找,從index位置+1開始查找和32一樣的。
    • 這里有一個(gè)特別需要注意的地方:如果32的位置我們之前沒有插入,是否將整個(gè)哈希表查詢一遍來確定32存不存在嗎?
    • 當(dāng)然不是,查詢過程有一個(gè)約定,就是查詢到空位置,就停止
    • 因?yàn)椴樵兊竭@里有空位置,32之前不可能跳過空位置去其他的位置。
  • ?? 線性探測的問題

    • ? 刪除32呢?
      • 刪除操作和插入查詢比較類似,但是也有一個(gè)特別注意點(diǎn)。
      • 注意:刪除操作一個(gè)數(shù)據(jù)項(xiàng)時(shí),不可以將這個(gè)位置下標(biāo)的內(nèi)容設(shè)置為null,為什么呢?
      • 因?yàn)閷⑺?strong>設(shè)置為null可能會(huì)影響我們之后查詢其他操作,所以通常刪除一個(gè)位置的數(shù)據(jù)項(xiàng)時(shí),我們可以將它進(jìn)行特殊處理(比如設(shè)置為-1)。
      • 當(dāng)我們之后看到-1位置的數(shù)據(jù)項(xiàng)時(shí),就知道查詢時(shí)要繼續(xù)查詢,但是插入時(shí)這個(gè)位置可以放置數(shù)據(jù)。
    • ?? 線性探測的問題
      • 線性探測有一個(gè)比較嚴(yán)重的問題,就是聚集。什么是聚集呢?
      • 比如我在沒有任何數(shù)據(jù)的時(shí)候,插入的是22-23-24-25-26,那么意味著下標(biāo)值:2-3-4-5-6的位置都有元素。
      • 這種一連串填充單元就叫做聚集。
      • 聚集會(huì)影響哈希表的性能,無論是插入/查詢/刪除都會(huì)影響。
      • 比如我們插入一個(gè)32,會(huì)發(fā)現(xiàn)連續(xù)的單元都不允許我們放置數(shù)據(jù),并且在這個(gè)過程中我們需要探索多次。
      • 二次探測可以解決一部分這個(gè)問題,我們一起來看一看。
二次探測
  • 我們剛才談到,線性探測存在的問題
    • 如果之前的數(shù)據(jù)是連續(xù)插入的,那么新插入的一個(gè)數(shù)據(jù)可能需要探測很長的距離
  • ? 二次探測在線性探測的基礎(chǔ)上進(jìn)行了優(yōu)化:
    • 二次探測主要優(yōu)化的是探測時(shí)的步長,什么意思呢?
    • 線性探測,我們可以看成是步長為1的探測,比如從下標(biāo)值x開始,那么線性測試就是x+1,x+2,x+3依次探測。
    • 二次探測,對(duì)步長做了優(yōu)化,比如從下標(biāo)值x開始,x+12,x+22,x+32。
    • 這樣就可以一次性探測比較長的距離,比避免那些聚集帶來的影響。
  • ?? 二次探測的問題:
    • 但是二次探測依然存在問題,比如我們連續(xù)插入的是32-112-82-2-192,那么它們依次累加的時(shí)候步長的相同的。
    • 也就是這種情況下會(huì)造成步長不一樣的一種聚集。還是會(huì)影響效率。(當(dāng)然這種可能性相對(duì)于連續(xù)的數(shù)字會(huì)小一些)
    • 怎么根本解決這個(gè)問題呢?讓每個(gè)人的步長不一樣,一起來看看再哈希法吧。
再哈希法
  • ?? 為了消除線性探測和二次探測中無論步長+1還是步長+平法中存在的問題, 還有一種最常用的解決方案: 再哈希法.
  • ?? 再哈希法:
    • 二次探測的算法產(chǎn)生的探測序列步長是固定的: 1, 4, 9, 16, 依次類推.
    • 現(xiàn)在需要一種方法: 產(chǎn)生一種依賴關(guān)鍵字的探測序列, 而不是每個(gè)關(guān)鍵字都一樣.
    • 那么, 不同的關(guān)鍵字即使映射到相同的數(shù)組下標(biāo), 也可以使用不同的探測序列.
    • 再哈希法的做法就是: 把關(guān)鍵字用另外一個(gè)哈希函數(shù), 再做一次哈?;?/strong>, 用這次哈?;?strong>結(jié)果作為步長.
    • 對(duì)于指定的關(guān)鍵字,步長在整個(gè)探測中是不變的, 不過不同的關(guān)鍵字使用不同的步長.
  • ?? 第二次哈希化需要具備如下特點(diǎn):
    • 第一個(gè)哈希函數(shù)不同. (不要再使用上一次的哈希函數(shù)了, 不然結(jié)果還是原來的位置)
    • 不能輸出為0(否則, 將沒有步長. 每次探測都是原地踏步, 算法就進(jìn)入了死循環(huán))
  • ?? 其實(shí), 我們不用費(fèi)腦細(xì)胞來設(shè)計(jì)了, 計(jì)算機(jī)專家已經(jīng)設(shè)計(jì)出一種工作很好的哈希函數(shù):
    • stepSize = constant - (key % constant)
    • 其中constant是質(zhì)數(shù), 且小于數(shù)組的容量.
    • 例如: stepSize = 5 - (key % 5), 滿足需求, 并且結(jié)果不可能為0.
哈?;男?/h5>
  • 哈希表中執(zhí)行插入和搜索操作效率是非常高的
    • ? 如果沒有產(chǎn)生沖突,那么效率就會(huì)更高。
    • ? 如果發(fā)生沖突,存取時(shí)間就依賴后來的探測長度。
    • ? 平均探測長度以及平均存取時(shí)間,取決于填裝因子,隨著填裝因子變大,探測長度也越來越長。
    • ? 隨著填裝因子變大,效率下降的情況,在不同開放地址法方案中比鏈地址法更嚴(yán)重,所以我們來對(duì)比一下他們的效率,再?zèng)Q定我們選取的方案。
  • ? 在分析效率之前,我們先了解一個(gè)概念:裝填因子
    • ? 裝填因子表示當(dāng)前哈希表中已經(jīng)包含的數(shù)據(jù)項(xiàng)和整個(gè)哈希表長度的比值。
    • ? 裝填因子 = 總數(shù)據(jù)項(xiàng) / 哈希表長度。
    • ? 開放地址法的裝填因子最大是多少呢?1,因?yàn)樗仨殞ふ业娇瞻椎膯卧拍軐⒃胤湃搿?/li>
    • ? 鏈地址法的裝填因子呢?可以大于1,因?yàn)槔湻梢詿o限的延伸下去,只要你愿意。(當(dāng)然后面效率就變低了)
線性探測效率
  • 下面的等式顯示了線性探測時(shí),探測序列§和填裝因子(L)的關(guān)系
    ? 公式來自于Knuth(算法分析領(lǐng)域的專家,現(xiàn)代計(jì)算機(jī)的先驅(qū)人物),這些公式的推導(dǎo)自己去看了一下,確實(shí)有些繁瑣,這里不再
    給出推導(dǎo)過程,僅僅說明它的效率。
    【數(shù)據(jù)結(jié)構(gòu)與算法——TypeScript】哈希表,數(shù)據(jù)結(jié)構(gòu)與算法,typescript,散列表,javascript,前端,筆記,數(shù)據(jù)結(jié)構(gòu),算法

  • 線性探測效率

    • ? 圖片解析:
      • ? 當(dāng)填裝因子是1/2時(shí),成功的搜索需要1.5次比較,不成功的搜索需要2.5次
      • ? 當(dāng)填裝因子為2/3時(shí),分別需要2.0次和5.0次比較
      • ? 如果填裝因子更大,比較次數(shù)會(huì)非常大。
      • ? 應(yīng)該使填裝因子保持在2/3以下,最好在1/2以下,另一方面,填裝因子越低,對(duì)于給定數(shù)量的數(shù)據(jù)項(xiàng),就需要越多的空間。
      • ? 實(shí)際情況中,最好的填裝因子取決于存儲(chǔ)效率和速度之間的平衡,隨著填裝因子變小,存儲(chǔ)效率下降,而速度上升。
二次探測和再哈希化性能
  • ? 二次探測和再哈希法的性能相當(dāng)。它們的性能比線性探測略好。
    【數(shù)據(jù)結(jié)構(gòu)與算法——TypeScript】哈希表,數(shù)據(jù)結(jié)構(gòu)與算法,typescript,散列表,javascript,前端,筆記,數(shù)據(jù)結(jié)構(gòu),算法

  • ? 圖片解析:

    • ? 當(dāng)填裝因子是0.5時(shí),成功和不成的查找平均需要2次比較
    • ? 當(dāng)填裝因子為2/3時(shí),分別需要2.37和3.0次比較
    • ? 當(dāng)填裝因子為0.8時(shí),分別需要2.9和5.0次
    • ? 因此對(duì)于較高的填裝因子,對(duì)比線性探測,二次探測和再哈希法還是可以忍受的。
鏈地址法性能
  • ? 鏈地址法的效率分析有些不同,一般來說比開放地址法簡單。我們來分析一下這個(gè)公式應(yīng)該是怎么樣的。

    • ? 假如哈希表包含arraySize個(gè)數(shù)據(jù)項(xiàng),每個(gè)數(shù)據(jù)項(xiàng)有一個(gè)鏈表,在表中一共包含N個(gè)數(shù)據(jù)項(xiàng)。
    • ? 那么,平均起來每個(gè)鏈表有多少個(gè)數(shù)據(jù)項(xiàng)呢?非常簡單,N / arraySize
    • ? 有沒有發(fā)現(xiàn)這個(gè)公式有點(diǎn)眼熟?其實(shí)就是裝填因子。
      【數(shù)據(jù)結(jié)構(gòu)與算法——TypeScript】哈希表,數(shù)據(jù)結(jié)構(gòu)與算法,typescript,散列表,javascript,前端,筆記,數(shù)據(jù)結(jié)構(gòu),算法
  • ? OK,那么我們現(xiàn)在就可以求出查找成功和不成功的次數(shù)了

    • ? 成功可能只需要查找鏈表的一半即可:1 + loadFactor/2
    • ? 不成功呢?可能需要將整個(gè)鏈表查詢完才知道不成功:1 + loadFactor。
  • ? 經(jīng)過上面的比較我們可以發(fā)現(xiàn),鏈地址法相對(duì)來說效率是好于開放地址法的。

  • ? 所以在真實(shí)開發(fā)中,使用鏈地址法的情況較多

    • ? 因?yàn)樗粫?huì)因?yàn)樘砑恿四吃睾笮阅芗眲∠陆怠?/li>
    • ? 比如在Java的HashMap中使用的就是鏈地址法。

哈希函數(shù)代碼實(shí)現(xiàn)

哈希函數(shù)
  • ?? 好的哈希函數(shù)應(yīng)該盡可能讓計(jì)算的過程變得簡單,提高計(jì)算的效率。
    • 哈希表的主要優(yōu)點(diǎn)是它的速度,所以在速度上不能滿足,那么就達(dá)不到設(shè)計(jì)的目的了。
    • 提高速度的一個(gè)辦法就是讓哈希函數(shù)中盡量少的有乘法和除法。因?yàn)樗鼈兊?strong>性能是比較低的。
  • ?? 設(shè)計(jì)好的哈希函數(shù)應(yīng)該具備哪些優(yōu)點(diǎn)呢?
    • ? 快速的計(jì)算
      • ? 哈希表的優(yōu)勢就在于效率,所以快速獲取到對(duì)應(yīng)的hashCode非常重要。
      • ? 我們需要通過快速的計(jì)算來獲取到元素對(duì)應(yīng)的hashCode
    • ? 均勻的分布
      • ? 哈希表中,無論是鏈地址法還是開放地址法,當(dāng)多個(gè)元素映射到同一個(gè)位置的時(shí)候,都會(huì)影響效率。
      • ? 所以,優(yōu)秀的哈希函數(shù)應(yīng)該盡可能將元素映射到不同的位置,讓元素在哈希表中均勻的分布。
快速計(jì)算 - 霍納法則
  • ? 在前面,我們計(jì)算哈希值的時(shí)候使用的方式: ?? cats = 3273+1272+20*27+17= 60337
  • ? 這種方式是直觀的計(jì)算結(jié)果,那么這種計(jì)算方式會(huì)進(jìn)行幾次乘法幾次加法呢?
    • ? 當(dāng)然,我們可能不止4項(xiàng),可能有更多項(xiàng)
    • ? 我們抽象一下,這個(gè)表達(dá)式其實(shí)是一個(gè)多項(xiàng)式: a(n)xn+a(n-1)x(n-1)+…+a(1)x+a(0)
  • ? 現(xiàn)在問題就變成了多項(xiàng)式有多少次乘法和加法:
    • ? 乘法次數(shù):n+(n-1)+…+1=n(n+1)/2
    • ? 加法次數(shù):n次
    • ? O(N2)

?????

  • ? 多項(xiàng)式的優(yōu)化:????? 霍納法則

    • ? 解決這類求值問題的高效算法–霍納法則。在中國,霍納法則也被稱為秦九韶算法。
  • ? 通過如下變換我們可以得到一種快得多的算法,即

    Pn(x)=anxn+a(n-1)x^(n-1)+…+a1x+a0=((…(((anx+an-1)x+an-2)x+an-3)…)x+a1)x+a0

    • 這種求值的方式我們稱為霍納法則。

    • ?? 當(dāng)x=4時(shí),2x4 - 3x3 + 5x2 + x - 7 的值?

      • 先將其轉(zhuǎn)換為的形式 x(x(x(2x - 3 ) + 5 ) +1 ) -7
      系數(shù) 2 -3 5 1 -7
      x=4 2 2x2-3 =5 4x5+5=25 4x25+1 = 101 4x101-7=397
  • ? 變換后,我們需要多少次乘法,多少次加法呢?

    • ? 乘法次數(shù):N次
    • ? 加法次數(shù):N次。
  • ? 如果使用大O表示時(shí)間復(fù)雜度的話,我們直接從O(N2)降到了O(N)。

均勻分布
  • ? 均勻的分布
    • ? 在設(shè)計(jì)哈希表時(shí),我們已經(jīng)有辦法處理映射到相同下標(biāo)值的情況:鏈地址法或者開放地址法。
    • ? 但是無論哪種方案,為了提供效率,最好的情況還是讓數(shù)據(jù)在哈希表中均勻分布
    • ? 因此,我們需要在使用常量的地方,盡量使用質(zhì)數(shù)。
    • ? 哪些地方我們會(huì)使用到常量呢?
  • ? ??質(zhì)數(shù)的使用
    • ? 哈希表的長度。
    • ? N次冪的底數(shù)(我們之前使用的是27)
  • ? 為什么他們使用質(zhì)數(shù),會(huì)讓哈希表分布更加均勻呢?
    • ? 質(zhì)數(shù)和其他數(shù)相乘的結(jié)果相比于其他數(shù)字更容易產(chǎn)生唯一性的結(jié)果,減少哈希沖突。
    • ? Java中的N次冪的底數(shù)選擇的是31,是經(jīng)過長期觀察分布結(jié)果得出的;
N次冪的底數(shù)
  • ? 這里采用質(zhì)數(shù)的原因是為了產(chǎn)生的數(shù)據(jù)不按照某種規(guī)律遞增。
    • ? 比如我們這里有一組數(shù)據(jù)是按照4進(jìn)行遞增的:0 4 8 12 16,將其映射到長度為8的哈希表中。
    • ? 它們的位置是多少呢?0 - 4 - 0 - 4,依次類推。
    • ? 如果我們哈希表本身不是質(zhì)數(shù),而我們遞增的數(shù)量可以使用質(zhì)數(shù),比如5,那么 0 5 10 15 20
    • ? 它們的位置是多少呢?0 - 5 - 2 - 7 - 4,依次類推。也可以盡量讓數(shù)據(jù)均勻的分布。
    • ? 我們之前使用的是27,這次可以使用一個(gè)接近的數(shù),比如31/37/41等等。一個(gè)比較常用的數(shù)是31或37。
  • ? 總之,質(zhì)數(shù)是一個(gè)非常神奇的數(shù)字。
  • ? 這里建議兩處都使用質(zhì)數(shù):
    • ? 哈希表中數(shù)組的長度。
    • ? N次冪的底數(shù)。
哈希函數(shù)的實(shí)現(xiàn)
    /**
     * 哈希函數(shù),將key映射成index
     * @param key 轉(zhuǎn)換的key
     * @param max 數(shù)組的長度(最大的數(shù)值)
     * @returns 索引值
     */
    function hashFunc(key: string, max: number): number {
    // 1.計(jì)算 hashCode: cats => 60337 (27為底)
    let hascode = 0;
    const length = key.length;
    for (let i = 0; i < length; i++) {
        //   霍納法則
        hascode = 31 * hascode + key.charCodeAt(i);
    }
    // 2.計(jì)算索引
    const index = hascode % max;
    return index;
    }

    console.log(hashFunc('abc', 7));
    console.log(hashFunc('nba', 7));
    console.log(hashFunc('cbd', 7));
    console.log(hashFunc('mba', 7));
    console.log(hashFunc('dfc', 7));
    console.log(hashFunc('rft', 7));

哈希表創(chuàng)建和操作

?? 創(chuàng)建哈希表

【數(shù)據(jù)結(jié)構(gòu)與算法——TypeScript】哈希表,數(shù)據(jù)結(jié)構(gòu)與算法,typescript,散列表,javascript,前端,筆記,數(shù)據(jù)結(jié)構(gòu),算法

  • ?? 我們這里采用鏈地址法來實(shí)現(xiàn)哈希表:

    • ?? 實(shí)現(xiàn)的哈希表(基于storage的數(shù)組)每個(gè)index對(duì)應(yīng)的是一個(gè)數(shù)組(bucket)。(當(dāng)然基于鏈表也可以。)
    • ?? bucket中存放什么呢?我們最好將key和value都放進(jìn)去,我們繼續(xù)使用一個(gè)數(shù)組。(其實(shí)其他語言使用元組更好)
    • ?? 最終我們的哈希表的數(shù)據(jù)格式是這樣:[[ [k,v],[k,v],[k,v] ] ,[ [k,v],[k,v] ],[ [k,v] ] ]
  • ??????? 代碼解析:

    • 我們定義了三個(gè)屬性:
      • ?? storage作為我們的數(shù)組,數(shù)組中存放相關(guān)的元素。
      • ?? count表示當(dāng)前已經(jīng)存在了多少數(shù)據(jù)。
      • ?? limit用于標(biāo)記數(shù)組中一共可以存放多少個(gè)元素。
      class HashTable<T = any> {
        // 創(chuàng)建一個(gè)數(shù)組,用來存儲(chǔ)鏈地址法中的鏈(數(shù)組)
        private storage: [string, T][][] = [];
        // 數(shù)組長度
        private length: number = 7;
        // 記錄已經(jīng)存放元素的個(gè)數(shù)
        private count: number = 0;
      }
    
?? 插入/修改數(shù)據(jù)put
  • ?? 哈希表的插入和修改操作是同一個(gè)函數(shù):

    • ? 因?yàn)?,?dāng)使用者傳入一個(gè)<Key,Value>時(shí)
    • ? 如果原來不存該key,那么就是插入操作。
    • ? 如果已經(jīng)存在該key,那么就是修改操作。
  • ??????? 代碼解析:

    • ?? 步驟1:根據(jù)傳入的key獲取對(duì)應(yīng)的hashCode,也就是數(shù)組的index

    • ?? 步驟2:從哈希表的index位置中取出桶(另外一個(gè)數(shù)組)

    • ?? 步驟3:查看上一步的bucket是否為null

      • 為null,表示之前在該位置沒有放置過任何的內(nèi)容,那么就新建一個(gè)數(shù)組[]
    • ?? 步驟4:查看是否之前已經(jīng)放置過key對(duì)應(yīng)的value

      • ? 如果放置過,那么就是依次替換操作,而不是插入新的數(shù)據(jù)。
      • ? 我們使用一個(gè)變量override來記錄是否是修改操作
    • ?? 步驟5:如果不是修改操作,那么插入新的數(shù)據(jù)。

      • ? 在bucket中push新的[key,value]即可。
      • ? 注意:這里需要將count+1,因?yàn)閿?shù)據(jù)增加了一項(xiàng)。
        【數(shù)據(jù)結(jié)構(gòu)與算法——TypeScript】哈希表,數(shù)據(jù)結(jié)構(gòu)與算法,typescript,散列表,javascript,前端,筆記,數(shù)據(jù)結(jié)構(gòu),算法
  • put的流程圖
    【數(shù)據(jù)結(jié)構(gòu)與算法——TypeScript】哈希表,數(shù)據(jù)結(jié)構(gòu)與算法,typescript,散列表,javascript,前端,筆記,數(shù)據(jù)結(jié)構(gòu),算法

?? 獲取數(shù)據(jù) get
  • ?? 獲取數(shù)據(jù):
    • 根據(jù)key獲取對(duì)應(yīng)的value

    • ??????? 代碼解析:

      • ?? 步驟1:根據(jù)key獲取hashCode(也就是index)
      • ?? 步驟2:根據(jù)index取出bucket。
      • ?? 步驟3:因?yàn)槿绻鸼ucket都是null,那么說明這個(gè)位置之前并沒有插入過數(shù)據(jù)。
      • ?? 步驟4:有了bucket,就遍歷,并且如果找到,就將對(duì)應(yīng)的value返回即可。
      • ?? 步驟5:沒有找到,返回null
        // 獲取數(shù)據(jù) 根據(jù)key獲取對(duì)應(yīng)的value
        get(key: string): T | undefined {
          // 1. 根據(jù)key獲取索引值
          const index = this.hashFunc(key, this.length);
          // 2. 獲取bucket
          const bucket = this.storage[index];
          if (!bucket) return undefined;
          // 3. 對(duì)比bucket 遍歷
          for (let i = 0; i < bucket.length; i++) {
            const tuple = bucket[i];
            const tupleKey = tuple[0];
            const tupleVal = tuple[1];
            if (key === tupleKey) {
              return tupleVal;
            }
          }
          return undefined;
        }
      
刪除數(shù)據(jù)
  • ?? 刪除數(shù)據(jù):

    • ? 我們根據(jù)對(duì)應(yīng)的key,刪除對(duì)應(yīng)的key/value
  • ? 代碼解析:

    • ? 思路和獲取數(shù)據(jù)相似
      delete(key: string): T | undefined {
        // 1. 根據(jù)key獲取索引值
        const index = this.hashFunc(key, this.length);
        // 2. 獲取bucket
        const bucket = this.storage[index];
        if (!bucket) return undefined;
        // 3. 對(duì)比bucket 遍歷
        for (let i = 0; i < bucket.length; i++) {
          const tuple = bucket[i];
          const tupleKey = tuple[0];
          const tupleVal = tuple[1];
          if (key === tupleKey) {
            bucket.splice(i, 1);
            this.count--;
            return tupleVal;
          }
        }
        return undefined;
      }
    

哈希表的自動(dòng)擴(kuò)容

哈希表擴(kuò)容的思想
  • ?為什么需要擴(kuò)容?
    • ? 目前,我們是將所有的數(shù)據(jù)項(xiàng)放在長度為7的數(shù)組中的。
    • ? 因?yàn)槲覀兪褂玫氖?strong>鏈地址法,loadFactor可以大于1,所以這個(gè)哈希表可以無限制的插入新數(shù)據(jù)。
    • ? 但是,隨著數(shù)據(jù)量的增多每一個(gè)index對(duì)應(yīng)的****bucket會(huì)越來越長,也就造成效率的降低。
    • ? 所以,在合適的情況對(duì)數(shù)組進(jìn)行擴(kuò)容,比如擴(kuò)容兩倍。
  • ??如何進(jìn)行擴(kuò)容?
    • ? 擴(kuò)容可以簡單的將容量增大兩倍(不是質(zhì)數(shù)嗎?質(zhì)數(shù)的問題后面再討論)
    • ? 但是這種情況下,所有的數(shù)據(jù)項(xiàng)一定要同時(shí)進(jìn)行修改(重新調(diào)用哈希函數(shù),來獲取到不同的位置)
    • ? 比如hashCode=12的數(shù)據(jù)項(xiàng),在length=8的時(shí)候,index=4。在長度為16的時(shí)候呢?index=12。
    • ? 這是一個(gè)耗時(shí)的過程,但是如果數(shù)組需要擴(kuò)容,那么這個(gè)過程是必要的。
      -?? 什么情況下擴(kuò)容呢?
    • ? 比較常見的情況是loadFactor>0.75的時(shí)候進(jìn)行擴(kuò)容。
    • ? 比如Java的哈希表就是在裝填因子大于0.75的時(shí)候,對(duì)哈希表進(jìn)行擴(kuò)容。
擴(kuò)容函數(shù)
  • ??????? 代碼解析:
    • ?? 步驟1:先將之前數(shù)組保存起來,因?yàn)槲覀兇龝?huì)兒會(huì)將storeage = []
    • ?? 步驟2:之前的屬性值需要重置。
    • ?? 步驟3:遍歷所有的數(shù)據(jù)項(xiàng),重新插入到哈希表中。
  • ?在什么時(shí)候調(diào)用擴(kuò)容方法呢?
    • ?? 在每次添加完新的數(shù)據(jù)時(shí),都進(jìn)行判斷。(也就是put方法中)
  • ?? 修改容量
  // 修改容量
  private resize(newLimit: number) {
    // 設(shè)置新的長度
    this.length = newLimit;
    // 獲取原來所有數(shù)據(jù),并且重新放入新的容量數(shù)組中
    // 對(duì)數(shù)據(jù)進(jìn)行初始化
    const oldStorage = this.storage;
    this.storage = [];
    this.count = 0;
    // 獲取原來的數(shù)據(jù),放入新的數(shù)組中
    oldStorage.forEach((bucket) => {
      if (!bucket) return;
      for (let i = 0; i < bucket.length; i++) {
        const tuple = bucket[i];
        this.put(tuple[0], tuple[1]);
      }
    });
  }
  • ?? 擴(kuò)容

      // 擴(kuò)容:發(fā)現(xiàn)loadFactor>0.75
        const loadFactor = this.count / this.length;
        if (loadFactor > 0.75) {
          this.resize(this.length * 2);
        }
    
  • ?? 縮容文章來源地址http://www.zghlxwxcb.cn/news/detail-641326.html

  // 縮容:發(fā)現(xiàn)loadFactor < 0.25
  const loadFactor = this.count / this.length;
  if (loadFactor < 0.25 && this.length > 7) {
    this.resize(Math.floor(this.length / 2));
  }        
容量質(zhì)數(shù)
  • ? 我們前面提到過,容量最好是質(zhì)數(shù)。
    • ? 雖然在鏈地址法中將容量設(shè)置為質(zhì)數(shù),沒有在開放地址法中重要,
    • ? 但是其實(shí)鏈地址法中質(zhì)數(shù)作為容量也更利于數(shù)據(jù)的均勻分布。所以,我們還是完成一下這個(gè)步驟。
  • ? 我們這里先討論一個(gè)常見的面試題,判斷一個(gè)數(shù)是質(zhì)數(shù)。
  • ? 質(zhì)數(shù)的特點(diǎn):
    • ? 質(zhì)數(shù)也稱為素?cái)?shù)。
    • ? 質(zhì)數(shù)表示大于1的自然數(shù)中,只能被1和自己整除的數(shù)。
  • ? OK,了解了這個(gè)特點(diǎn),應(yīng)該不難寫出它的算法:
  • ?? 更高效的質(zhì)數(shù)判斷
    • ? 對(duì)于每個(gè)數(shù)n,其實(shí)并不需要從2判斷到n-1
    • ? 一個(gè)數(shù)若可以進(jìn)行因數(shù)分解,那么分解時(shí)得到的兩個(gè)數(shù)一定是一個(gè)小于等于sqrt(n),一個(gè)大于等于sqrt(n)。
      • ? 注意: sqrt是square root的縮寫,表示平方根;
    • ? 比如16可以被分別。那么是2*8,2小于sqrt(16),也就是4,8大于4。而4*4都是等于sqrt(n)
    • ? 所以其實(shí)我們遍歷到等于sqrt(n)即可
  /**
   * 根據(jù)傳入的數(shù)字,判斷是否是一個(gè)質(zhì)數(shù)
   * @param num 傳入要判斷的數(shù)字
   * @returns 是否是質(zhì)數(shù)
   */
  export function isPrime(num: number): boolean {
    const sqrt = Math.sqrt(num);
    for (let i = 2; i <= sqrt; i++) {
      if (num % i === 0) return false;
    }
    return true;
  }
擴(kuò)容的質(zhì)數(shù)
  • 首先,將初始的limit為8,改成7
    • ? 前面,我們有對(duì)容量進(jìn)行擴(kuò)展,方式是:原來的容量 x 2
  • ? 比如之前的容量是7,那么擴(kuò)容后就是14。14還是一個(gè)質(zhì)數(shù)嗎?
  • ? 顯然不是,所以我們還需要一個(gè)方法,來實(shí)現(xiàn)一個(gè)新的容量為質(zhì)數(shù)的算法。
  • ? 那么我們可以封裝獲取新的容量的代碼(質(zhì)數(shù))
  isPrime(num: number): boolean {
    const sqrt = Math.sqrt(num);
    for (let i = 2; i <= sqrt; i++) {
      if (num % i === 0) return false;
    }
    return true;
  }
  private getNextPrime(num: number) {
    // 判斷容量是否為質(zhì)數(shù)
    let newPrime = num;
    while (!this.isPrime(newPrime)) {
      newPrime++;
    }
    return newPrime;
  }
  // 修改容量
  private resize(newLimit: number) {
    // 設(shè)置新的長度
    let newPrime = this.getNextPrime(newLimit);
    if (newPrime < 7) newPrime = 7;
    this.length = newPrime;

    // 獲取原來所有數(shù)據(jù),并且重新放入新的容量數(shù)組中
    // 對(duì)數(shù)據(jù)進(jìn)行初始化
    const oldStorage = this.storage;
    this.storage = [];
    this.count = 0;
    // 獲取原來的數(shù)據(jù),放入新的數(shù)組中
    oldStorage.forEach((bucket) => {
      if (!bucket) return;
      for (let i = 0; i < bucket.length; i++) {
        const tuple = bucket[i];
        this.put(tuple[0], tuple[1]);
      }
    });
  }

1 、【數(shù)據(jù)結(jié)構(gòu)與算法——TypeScript】數(shù)組、棧、隊(duì)列、鏈表

2 、【數(shù)據(jù)結(jié)構(gòu)與算法——TypeScript】算法的復(fù)雜度分析、 數(shù)組和鏈表的對(duì)比

到了這里,關(guān)于【數(shù)據(jù)結(jié)構(gòu)與算法——TypeScript】哈希表的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • 數(shù)據(jù)結(jié)構(gòu)與算法之隊(duì)列: Leetcode 621. 任務(wù)調(diào)度器 (Typescript版)

    任務(wù)調(diào)度器 https://leetcode.cn/problems/task-scheduler/ 描述 給你一個(gè)用字符數(shù)組 tasks 表示的 CPU 需要執(zhí)行的任務(wù)列表。其中每個(gè)字母表示一種不同種類的任務(wù)。任務(wù)可以以任意順序執(zhí)行,并且每個(gè)任務(wù)都可以在 1 個(gè)單位時(shí)間內(nèi)執(zhí)行完。在任何一個(gè)單位時(shí)間,CPU 可以完成一個(gè)任務(wù),或

    2024年02月02日
    瀏覽(18)
  • 數(shù)據(jù)結(jié)構(gòu)與算法之?dāng)?shù)組: Leetcode 914. 卡牌分組 (Typescript版)

    卡牌分組 https://leetcode.cn/problems/x-of-a-kind-in-a-deck-of-cards/ 描述 給定一副牌,每張牌上都寫著一個(gè)整數(shù)。 此時(shí),你需要選定一個(gè)數(shù)字 X,使我們可以將整副牌按下述規(guī)則分成 1 組或更多組: 每組都有 X 張牌。 組內(nèi)所有的牌上都寫著相同的整數(shù)。 僅當(dāng)你可選的 X = 2 時(shí)返回 true。

    2024年02月02日
    瀏覽(28)
  • 【數(shù)據(jù)結(jié)構(gòu)與算法——TypeScript】算法的復(fù)雜度分析、 數(shù)組和鏈表的對(duì)比

    【數(shù)據(jù)結(jié)構(gòu)與算法——TypeScript】算法的復(fù)雜度分析、 數(shù)組和鏈表的對(duì)比

    什么是算法復(fù)雜度(現(xiàn)實(shí)案例)? ????? 前面已經(jīng)解釋了什么是算法? 其實(shí)就是解決問題的一系列步驟操作、邏輯。 ? 對(duì)于同一個(gè)問題,我們往往有很多種解決思路和方法,也就是可以采用不同的算法。 舉個(gè)例子(現(xiàn)實(shí)例子):在一個(gè)龐大的圖書館中,我們需要找一本書

    2024年02月14日
    瀏覽(31)
  • 數(shù)據(jù)結(jié)構(gòu)與算法之字符串: Leetcode 20. 有效的括號(hào) (Typescript版)

    有效的括號(hào) https://leetcode.cn/problems/valid-parentheses/ 描述 給定一個(gè)只包括 ‘(’,‘)’,‘{’,‘}’,‘[’,‘]’ 的字符串 s ,判斷字符串是否有效。 有效字符串需滿足: 左括號(hào)必須用相同類型的右括號(hào)閉合。 左括號(hào)必須以正確的順序閉合。 每個(gè)右括號(hào)都有一個(gè)對(duì)應(yīng)的相

    2024年02月01日
    瀏覽(22)
  • 數(shù)據(jù)結(jié)構(gòu)與算法之動(dòng)態(tài)規(guī)劃: Leetcode 509. 斐波那契數(shù) (Typescript版)

    斐波那契數(shù) https://leetcode.cn/problems/fibonacci-number/ 描述 斐波那契數(shù) (通常用 F(n) 表示)形成的序列稱為 斐波那契數(shù)列 。該數(shù)列由 0 和 1 開始,后面的每一項(xiàng)數(shù)字都是前面兩項(xiàng)數(shù)字的和。也就是: 給定 n ,請(qǐng)計(jì)算 F(n) 。 示例 1 示例 2 示例 3 提示 0 = n = 30 算法實(shí)現(xiàn) 1 )方案 1 這

    2024年02月04日
    瀏覽(29)
  • 數(shù)據(jù)結(jié)構(gòu)與算法之字符串: Leetcode 696. 計(jì)數(shù)二進(jìn)制子串 (Typescript版)

    計(jì)數(shù)二進(jìn)制子串 https://leetcode.cn/problems/count-binary-substrings/ 描述 給定一個(gè)字符串 s,統(tǒng)計(jì)并返回具有相同數(shù)量 0 和 1 的非空(連續(xù))子字符串的數(shù)量,并且這些子字符串中的所有 0 和所有 1 都是成組連續(xù)的。 重復(fù)出現(xiàn)(不同位置)的子串也要統(tǒng)計(jì)它們出現(xiàn)的次數(shù)。 示例 1: 示

    2024年02月01日
    瀏覽(96)
  • 數(shù)據(jù)結(jié)構(gòu)與算法之二叉樹: Leetcode 98. 驗(yàn)證二叉搜索樹 (Typescript版)

    驗(yàn)證二叉搜索樹 https://leetcode.cn/problems/validate-binary-search-tree/ 描述 給你一個(gè)二叉樹的根節(jié)點(diǎn) root ,判斷其是否是一個(gè)有效的二叉搜索樹 有效 二叉搜索樹定義如下: 節(jié)點(diǎn)的左子樹只包含 小于 當(dāng)前節(jié)點(diǎn)的數(shù)。 節(jié)點(diǎn)的右子樹只包含 大于 當(dāng)前節(jié)點(diǎn)的數(shù)。 所有左子樹和右子樹自身

    2024年02月16日
    瀏覽(22)
  • 數(shù)據(jù)結(jié)構(gòu)與算法之二叉樹: Leetcode 111. 二叉樹的最小深度 (Typescript版)

    二叉樹的最小深度 https://leetcode.cn/problems/minimum-depth-of-binary-tree/ 描述 就 給定一個(gè)二叉樹,找出其最小深度。 最小深度是從根節(jié)點(diǎn)到最近葉子節(jié)點(diǎn)的最短路徑上的節(jié)點(diǎn)數(shù)量。 說明:葉子節(jié)點(diǎn)是指沒有子節(jié)點(diǎn)的節(jié)點(diǎn)。 示例 1 示例 2 提示 樹中節(jié)點(diǎn)數(shù)的范圍在 [0, 1 0 5 10^5 1 0 5 ] 內(nèi)

    2024年02月06日
    瀏覽(29)
  • 數(shù)據(jù)結(jié)構(gòu)與算法之堆: Leetcode 215. 數(shù)組中的第K個(gè)最大元素 (Typescript版)

    數(shù)組中的第K個(gè)最大元素 https://leetcode.cn/problems/kth-largest-element-in-an-array/ 描述 給定整數(shù)數(shù)組 nums 和整數(shù) k,請(qǐng)返回?cái)?shù)組中第 k 個(gè)最大的元素。 請(qǐng)注意,你需要找的是數(shù)組排序后的第 k 個(gè)最大的元素,而不是第 k 個(gè)不同的元素。 你必須設(shè)計(jì)并實(shí)現(xiàn)時(shí)間復(fù)雜度為 O(n) 的算法解決此

    2024年02月07日
    瀏覽(42)
  • 數(shù)據(jù)結(jié)構(gòu)與算法之字符串: Leetcode 557. 反轉(zhuǎn)字符串中的單詞 III (Typescript版)

    翻轉(zhuǎn)字符串中的單詞 III https://leetcode.cn/problems/reverse-words-in-a-string-iii/ 描述 給定一個(gè)字符串 s ,你需要反轉(zhuǎn)字符串中每個(gè)單詞的字符順序,同時(shí)仍保留空格和單詞的初始順序。 示例 1: 示例 2: 提示: 1 = s.length = 5 * 1 0 4 10^4 1 0 4 s 包含可打印的 ASCII 字符。 s 不包含任何開頭或

    2024年02月01日
    瀏覽(24)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包