『數(shù)據(jù)結(jié)構(gòu)』散列表(hash table)

哈希表 (hash table) , 可以實(shí)現(xiàn) O(1) 的 read, write, update
相對(duì)應(yīng) python 中的 dict, c語(yǔ)言中的 map

其實(shí)數(shù)組也能實(shí)現(xiàn), 只是數(shù)組用來(lái)索引的關(guān)鍵字是下標(biāo), 是整數(shù).
而哈希表就是將各種關(guān)鍵字映射到數(shù)組下標(biāo)的一種"數(shù)組"

<a id="markdown-1-關(guān)鍵字" name="1-關(guān)鍵字"></a>

1. 關(guān)鍵字

由于關(guān)鍵字是用來(lái)索引數(shù)據(jù)的, 所以要求它不能變動(dòng)(如果變動(dòng),實(shí)際上就是一個(gè)新的關(guān)鍵字插入了), 在python 中表現(xiàn)為 imutable. 常為字符串.

<a id="markdown-2-映射" name="2-映射"></a>

2. 映射

<a id="markdown-21-散列函數(shù)hash" name="21-散列函數(shù)hash"></a>

2.1. 散列函數(shù)(hash)

將關(guān)鍵字 k 進(jìn)行映射, 映射函數(shù) h, 映射后的數(shù)組地址 h(k).

<a id="markdown-211-簡(jiǎn)單一致散列" name="211-簡(jiǎn)單一致散列"></a>

2.1.1. 簡(jiǎn)單一致散列

  • 簡(jiǎn)單一致假設(shè):元素散列到每個(gè)鏈表的可能性是相同的, 且與其他已被散列的元素獨(dú)立無(wú)關(guān).
  • 簡(jiǎn)單一致散列(simple uniform hashing): 滿(mǎn)足簡(jiǎn)單一致假設(shè)的散列

好的散列函數(shù)應(yīng) 滿(mǎn)足簡(jiǎn)單一致假設(shè)
例如
\begin{aligned} &(1)\quad h(k) = k \ mod\ m \\ &(2)\quad h(k) = \lfloor {m(kA \ mod\ 1)\rfloor} = kA-\lfloor kA \rfloor \text{,\ x(0< A< 1)}\\ &\quad\text{任何 A 都使用,最佳的選擇與散列的數(shù)據(jù)特征有關(guān).}\\ &\quad\text{ Knuth 認(rèn)為,最理想的是黃金分割數(shù)}\frac{\sqrt{5} -1}{2} \approx 0.618 \end{aligned}

<a id="markdown-212-碰撞collision" name="212-碰撞collision"></a>

2.1.2. 碰撞(collision)

由于關(guān)鍵字值域大于映射后的地址值域, 所以可能出現(xiàn)兩個(gè)關(guān)鍵字有相同的映射地址

<a id="markdown-213-str2int-的方法" name="213-str2int-的方法"></a>

2.1.3. str2int 的方法

可以先用 ascii 值,然后

  • 各位相加
  • 兩位疊加
  • 循環(huán)移位
  • ...

<a id="markdown-22-直接尋址法" name="22-直接尋址法"></a>

2.2. 直接尋址法

將關(guān)鍵字直接對(duì)應(yīng)到數(shù)組地址, 即 h(k)=k

缺點(diǎn): 如果關(guān)鍵字值域范圍大, 但是數(shù)量小, 就會(huì)浪費(fèi)空間, 有可能還不能儲(chǔ)存這么大的值域范圍.

<a id="markdown-23-鏈接法" name="23-鏈接法"></a>

2.3. 鏈接法

通過(guò)鏈接法來(lái)解決碰撞

記有 m 個(gè)鏈表, n 個(gè)元素 \alpha = \frac{n}{m} 為每個(gè)鏈表的期望元素個(gè)數(shù)(長(zhǎng)度)

則查找成功,或者不成功的時(shí)間復(fù)雜度為 \Theta(1+\alpha)
如果 n=O(m), namely \quad \alpha=\frac{O(m)}{m}=O(1), 則上面的鏈接法滿(mǎn)足 O(1)的速度

