Hash表的擴容(轉載)

Hash表(Hash Table)

?

???? hash表實際上由size個的桶組成一個桶數組table[0...size-1] 。

當一個對象經過哈希之后。得到一個對應的value , 于是我們把這個對象放到桶table[ value ]中。當一個桶中有多個對象時。我們把桶中的對象組織成為一個鏈表。

這在沖突處理上稱之為拉鏈法。

?

負載因子(load factor)

?

??? ?如果一個hash表中桶的個數為 size , 存儲的元素個數為used .則我們稱 used / size 為負載因子loadFactor?. 一般的情況下,當loadFactor<=1時,hash表查找的期望復雜度為O(1).?因此。每次往hash表中加入元素時。我們必須保證是在loadFactor <1的情況下,才可以加入。

?

容量擴張(Expand)&?分攤轉移

?

????? 當我們加入一個新元素時。一旦loadFactor大于等于1了,我們不能單純的往hash表里邊加入元素。

由于加入完之后,loadFactor將大于1,這樣也就不能保證查找的期望時間復雜度為常數級了。這時。我們應該對桶數組進行一次容量擴張,讓size增大 。

這樣就能保證加入元素后 used / size 仍然小于等于1 , 從而保證查找的期望時間復雜度為O(1).可是。怎樣進行容量擴張呢? C++中的vector的容量擴張是一種好方法。

于是有了例如以下思路 : Hash表中每次發現loadFactor==1時,就開辟一個原來桶數組的兩倍空間(稱為新桶數組),然后把原來的桶數組中元素所有轉移過來到新的桶數組中。注意這里轉移是須要元素一個個又一次哈希到新桶中的。原因后面會講到。

????? 這樣的方法的缺點是,容量擴張是一次完畢的,期間要花非常長時間一次轉移hash表中的全部元素。這樣在hash表中loadFactor==1時。往里邊插入一個元素將會等候非常長的時間。


????redis中的dict.c中的設計思路是用兩個hash表來進行進行擴容和轉移的工作:當從第一個hash表的loadFactor=1時,假設要往字典里插入一個元素。首先為第二個hash表開辟2倍第一個hash表的容量。同一時候將第一個hash表的一個非空桶中元素所有轉移到第二個hash表中。然后把待插入元素存儲到第二個hash表里。繼續往字典里插入第二個元素,又會將第一個hash表的一個非空桶中元素所有轉移到第二個hash表中,然后把元素存儲到第二個hash表里……直到第一個hash表為空。

???? ?這樣的策略就把第一個hash表全部元素的轉移分攤為多次轉移,并且每次轉移的期望時間復雜度為O(1)。

這樣就不會出現某一次往字典中插入元素要等候非常長時間的情況了。

為了更深入的理解這個過程。先看看在dict.h中的兩個結構體:

typedef?struct?dictht?{
????dictEntry?**table;
????unsigned?long?size;
????unsigned?long?sizemask;
????unsigned?long?used;
}?dictht;

typedef?struct?dict?{
????dictType?*type;
????void?*privdata;
????dictht?ht[2];
????int?rehashidx;?/*?rehashing?not?in?progress?if?rehashidx?==?-1?*/
????int?iterators;?/*?number?of?iterators?currently?running?*/
}?dict;

dictht指的就是上面說的桶數組,size用來表示容量,一般為2^n?sizemask(一般為2^n-1,二進制表示為n1)用來對哈希值取模?, used表示hash表中存儲了多少個元素。

??????????? dict表示字典,由兩個桶數組組成。type是一些函數指針(哈希函數及keyvalue的一些處理函數)。



d->rehashidx

?

這個變量的理解非常關鍵:

d->rehashidx?表明了新元素究竟是存儲到桶數組0中。還是桶數組1中,同一時候指明了d->h[0]中究竟是哪一個桶轉移到d->h[1]中。

d->rehashidx==-1時,這時新加入的元素應該存儲在桶數組0里邊。