<a id="markdown-231-全域散列universal-hashing" name="231-全域散列universal-hashing"></a>

2.3.1. 全域散列(universal hashing)

隨機(jī)地選擇散列函數(shù), 使之獨(dú)立于要存儲(chǔ)的關(guān)鍵字
<a id="markdown-2311-定義" name="2311-定義"></a>

2.3.1.1. 定義

設(shè)一組散列函數(shù) H=\{h_1,h_2,\ldots,h_i\}, 將 關(guān)鍵字域 U 映射到 \{0,1,\ldots,m-1\} , 全域的函數(shù)組, 滿(mǎn)足
for \ k \neq l \ \in U, h(k) = h(l), \text{這樣的 h 的個(gè)數(shù)不超過(guò)}\frac{|H|}{m}
即從 H 中任選一個(gè)散列函數(shù), 當(dāng)關(guān)鍵字不相等時(shí), 發(fā)生碰撞的概率不超過(guò) \frac{1}{m}

<a id="markdown-2312-性質(zhì)" name="2312-性質(zhì)"></a>

2.3.1.2. 性質(zhì)

對(duì)于 m 個(gè)槽位的表, 只需 \Theta(n)的期望時(shí)間來(lái)處理 n 個(gè)元素的 insert, search, delete,其中 有O(m)個(gè)insert 操作
<a id="markdown-2313-實(shí)現(xiàn)" name="2313-實(shí)現(xiàn)"></a>

2.3.1.3. 實(shí)現(xiàn)

選擇足夠大的 prime p, 記Z_p=\{0,1,\ldots,p-1\}, Z_p^{*}=\{1,\ldots,p-1\},
h_{a,b}(k) = ((ak+b)mod\ p) mod\ m
H_{p,m}=\{h_{a,b}|a\in Z_p^{*},b\in Z_p\}
<a id="markdown-24-開(kāi)放尋址法" name="24-開(kāi)放尋址法"></a>

2.4. 開(kāi)放尋址法

所有表項(xiàng)都在散列表中, 沒(méi)有鏈表.
且散列表裝載因子\alpha=\frac{n}{m}\leqslant1
這里散列函數(shù)再接受一個(gè)參數(shù), 作為探測(cè)序號(hào)
逐一試探 h(k,0),h(k,1),\ldots,h(k,m-1),這要有滿(mǎn)足的,就插入, 不再計(jì)算后面的 hash值

探測(cè)序列一般分有三種

  • 線(xiàn)性\ 0,1,\ldots,m-1
    存在一次聚集問(wèn)題
  • 二次\ 0,1,\ldots,(m-1)^2
    存在二次聚集問(wèn)題
  • 雙重探查
    h(k,i) = (h_1(k)+i*h_2(k))mod\ m
    為了能查找整個(gè)表, 即要為模 m 的完系, 則 h_2(k)要與 m 互質(zhì).
    如可以取 h_1(k) = k\ mod \ m,h_2(k) = 1+(k\ mod\ {m-1})

注意刪除時(shí), 不能直接刪除掉(如果有元素插入在其后插入時(shí)探測(cè)過(guò)此地址,刪除后就不能訪(fǎng)問(wèn)到那個(gè)元素了), 應(yīng)該 只是做個(gè)標(biāo)記為刪除

<a id="markdown-241-不成功查找的探查數(shù)的期望" name="241-不成功查找的探查數(shù)的期望"></a>

2.4.1. 不成功查找的探查數(shù)的期望

對(duì)于開(kāi)放尋址散列表,且 \alpha<1,一次不成功的查找,是這樣的: 已經(jīng)裝填了 n 個(gè), 總共有m 個(gè),則空槽有 m-n 個(gè).
不成功的探查是這樣的: 一直探查到已經(jīng)裝填的元素(但是不是要找的元素), 直到遇到?jīng)]有裝填的空槽. 所以這服從幾何分布, 即
p(\text{不成功探查})=p(\text{第一次找到空槽})=\frac{m-n}{m}

E(\text{探查數(shù)})=\frac{1}{p}\leqslant \frac{1}{1-\alpha}

<a id="markdown-2411-插入探查數(shù)的期望" name="2411-插入探查數(shù)的期望"></a>

2.4.1.1. 插入探查數(shù)的期望

所以, 插入一個(gè)關(guān)鍵字, 也最多需要 \frac{1}{1-\alpha}次, 因?yàn)椴迦脒^(guò)程就是前面都是被占用了的槽, 最后遇到一個(gè)空槽.與探查不成功是一樣的過(guò)程
<a id="markdown-2412-成功查找的探查數(shù)的期望" name="2412-成功查找的探查數(shù)的期望"></a>

2.4.1.2. 成功查找的探查數(shù)的期望

成功查找的探查過(guò)程與插入是一樣的. 所以查找關(guān)鍵字 k 相當(dāng)于 插入它, 設(shè)為第 i+1 個(gè)插入的(前面插入了i個(gè),裝載因子\alpha=\frac{i}{m}. 那么期望探查數(shù)就是
\frac{1}{1-\alpha}=\frac{1}{1-\frac{i}{m}}=\frac{m}{m-i}

則成功查找的期望探查數(shù)為
\begin{aligned} \frac{1}{n}\sum_{i=0}^{n-1}\frac{m}{m-i}=\frac{m}{n}\sum_{i=0}^{n-1}\frac{1}{m-i} &= \frac{m}{n}\sum_{i=m-n+1}^{m}\frac{1}{i}\\ &\leqslant \frac{1}{\alpha} \int_{m-n}^m\frac{1}{x}dx\\ &=\frac{1}{\alpha}ln\frac{1}{1-\alpha} \end{aligned}

代碼

github地址

class item:
    def __init__(self,key,val,nextItem=None):
        self.key = key
        self.val = val
        self.next = nextItem
    def to(self,it):
        self.next = it
    def __eq__(self,it):
        '''using  keyword <in> '''
        return self.key == it.key
    def __bool__(self):
        return self.key is not None
    def __str__(self):
        li = []
        nd = self
        while nd:
            li.append(f'({nd.key}:{nd.val})')
            nd = nd.next
        return ' -> '.join(li)
    def __repr__(self):
        return f'item({self.key},{self.val})'
class hashTable:
    def __init__(self,size=100):
        self.size = size
        self.slots=[item(None,None) for i in range(self.size)]
    def __setitem__(self,key,val):
        nd = self.slots[self.myhash(key)]
        while nd.next:
            if nd.key ==key:
                if nd.val!=val: nd.val=val
                return
            nd  = nd.next
        nd.next = item(key,val)

    def myhash(self,key):
        if isinstance(key,str):
            key = sum(ord(i) for i in key)
        if not isinstance(key,int):
            key = hash(key)
        return key % self.size
    def __iter__(self):
        '''when using keyword <in>, such as ' if key in dic',
            the dic's  __iter__ method will be called,(if hasn't, calls __getitem__
            then  ~iterate~  dic's keys to compare whether one equls to the key
        '''
        for nd in self.slots:
            nd = nd.next
            while nd :
                yield nd.key
                nd = nd.next
    def __getitem__(self,key):
        nd =self.slots[ self.myhash(key)].next
        while nd:
            if nd.key==key:
                return nd.val
            nd = nd.next
        raise Exception(f'[KeyError]: {self.__class__.__name__} has no key {key}')

    def __delitem__(self,key):
        '''note that None item and item(None,None) differ with each other,
            which means you should take care of them and correctly cop with None item
            especially when deleting items
        '''
        n = self.myhash(key)
        nd = self.slots[n].next
        if nd.key == key:
            if nd.next is None:
                self.slots[n] =  item(None,None) # be careful
            else:self.slots[n] = nd.next
            return
        while nd:
            if nd.next is None: break  # necessary
            if nd.next.key ==key:
                nd.next = nd.next.next
            nd = nd.next
    def __str__(self):
        li = ['\n\n'+'-'*5+'hashTable'+'-'*5]
        for i,nd in enumerate(self.slots):
            li.append(f'{i}: '+str(nd.next))
        return '\n'.join(li)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末嘿棘,一起剝皮案震驚了整個(gè)濱河市悯辙,隨后出現(xiàn)的幾起案子贸毕,更是在濱河造成了極大的恐慌忧侧,老刑警劉巖朗伶,帶你破解...
    沈念sama閱讀 211,817評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件芭毙,死亡現(xiàn)場(chǎng)離奇詭異携茂,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)雕蔽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)扇售,“玉大人,你說(shuō)我怎么就攤上這事困乒》∫ィ” “怎么了吱抚?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,354評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵携御,是天一觀的道長(zhǎng)鸵膏。 經(jīng)常有香客問(wèn)我谭企,道長(zhǎng)盹廷,這世上最難降的妖魔是什么久橙? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,498評(píng)論 1 284
  • 正文 為了忘掉前任渤弛,我火速辦了婚禮她肯,結(jié)果婚禮上晴氨,老公的妹妹穿的比我還像新娘腊瑟。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,600評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著该肴,像睡著了一般秦效。 火紅的嫁衣襯著肌膚如雪底扳。 梳的紋絲不亂的頭發(fā)上衷模,一...
    開(kāi)封第一講書(shū)人閱讀 49,829評(píng)論 1 290
  • 那天若皱,我揣著相機(jī)與錄音,去河邊找鬼走触。 笑死生棍,一個(gè)胖子當(dāng)著我的面吹牛韩脑,可吹牛的內(nèi)容都是我干的首量。 我是一名探鬼主播加缘,決...
    沈念sama閱讀 38,979評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼鸭叙,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了拣宏?” 一聲冷哼從身側(cè)響起沈贝,我...
    開(kāi)封第一講書(shū)人閱讀 37,722評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎勋乾,沒(méi)想到半個(gè)月后宋下,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,189評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡辑莫,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,519評(píng)論 2 327
  • 正文 我和宋清朗相戀三年学歧,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片各吨。...
    茶點(diǎn)故事閱讀 38,654評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡枝笨,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出揭蜒,到底是詐尸還是另有隱情横浑,我是刑警寧澤,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布屉更,位于F島的核電站徙融,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏偶垮。R本人自食惡果不足惜张咳,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,940評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望似舵。 院中可真熱鬧,春花似錦葱峡、人聲如沸砚哗。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,762評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)蛛芥。三九已至,卻和暖如春军援,著一層夾襖步出監(jiān)牢的瞬間仅淑,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,993評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工胸哥, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留涯竟,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,382評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像庐船,于是被迫代替她去往敵國(guó)和親银酬。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,543評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容

  • 散列表是支持 INSERT 筐钟、DELETE 和 SEARCH 的字典操作揩瞪,其是對(duì)普通數(shù)組概念的推廣,因?yàn)榭梢詫?duì)數(shù)組...
    點(diǎn)融黑幫閱讀 862評(píng)論 0 11
  • "use strict";function _classCallCheck(e,t){if(!(e instanc...
    久些閱讀 2,028評(píng)論 0 2
  • 課程介紹 先修課:概率統(tǒng)計(jì)篓冲,程序設(shè)計(jì)實(shí)習(xí)李破,集合論與圖論 后續(xù)課:算法分析與設(shè)計(jì),編譯原理壹将,操作系統(tǒng)喷屋,數(shù)據(jù)庫(kù)概論,人...
    ShellyWhen閱讀 2,265評(píng)論 0 3
  • 今天 我們拿好籃球準(zhǔn)備去上體育課的時(shí)候瞭恰,老師就進(jìn)門(mén)說(shuō)我我們只想選30個(gè)同學(xué)跟我去練啦啦操屯曹,那剩下的同學(xué)...
    祥頤閱讀 233評(píng)論 0 0
  • 繁華大都市里的樹(shù) 一棵棵金燦燦的 它們也熟了 一片一片地 連著熟 公孫樹(shù)還綠著 它們不急著熟 它們不再急著熟 20...
    淑文許閱讀 119評(píng)論 3 2