d->rehashidx!=-1?時,表示應該將桶數組0中的第一個非空桶元素所有轉移到桶數組1中來(最好還是稱這個過程為桶轉移,或者稱為rehash)。這個過程必須將非空桶中的元素一個個又一次哈希放到桶數組1中,由于d->h[1]->sizemask已經不同于d->h[0]->sizemask了。

這時新加入的元素應該存儲在桶數組1里邊,由于此刻的桶數組0loadFactor1?。而桶數組1loadFactor小于1?

?

當發現桶數組0中的元素所有都轉移到桶數組1中,即桶數組0為空時。釋放桶數組0的空間。把桶數組0的指針指向桶數組1。將d->rehashidx賦值為-1?,這樣桶數組1就空了,下次加入元素時。仍然加入到桶數組0中。直到桶數組0的元素個數超過桶的個數,我們又又一次開辟桶數組02倍空間給桶數組1?,同一時候改動d->rehashidx=0。這樣下次加入元素是就加入到桶數組1中去了。

?

值得注意的是。在每次刪除、查找、替換操作進行之前,依據d->rehashidx的狀態來推斷是否須要進行桶轉移。這能夠加快轉移速度。

?

以下是一份精簡的偽代碼,通過依次插入element[1..n]n個元素到dict來具體描寫敘述容量擴張及轉移的過程:

//初始化兩個hash表
d->h[0].size?=?4?;?d->h[1].used?=?0?;??//分配四個空桶
d->h[1].size?=?0?;?d->h[1].used?=?0?;??//初始化一個空表
?
for(i?=?1?;?i?<=?n?;?++?i){
??????if(?d->rehashidx?!=-1?){
??????????????????if(d->h[0]->used?!=?0){
????????????????????????????把?d->h[0]中一個非空桶元素轉移(又一次hash)到?d->h[1]中??。??
????????????????????????????//?上一步會使得:
????????????????????????????
//?d->h[0]->used?-=?轉移的元素個數?
????????????????????????????
//?d->h[1]->used?+=?轉移的元素個數?。
????????????????????????????把?element[i]?哈希到?d->h[1]中??;??//?d->h[1]->used?++?
??????????????????}else{
????????????????????????????//用桶數組1覆蓋桶數組0;?賦值前要釋放d->h[0]的空間,賦值后重置d->h[1])
????????????????????????????d->h[0]?=?d->h[1]?;?
????????????????????????????d->rehashidx?=?-1?;?
????????????????????????????把element[i]哈希到d->h[0]中;//?d->h[0]->used?++?;?
?????????????????}
??????}else?if(?d->h[0]->used?>=?d->h[0]->size?)
????????????????d->h[1]?=?new?bucket[2*d->h[0]->size?];????
????????????????//?d->h[0]->size?等于d->h[0]->size的2倍?
????????????????把element[i]哈希到d->h[1]中?;??//?d->h[1]->used?++?
????????????????d->rehashidx?=?0?;?????????????????????????????
??????}else{
????????????????把element[i]哈希到d->h[0]中;??//?d->h[0]->used?++?
??????}
}




字典的迭代器(Iterator

?

分為安全迭代器( safe Iterator )非安全迭代器

安全迭代器可以保證在迭代器未釋放之前,字典兩個hash表之間不會進行桶轉移。



桶轉移對迭代器的影響是很大的,如果一個迭代器指向d->h[0]的某個桶中的元素實體。在一次桶轉移后,這個實體被rehashd->h[1]中。

而在d->h[1]中根本不知道哪些元素被迭代器放過過,哪些沒有被訪問過,這樣有可能讓迭代器反復訪問或者缺失訪問字典中的一些元素。

所以安全迭代器可以保證不多不少不反復的訪問到全部的元素(當然在迭代過程中。不能涉及插入新元素和刪除新元素的操作)。







本文轉自mfrbuaa博客園博客,原文鏈接:http://www.cnblogs.com/mfrbuaa/p/5245064.html,如需轉載請自行聯系原作者

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/454875.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/454875.shtml
英文地址,請注明出處:http://en.pswp.cn/news/454875.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

寫在前面的一些話:《Learning OpenCV》中文版 .

2009-09-17 15:51 7578人閱讀 評論(4) 收藏 舉報 <!-- /* Font Definitions */ font-face {font-family:Helvetica; panose-1:2 11 5 4 2 2 2 2 2 4; mso-font-charset:0; mso-generic-font-family:swiss; mso-font-format:other; mso-font-pitch:variable; mso-font-sign…

獨家 | 一文讀懂自然語言處理NLP(附學習資料)

前言 自然語言處理是文本挖掘的研究領域之一&#xff0c;是人工智能和語言學領域的分支學科。在此領域中探討如何處理及運用自然語言。 對于自然語言處理的發展歷程&#xff0c;可以從哲學中的經驗主義和理性主義說起。基于統計的自然語言處理是哲學中的經驗主義&#xff0c;基…

python mock測試_使用mock測試python中的函數

對于測試覆蓋&#xff0c;我想測試文件signalC中該函數的異常塊&#xff1a;class SignalC:def readSignal(self, a):try:with open(os.path.join(self.newSubFolder, "my file" .csv), a) as csvfile:writer csv.writer(csvfile, delimiter,, quotechar|,quotingc…

如何更好閱讀源代碼 .

寫在前面的話&#xff1a;    自從我在linuxaid.com.cn上發表一些文章開始&#xff0c;就不斷的有網友發來電子郵件&#xff0c;或者是就其中某些問題進行探討&#xff0c;或者是查詢其他文章的地址&#xff08;往往這些網友看的是其他網站轉載的我的文章&#xff09;&#x…

wins系統flask綁定mysql_flask如何連接mssql,網上大多是sqlite和mysql教程?

這個居然也冒出來&#xff0c;刨墳了。我們不喜歡寫原生SQL語句&#xff0c;那個寫著費勁&#xff0c;日常開發時候&#xff0c;我們怎么CRUD數據庫呢&#xff1f;一般使用ORM&#xff0c;對象關系映射(英語&#xff1a;Object Relational Mapping&#xff0c;簡稱ORM)。主力使…

hdu 6086 -- Rikka with String(AC自動機 + 狀壓DP)

題目鏈接 Problem DescriptionAs we know, Rikka is poor at math. Yuta is worrying about this situation, so he gives Rikka some math tasks to practice. There is one of them:Yuta has n 01 strings si, and he wants to know the number of 01 antisymmetric strings …

課堂動手動腦問題

對于隨機數&#xff0c;java通過Math.random&#xff08;&#xff09;來實現&#xff0c;比如要得到一個隨機數我們可以int a&#xff1b; a&#xff08;int&#xff09;Math.random();但對于隨機數&#xff0c;它是從0到1之間的數&#xff0c;所以必須通過int把它轉為整數&…

GNU/Linux下有多少是GNU的?

導讀&#xff1a;一個葡萄牙的學生寫了一篇文章 《How much GNU is there in GNU/Linux?》由酷殼網的陳皓整理編譯為《GNU/Linux下有多少是GNU的》。這篇文章主要分布了今年4月份的Ubuntu Natty的Linux分發包。其主要是用代碼行來做的分析&#xff0c;用兩個餅圖對比分析。 內…

便攜式三星mysql_JDBC鏈接mysql - 三星藍

package chp07;importjava.sql.Connection;importjava.sql.DriverManager;importjava.sql.ResultSet;importjava.sql.SQLException;importjava.sql.Statement;public classJDBC_Test {//創建靜態全局變量staticConnection conn;staticStatement st;public static voidmain(Stri…

C++ 類、對象、class

一、對象初始化 1.不能在類聲明中對數據成員初始化&#xff0c;因為類只是一個抽象類型&#xff0c;不占存儲空間&#xff0c;無處容納數據。 2.若某類的數據成員都是public&#xff0c;則可以像結構體一樣初始化&#xff0c;如 Time t{12,21,04}&#xff1b; 若數據成員有priv…

Unity 富文本

參考鏈接&#xff1a;http://www.ceeger.com/Manual/StyledText.html 首先要說的是不僅僅ugui的text組件支持富文本&#xff0c;Debug.Log也是支持的 Debug.Log("<color#ffff00ff><b>愛生活</b></color> <color#00ffffff><b> 愛海瀾&…

Web項目替換jar包中的文件的方法

經常遇到這樣的問題&#xff0c;需要修改jar包中的方法。應該如何做&#xff1f; 1、有些很人性化的框架jar包&#xff0c;比如SpringSecurity&#xff0c;可以修改配置文件指定一個新建的類&#xff0c;讓類實現Jar包中的對應的接口就好了。 2、大部分的jar包都不會有這么方便…

程序員技術練級攻略

導讀&#xff1a;本文是由陳皓和他的一位朋友Mailper合作完成&#xff0c;原名叫《Build Your Programming Technical Skills》&#xff0c;本文分享了Mailper和作者個人的學習經歷。每個程序員都希望自己能順利的升級到高的層次&#xff0c;您不妨按照下面的方法去做。 前言 你…

Linux shell 之 提取文件名和目錄名的一些方法

很多時候在使用Linux的shell時&#xff0c;我們都需要對文件名或目錄名進行處理&#xff0c;通常的操作是由路徑中提取出文件名&#xff0c;從路徑中提取出目錄名&#xff0c;提取文件后綴名等等。例如&#xff0c;從路徑/dir1/dir2/file.txt中提取也文件名file.txt&#xff0c…

bzoj 2752: [HAOI2012]高速公路(road)

Description Y901高速公路是一條重要的交通紐帶&#xff0c;政府部門建設初期的投入以及使用期間的養護費用都不低&#xff0c;因此政府在這條高速公路上設立了許多收費站。Y901高速公路是一條由N-1段路以及N個收費站組成的東西向的鏈&#xff0c;我們按照由西向東的順序將收費…

搭建DNS主、從服務實驗

此次我們的口號是&#xff1a;簡單、有趣上手DNS服務博主是一個言出必行de好人&#xff0c;&#xff08;正經臉&#xff09;上次轉載了有關DNS的基礎介紹&#xff0c;此次我們來通過實驗搭建DNS服務器從而更好的了解DNS搭建過程如何開始&#xff0c;且聽我細細道來首先我們通常…

GDB中應該知道的幾個調試方法

七、八年前寫過一篇《用GDB調試程序》&#xff0c;于是&#xff0c;從那以后&#xff0c;很多朋友在MSN上以及給我發郵件詢問我關于GDB的問題&#xff0c;一直到今天&#xff0c;還有人在問GDB的相關問題。這么多年來&#xff0c;有一些問題是大家反復在問的&#xff0c;一方面…

長沙java技術_長沙如何提高自身的Java技術

長沙如何提高自身的Java技術&#xff1f;Java自發行二十多年來&#xff0c;一直都是開發者的寵兒&#xff0c;在編程界的位置一直十分穩固。雖然Java人才需求量大&#xff0c;薪資水平高&#xff0c;但想要用Java語言勝任企業工作不容易。比如要成為一名Java架構師&#xff0c;…

strcpy與strcat函數原型

1.strcpy函數原型 char *my_strcpy(char *dest,const char *src) //const使在函數中不能修改*src其原先的值{   char *strDest dest; //保存原始的strDest   assert((dest!NULL)&&(src!NULL)); //檢驗參數&#xff0c;…

CCF 201312-4 有趣的數

試題編號&#xff1a;201312-4試題名稱&#xff1a;有趣的數時間限制&#xff1a;1.0s內存限制&#xff1a;256.0MB問題描述&#xff1a; 問題描述我們把一個數稱為有趣的&#xff0c;當且僅當&#xff1a;1. 它的數字只包含0, 1, 2, 3&#xff0c;且這四個數字都出現過至少一次…