版權(quán)說(shuō)明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡(jiǎn)介
第十章散列結(jié)構(gòu)
1算法與數(shù)據(jù)結(jié)構(gòu)枚舉向量,這種向量中使用的下標(biāo)不是整數(shù),而是某枚舉類型的實(shí)例。
對(duì)枚舉向量,查詢的出發(fā)點(diǎn)是一個(gè)枚舉值,要做就是由這個(gè)枚舉值出發(fā),確定有關(guān)數(shù)據(jù)元素的存儲(chǔ)位置。2算法與數(shù)據(jù)結(jié)構(gòu)作為查詢出發(fā)點(diǎn)的值可能有各種不同情況,需要進(jìn)一步研究有關(guān)的技術(shù)和方法。
在集合與字典的一章里,已經(jīng)討論了一些結(jié)構(gòu)和技術(shù),那里使用的根本技術(shù)是關(guān)鍵碼比較。3算法與數(shù)據(jù)結(jié)構(gòu)散列結(jié)構(gòu)與枚舉向量有類似之處:這里采用的根本存儲(chǔ)結(jié)構(gòu)也是一個(gè)向量,要做的也是從關(guān)鍵碼出發(fā),直接確定數(shù)據(jù)元素的存儲(chǔ)位置。但是散列結(jié)構(gòu)更具一般性,原那么上說(shuō)它允許任意種類的關(guān)鍵碼。4算法與數(shù)據(jù)結(jié)構(gòu)設(shè)計(jì)良好的散列結(jié)構(gòu)可以具有非常高的操作效率,因此這種結(jié)構(gòu)在計(jì)算機(jī)領(lǐng)域中應(yīng)用廣泛,是一種非常有效的存儲(chǔ)和查詢結(jié)構(gòu)。
5算法與數(shù)據(jù)結(jié)構(gòu)下面將首先介紹散列結(jié)構(gòu)的根本思想,然后重點(diǎn)討論散列結(jié)構(gòu)實(shí)現(xiàn)的兩個(gè)根本要素:散列函數(shù)和“碰撞〞的各種解決技術(shù)。本章的后面局部把散列結(jié)構(gòu)作為集合和字典的重要實(shí)現(xiàn)技術(shù),討論這些方面的有關(guān)問(wèn)題。
6算法與數(shù)據(jù)結(jié)構(gòu)10.1根本概念7算法與數(shù)據(jù)結(jié)構(gòu)存儲(chǔ)方式和檢索方法:
散列函數(shù)
關(guān)鍵碼——>——>地址(固定,連續(xù)空間)8算法與數(shù)據(jù)結(jié)構(gòu)“散列〞一詞的意思是進(jìn)行某種隨機(jī)的混合,有的中文教科書(shū)或文獻(xiàn)中把它稱為“雜湊〞,對(duì)應(yīng)的英文詞是一樣的——hash。
9算法與數(shù)據(jù)結(jié)構(gòu)
散列函數(shù)是一種數(shù)據(jù)轉(zhuǎn)換函數(shù),它的參數(shù)應(yīng)該是某種查詢的依據(jù),一般稱為“關(guān)鍵詞〞或“關(guān)鍵碼〞,而函數(shù)返回的是一個(gè)無(wú)符號(hào)整數(shù)值,作為確定下標(biāo)的依據(jù)。
散列的根本想法就是構(gòu)造一個(gè)對(duì)應(yīng)關(guān)系,把每個(gè)關(guān)鍵碼對(duì)應(yīng)到一個(gè)整數(shù)〔存儲(chǔ)位置〕。這樣,如果需要存儲(chǔ)與某個(gè)關(guān)鍵碼相關(guān)的數(shù)據(jù),就將它存到與關(guān)鍵碼對(duì)應(yīng)的存儲(chǔ)位置里去;如果需要查詢與某個(gè)關(guān)鍵碼有關(guān)的信息,就到與這個(gè)關(guān)鍵碼對(duì)應(yīng)的位置中去找。
10算法與數(shù)據(jù)結(jié)構(gòu)“散列〞〔hash〕一詞的意思是進(jìn)行某種混合性的變換,從關(guān)鍵碼本身看,這種變換可能并沒(méi)有很清楚的具體意義。散列這個(gè)詞在有些中文教科書(shū)或文獻(xiàn)中被譯為“雜湊〞,散列結(jié)構(gòu)也被稱為“散列表〞、“雜湊表〞,或者按照音譯稱為“哈希表〞等。11算法與數(shù)據(jù)結(jié)構(gòu)根據(jù)散列函數(shù)概念,可以定義一種新向量類,叫作simpleHashVector類〔簡(jiǎn)單散列向量類〕。這里把它定義為向量類Vector的一個(gè)子類。簡(jiǎn)單散列向量比普通向量多一個(gè)數(shù)據(jù)成分,那就是一個(gè)指向散列函數(shù)的指針hashfun。對(duì)具體的簡(jiǎn)單散列向量而言,這個(gè)數(shù)據(jù)成分是在構(gòu)造時(shí)建立,是一種不能改變的性質(zhì)。12算法與數(shù)據(jù)結(jié)構(gòu)簡(jiǎn)單散列向量類有兩個(gè)類型參數(shù),一個(gè)是向量的索引〔關(guān)鍵碼〕類型H,另一個(gè)是向量元素類型T。
13算法與數(shù)據(jù)結(jié)構(gòu)template<classH,classT>classsimpleHashVector:publicvector<T>
{
public:
//構(gòu)造函數(shù)
simpleHashVector(intmax,int(f)(constH&));
simpleHashVector(intmax,int(f)(constH&),T&initialValue);
simpleHashVector(constsimpleHashVector<H,T>&v);
//下標(biāo)操作
T&operator[](constH&index);
private:
//記錄散列函數(shù)的函數(shù)指針
constint(hashfun)(constH&);[qzy1]
};
類10.1simpleHashVector類標(biāo)準(zhǔn)說(shuō)明
14算法與數(shù)據(jù)結(jié)構(gòu)簡(jiǎn)單散列向量類的定義與枚舉向量有相似的地方,例如對(duì)它們都可以用字符序列形式的向量下標(biāo)直接存取相關(guān)元素。15算法與數(shù)據(jù)結(jié)構(gòu)但是對(duì)于枚舉向量,作為下標(biāo)的字符序列必須是某個(gè)枚舉類型的值,下標(biāo)操作的實(shí)現(xiàn)是由系統(tǒng)自動(dòng)將枚舉值轉(zhuǎn)換成內(nèi)部整數(shù)值。而對(duì)于簡(jiǎn)單散列向量,那么可以使用真正的字符串,這種字符串可以送去打印等。16算法與數(shù)據(jù)結(jié)構(gòu)在查詢?cè)匚恢脮r(shí),散列結(jié)構(gòu)首先用給定的散列函數(shù),把H類型的關(guān)鍵碼〔可以看作是一種廣義“下標(biāo)〞〕轉(zhuǎn)換成一個(gè)適當(dāng)?shù)恼麛?shù)值,然后再把該整數(shù)轉(zhuǎn)換成對(duì)于向量合法的下標(biāo)值。后一轉(zhuǎn)換一般直接采用按向量大小取余數(shù)的方式。
17算法與數(shù)據(jù)結(jié)構(gòu)template<classH,classT>T&
hashVector<H,T>::operator[](constH&index)
{
returnvector<T>::operator[]((
hashfun)(index)%size);
}
簡(jiǎn)單散列向量類的其他成員函數(shù)都非常簡(jiǎn)單。讀者不難自己實(shí)現(xiàn)它們。
10.2散
列
函
數(shù)
18算法與數(shù)據(jù)結(jié)構(gòu)理想的散列函數(shù)是個(gè)一對(duì)一映射,它能把有關(guān)的一組關(guān)鍵碼映射到連續(xù)的一組整數(shù)值,每個(gè)關(guān)鍵碼對(duì)應(yīng)一個(gè)整數(shù)值,反之依然。19算法與數(shù)據(jù)結(jié)構(gòu)假設(shè)用h(x)代表散列函數(shù),一般來(lái)說(shuō),h應(yīng)該滿足三個(gè)條件:
h的定義域是全體可能出現(xiàn)的關(guān)鍵碼的集合,h的值域是散列表空間位置(向量下標(biāo))的集合;
希望函數(shù)值分布對(duì)于散列空間位置而言要盡量均勻;
h函數(shù)應(yīng)該是足夠簡(jiǎn)單的。
20算法與數(shù)據(jù)結(jié)構(gòu)對(duì)一般類型的關(guān)鍵碼,運(yùn)用散列函數(shù)的過(guò)程通常分成兩步:
第一步是將關(guān)鍵碼轉(zhuǎn)化成適當(dāng)?shù)恼麛?shù)值;
第二步是將得到的整數(shù)轉(zhuǎn)化為散列向量的合法下標(biāo)。
21算法與數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)第一步有很多種方法,下面列舉常見(jiàn)的幾種。
1)映射:將關(guān)鍵碼以某種數(shù)值方式映射成整數(shù)值。
對(duì)于比較復(fù)雜的映射,一般可用一個(gè)映射數(shù)組來(lái)定義。數(shù)組中的元素都在程序編制前預(yù)先給出。程序執(zhí)行時(shí),求散列值信息就是在映射數(shù)組中找到該元素,對(duì)于一組確定的關(guān)鍵碼來(lái)說(shuō),這是產(chǎn)生一個(gè)理想的散列函數(shù)的根本方法。
22算法與數(shù)據(jù)結(jié)構(gòu)2)折疊:先把關(guān)鍵碼分割成小塊,各局部的值必要時(shí)可以先散列處理,然后再混合起來(lái)便形成整個(gè)關(guān)鍵碼的散列值。
混合又有多種不同的方法,如加法、乘法或邏輯運(yùn)算。
23算法與數(shù)據(jù)結(jié)構(gòu)下面的小程序段實(shí)現(xiàn)了一種把字符串str變成整數(shù)值的過(guò)程,所用的方法便是將所有字母值加起來(lái)。
unsignedinthashval=0;
inti=str.length();
while(i>0)
hashval+=str[
i];
24算法與數(shù)據(jù)結(jié)構(gòu)3)移位:移位可以防止在折疊處理中由于運(yùn)算的可交換帶來(lái)的碰撞。
如上段程序便對(duì)apt、tap、pat會(huì)產(chǎn)生相同的散列值,而使用移位便可能減少這些碰撞。下面是參加移位處理后的程序段:
25算法與數(shù)據(jù)結(jié)構(gòu)unsignedinthashval=0;
inti=str.length();
while(i>0)
hashval=(hashval<<1)+str[
1];
26算法與數(shù)據(jù)結(jié)構(gòu)4)數(shù)字分析法:常常有這樣的情況:關(guān)鍵碼的位數(shù)比存儲(chǔ)區(qū)域的地址碼的位數(shù)多,同時(shí)其中各位的出現(xiàn)不是隨機(jī)的,在這種情況下可以通過(guò)事先對(duì)關(guān)鍵碼的各位情況進(jìn)行分析,散列時(shí)丟掉分布不均勻的位,留下分布均勻的位。
27算法與數(shù)據(jù)結(jié)構(gòu)例如,需要對(duì)以下關(guān)鍵碼集合進(jìn)行轉(zhuǎn)換,實(shí)際關(guān)鍵碼有9位數(shù)字,經(jīng)過(guò)數(shù)字分析法可以丟掉6位。
keyh(key)
000319426326
000718309709
000629443643
000758615715
000910497997
000310329329
28算法與數(shù)據(jù)結(jié)構(gòu)這個(gè)方法的缺點(diǎn)是散列函數(shù)依賴于關(guān)鍵碼集合,具體到這6個(gè)關(guān)鍵碼值,經(jīng)過(guò)分析留下第4、8、9位,假設(shè)換一個(gè)關(guān)鍵碼集合,就要重新分析,也許留下第3、6、7位。通過(guò)比照較大的實(shí)例集合進(jìn)行分析,就可能得到比較合理的結(jié)果。
29算法與數(shù)據(jù)結(jié)構(gòu)散列函數(shù)的第二步工作是將已得到的整數(shù)值轉(zhuǎn)化成散列表的合法下標(biāo)值,這時(shí)采用最多的是除余法。
所謂除余法,就是選擇一個(gè)恰當(dāng)?shù)恼麛?shù)B,用B去除前一步驟得到的整數(shù),取其余數(shù)作為散列表的下標(biāo)值。
30算法與數(shù)據(jù)結(jié)構(gòu)這個(gè)方法的關(guān)鍵是選取恰當(dāng)?shù)腂。如果B選為一個(gè)偶數(shù),那么它將總是把奇整數(shù)值轉(zhuǎn)換到奇數(shù)的下標(biāo),把偶整數(shù)值轉(zhuǎn)換到偶數(shù)的下標(biāo),這當(dāng)然不太好。選B為10的冪次就更不好,因?yàn)槟菢泳偷扔谶x擇整數(shù)的最后幾位數(shù)字作為地址。例如,選B=100,那么實(shí)際上就是取整數(shù)的最后兩位作為地址。
31算法與數(shù)據(jù)結(jié)構(gòu)在實(shí)際工作中一般認(rèn)為選取B為適當(dāng)?shù)乃財(cái)?shù)為好。例如:
B=7,13,31,61,127,251,503,1019
32算法與數(shù)據(jù)結(jié)構(gòu)除余法的地址計(jì)算公式簡(jiǎn)單。然而實(shí)踐證明,恰恰是這種簡(jiǎn)單的方法在許多情況下效果比較好。需要注意的是B的選擇,也就是散列表中桶的個(gè)數(shù)確實(shí)定,因?yàn)橛贸喾ㄋ?jì)算的結(jié)果一定在0~B-1的范圍之中。
33算法與數(shù)據(jù)結(jié)構(gòu)
同義詞
負(fù)載因子
碰撞〔開(kāi)地址,桶〕
34算法與數(shù)據(jù)結(jié)構(gòu)兩個(gè)關(guān)鍵碼散列成相同的值的現(xiàn)象被稱為“碰撞〞。碰撞的產(chǎn)生使散列的使用產(chǎn)生了問(wèn)題。
35算法與數(shù)據(jù)結(jié)構(gòu)下面我們先引入“同義詞〞的概念。設(shè)有關(guān)鍵碼k1和k2,經(jīng)過(guò)散列函數(shù)f的作用,把它們映射成相同的值,即f(k1)=f(k2),稱k1和k2在函數(shù)f的解釋下含義相同,簡(jiǎn)稱為“同義詞〞。顯然同義詞越多,碰撞的可能性就越大。
36算法與數(shù)據(jù)結(jié)構(gòu)
另外一個(gè)重要概念是“負(fù)載因子〞。簡(jiǎn)單地講,一種結(jié)構(gòu)的負(fù)載因子可以定義為這個(gè)結(jié)構(gòu)中實(shí)際元素的個(gè)數(shù)與能夠容納的元素個(gè)數(shù)之比。即:
=結(jié)構(gòu)中實(shí)際元素的個(gè)數(shù)/結(jié)構(gòu)中能夠容納的元素個(gè)數(shù)
37算法與數(shù)據(jù)結(jié)構(gòu)
10.3開(kāi)地址散列向量
38算法與數(shù)據(jù)結(jié)構(gòu)為了解決碰撞,人們提出了許多方法,本節(jié)先介紹其中的一種,稱為“開(kāi)地址法〞。用這種方法建立的散列結(jié)構(gòu)稱為“開(kāi)地址散列向量〞。
39算法與數(shù)據(jù)結(jié)構(gòu)類10.2給出openHashVector的標(biāo)準(zhǔn)說(shuō)明。為了表達(dá)簡(jiǎn)單,我們假設(shè)向量中每個(gè)元素都只包含關(guān)鍵碼自身,元素類型就是關(guān)鍵碼類型。這時(shí)模板類的參數(shù)只需要一個(gè)元素類型T〔也是關(guān)鍵碼類型〕;進(jìn)一步還假設(shè)對(duì)T類型有一個(gè)特定值,這個(gè)值不表示任何真正有意義的關(guān)鍵碼。因此,如果向量中某元素取這個(gè)值,就說(shuō)明該元素處于空閑狀態(tài)。40算法與數(shù)據(jù)結(jié)構(gòu)
template<classT>classopenHashVector
{
public:
//構(gòu)造函數(shù)
openHashVector(intmax,int(f)(constT&),T&value);
openHashVector(constopenHashVector<T>&);
//操作
intadd(constT&);
intincludes(T&);
voidremove(constT&);
private:
//數(shù)據(jù)域:散列函數(shù),基向量和向量長(zhǎng)度及初值
constint(hashfun)(constT&);
vector<T>data;
intsize;
constTspareValue;
};
類10.2openHashVector類標(biāo)準(zhǔn)說(shuō)明
41算法與數(shù)據(jù)結(jié)構(gòu)
template<classT>
openHashVector<T>::openHashVector(intmax,int(
f)(constT&),T&value)
:hashfun(f),data(max),spareValue(value)
{//設(shè)置向量大小
size=data.length();
for(unsignedi=0;i<size;i++)
data[i]=spareValue;
}
42算法與數(shù)據(jù)結(jié)構(gòu)
template<classT>intopenHashVector<T>::add(constT&v)
{//把一個(gè)確定數(shù)據(jù)記錄存放到開(kāi)地址散列向量
unsignedstart=(
hashfun)(v)%size,index=start;
//檢查位置是否已被占用
while(data[index]!=spareValue)
{
index=(index+1)%size;//考察下一個(gè)位置
if(index==start)return0;
}
data[index]=v;//發(fā)現(xiàn)空位,插入新元素
return1;
}
43算法與數(shù)據(jù)結(jié)構(gòu)
template<classT>intopenHashVector<T>::includes(T&value)
{
unsignedstart=(
hashfun)(value)%size;
unsignedindex=start;
TcurValue=data[index];
while(curValue!=value)//未找到對(duì)應(yīng)值就繼續(xù)循環(huán)
{
if(curValue==spareValue)return0;
index=(index+1)%size;
if(index==start)return0;
curValue=data[index];
}
return1;
}
44算法與數(shù)據(jù)結(jié)構(gòu)開(kāi)地址散列的思想很簡(jiǎn)單,但是它的碰撞處理方式也有些缺點(diǎn)。如果碰撞經(jīng)常發(fā)生,插入和查找的時(shí)間代價(jià)就要增加,特別是在向量中增加同義詞的時(shí)候,可能使原來(lái)并不是同義詞的關(guān)鍵碼也被牽連進(jìn)來(lái)〔這種現(xiàn)象也被稱為“堆積〞〕,進(jìn)一步增加了查找的時(shí)間。45算法與數(shù)據(jù)結(jié)構(gòu)還有另外一點(diǎn),在開(kāi)地址散列向量中的做刪除時(shí)要特別謹(jǐn)慎,如果簡(jiǎn)單地把被刪除向量元素置成初始值,結(jié)果就可能導(dǎo)致某些原有同義詞信息的喪失。這里面的具體原因和解決方法請(qǐng)讀者自己考慮。
46算法與數(shù)據(jù)結(jié)構(gòu)10.4桶散列——用桶解決碰撞
本節(jié)介紹碰撞的另一種處理方法。如果把散列表的元素?cái)U(kuò)充定義為一個(gè)能存放多個(gè)數(shù)據(jù)元素的結(jié)構(gòu),用這種結(jié)構(gòu)存放同義詞,能夠大大增強(qiáng)散列結(jié)構(gòu)處理碰撞的能力。人們把這種存放同義詞的結(jié)構(gòu)稱為“存儲(chǔ)桶〞,或簡(jiǎn)稱“桶〞。47算法與數(shù)據(jù)結(jié)構(gòu)采用桶方式實(shí)現(xiàn)散列結(jié)構(gòu),向結(jié)構(gòu)里參加一個(gè)元素的動(dòng)作就需要分兩步走:首先在散列向量中選擇應(yīng)該放入元素的桶,然后再向這個(gè)桶做元素插入。尋找和刪除元素的操作過(guò)程與插入類似。由于每個(gè)桶可以容納多個(gè)元素,碰撞問(wèn)題自然就解決了。我們把以這種方式實(shí)現(xiàn)的散列結(jié)構(gòu)稱為桶散列結(jié)構(gòu)或桶散列表。
48算法與數(shù)據(jù)結(jié)構(gòu)桶散列的抽象模板類
抽象的桶散列模板類bucketHashVector帶有三個(gè)類型參數(shù):類B是桶的實(shí)現(xiàn)類型,其實(shí)現(xiàn)細(xì)節(jié)方面的情況后面討論;類H是元素的關(guān)鍵碼類型,它也是散列函數(shù)處理的對(duì)象類型;類T是桶散列表中元素的類型。49算法與數(shù)據(jù)結(jié)構(gòu)template<classB,classH,classT>classbucketHashVector
{
public:
//構(gòu)造函數(shù)
bucketHashVector(unsignedintmax,unsignedint(f)(constH&));
virtualintisEmpty();//判斷是否為空散列表
virtualvoiddeleteAllValues();//刪除散列表里所有元素
protected:
friendclassbucketHashVectorIterator<B,H,T>;
constunsignedinttablesize;//散列表里桶個(gè)數(shù)
vector<B>buckets;//散列表用一個(gè)以桶為元素的向量
constunsignedint(hashfun)(constH&);//散列函數(shù)指針
unsignedinthash(constH&key)const;//實(shí)際使用的散列函數(shù)
//建立指定桶的遍歷器
virtualiterator<T>makeIterator(unsignedint)=0;
};
類10.3bucketHashVector類的標(biāo)準(zhǔn)說(shuō)明
50算法與數(shù)據(jù)結(jié)構(gòu)bucketHashVector類的對(duì)象有三個(gè)數(shù)據(jù)域:buckets是桶類型的向量,各個(gè)桶里存放散列表元素;整數(shù)tablesize是桶向量的元素個(gè)數(shù)(桶的個(gè)數(shù)),由于桶的數(shù)目與散列函數(shù)直接相關(guān),這里采用確定之后就不能再修改的方式,把它說(shuō)明為一個(gè)整型常量;hashfun是散列函數(shù)指針,在散列表定義時(shí)設(shè)定到選定的散列函數(shù)。這個(gè)散列函數(shù)以H類型的關(guān)鍵碼作參數(shù),計(jì)算出一個(gè)無(wú)符號(hào)的整數(shù)結(jié)果。
51算法與數(shù)據(jù)結(jié)構(gòu)在bucketHashVector類里只定義了兩個(gè)公用操作:一個(gè)操作測(cè)試散列表是否為空,另一個(gè)操作完成所有桶的去除工作,其他操作都必須在這個(gè)類的子類里定義。
上面兩個(gè)公有函數(shù)都需要遍歷所有的桶。要?jiǎng)h除散列表里所有的項(xiàng),必須去除每個(gè)桶的所有元素。
52算法與數(shù)據(jù)結(jié)構(gòu)template<classB,classH,classT>
voidbucketHashVector<B,H,T>::deleteAllValues()
{
for(inti=0;i<tablesize;i++)
buckets[i].deleteAllValues();
}
53算法與數(shù)據(jù)結(jié)構(gòu)類似的,檢查一個(gè)散列表里是否有元素就必須檢測(cè)每個(gè)桶的情況。在這些桶都為空的時(shí)候,才能說(shuō)散列表是空的。
template<classB,classH,classT>intbucketHashVector<B,H,T>::isEmpty()
{//如果任一個(gè)筒非空就返回0值
for(inti=0;i<tablesize;i++)
if(!buckeds[i].isEmpty())return0;
return1;//全都為空,散列表空
}
54算法與數(shù)據(jù)結(jié)構(gòu)函數(shù)hash定義了對(duì)關(guān)鍵碼的散列值計(jì)算。由于該函數(shù)不改變散列表本身,所以被說(shuō)明為const。函數(shù)hash通過(guò)函數(shù)指針hashfun調(diào)用桶散列表建立時(shí)設(shè)定的根本散列函數(shù),并把散列值歸入0與tablezise-1之間。函數(shù)hash在類的保護(hù)局部中說(shuō)明,在bucketHashVector的子類和友元類里都可以直接調(diào)用它。
55算法與數(shù)據(jù)結(jié)構(gòu)template<classB,classH,calssT>
unsignedintbucketHashVector<B,H,T>::hash(constH&key)const
{//返回關(guān)鍵字的散列值
return(
hashfun)(key)%tablesize;
}
56算法與數(shù)據(jù)結(jié)構(gòu)在bucketHashVector的保護(hù)局部還定義了一個(gè)名為makeIterator的純虛函數(shù),其參數(shù)是一個(gè)整數(shù)。這個(gè)函數(shù)的作用是根據(jù)參數(shù)值生成指定的桶遍歷器。由于桶類型B是模板參數(shù),所以函數(shù)的實(shí)現(xiàn)目前無(wú)法具體給出。所有bucketHashVector的子類都必須重新定義這個(gè)函數(shù)。例如下面的子類hashTree定義時(shí)就給出了它的重新定義。
57算法與數(shù)據(jù)結(jié)構(gòu)用樹(shù)作為桶的實(shí)現(xiàn)
要使用桶散列數(shù)據(jù)結(jié)構(gòu),必須建立bucketHashVector的非虛的子類,首先要做的是提供桶的實(shí)現(xiàn)類型。桶的實(shí)現(xiàn)類型應(yīng)該適合進(jìn)行插入和刪除操作,以便支持散列表的實(shí)現(xiàn)。58算法與數(shù)據(jù)結(jié)構(gòu)例如鏈接實(shí)現(xiàn)的表或者某種樹(shù)結(jié)構(gòu)。采用鏈接表實(shí)現(xiàn)桶比較簡(jiǎn)單,如果能保證散列表里每個(gè)鏈表都很短,各種操作的效率是可以保證的。定義hashList類的工作留做練習(xí)。59算法與數(shù)據(jù)結(jié)構(gòu)如果對(duì)桶中可能出現(xiàn)的元素?cái)?shù)目無(wú)法做出合理估計(jì),采用鏈表實(shí)現(xiàn)桶就可能出現(xiàn)操作效率比較低的情況,因?yàn)樵诜磸?fù)的插入操作中,散列表里某些桶〔鏈表〕可能變得很長(zhǎng),這時(shí)鏈表的線性查尋時(shí)間對(duì)操作效率就會(huì)產(chǎn)生決定性的影響。60算法與數(shù)據(jù)結(jié)構(gòu)類10.4給出了hashTree類的標(biāo)準(zhǔn)說(shuō)明,它也定義為bucketHashVector的子類。這個(gè)類中采用第七章中討論的AVL樹(shù)作為類型參數(shù)B的實(shí)現(xiàn)類型。這樣做無(wú)論桶的大小如何變化,都可以較好地保證桶內(nèi)的查尋效率。在這個(gè)類里定義了插入、刪除、包含測(cè)試三個(gè)成員函數(shù),并提供了實(shí)際的構(gòu)造函數(shù)和一個(gè)桶遍歷器生成函數(shù)。
61算法與數(shù)據(jù)結(jié)構(gòu)template<classT>classhashTree:publicbucketHashVector<avlTree<T>,T,T>
{
public:
//構(gòu)造函數(shù)
hashTree(unsignedintmax,unsignedint(f)(constT&));
voidadd(Tnewele);//增加一元素
intincludes(Tele)const;//元素包含
voidremove(Tele);//刪除一元素
protected:
virtualiterator<T>makeIterator(unsignedinti);
};
類10.4hashTree類的標(biāo)準(zhǔn)說(shuō)明〔用AVL樹(shù)作桶實(shí)現(xiàn)散列表〕
62算法與數(shù)據(jù)結(jié)構(gòu)在桶散列表里增加或刪除元素,采用的實(shí)現(xiàn)方式應(yīng)該與檢測(cè)元素是否在散列表中類似:首先調(diào)用散列函數(shù)完成桶的選擇,然后對(duì)選定的桶進(jìn)行具體操作。hashTree類里桶的類型已經(jīng)確定,這些操作的實(shí)現(xiàn)都非常簡(jiǎn)單。下面是add操作的實(shí)現(xiàn)。
63算法與數(shù)據(jù)結(jié)構(gòu)template<classT>voidhashTree<T>::add(Tnewele)
{//先找到適宜的桶,再把元素插入桶中
buckets[hash(newele)].add(newele);
}
64算法與數(shù)據(jù)結(jié)構(gòu)桶散列結(jié)構(gòu)操作時(shí)間的分析
人們經(jīng)常使用桶散列表結(jié)構(gòu),一個(gè)重要原因是在這種結(jié)構(gòu)上各種操作的執(zhí)行效率比較高。但是,另一方面,這種散列結(jié)構(gòu)操作的執(zhí)行時(shí)間估計(jì)卻比較復(fù)雜,它一方面與選定的散列函數(shù)和實(shí)際關(guān)鍵碼的分布情況有關(guān),另一方面又與結(jié)構(gòu)的具體設(shè)計(jì)有關(guān)。后者又不但受到桶類型選擇的影響,也受到桶的個(gè)數(shù)即向量大小的影響。
65算法與數(shù)據(jù)結(jié)構(gòu)首先看桶的數(shù)目對(duì)處理速度的影響。很明顯:桶散列表里的桶越少,發(fā)生碰撞的可能性就越大。對(duì)桶個(gè)數(shù)的選擇還有另一個(gè)需要考慮的因素:為使實(shí)際元素的關(guān)鍵碼能較均勻地映射到各桶里,人們經(jīng)常將桶的個(gè)數(shù)取定為某個(gè)素?cái)?shù),也用它作為散列函數(shù)的最后數(shù)值范圍。66算法與數(shù)據(jù)結(jié)構(gòu)
其次,桶類型的選擇對(duì)處理速度的影響也是顯然的。在根據(jù)關(guān)鍵碼選定某個(gè)桶后,下一步處理的速度就依賴于在桶內(nèi)處理的情況。如果采用AVL樹(shù)類型的桶,桶內(nèi)查找元素的平均時(shí)間代價(jià)是O(logk)(這里k是桶內(nèi)元素個(gè)數(shù));如果用簡(jiǎn)單鏈表結(jié)構(gòu)作為桶,桶內(nèi)查找元素就需要O(k)時(shí)間。67算法與數(shù)據(jù)結(jié)構(gòu)
在所有有關(guān)因素中,最難分析的是散列函數(shù)的影響。由于散列表的內(nèi)容是不斷變化的,插入和刪除的關(guān)鍵碼不能事先確定,對(duì)一個(gè)選定的散列函數(shù),它某時(shí)刻可能正好把所有元素均勻映射到各個(gè)桶中,而另一時(shí)刻也可能把所有元素都映射到同一個(gè)桶里。這里還有一個(gè)重要因素,那就是散列函數(shù)本身的計(jì)算復(fù)雜性。68算法與數(shù)據(jù)結(jié)構(gòu)設(shè)在一個(gè)桶散列表里有n個(gè)元素、m個(gè)桶,如果散列函數(shù)具有最理想的效果,每個(gè)桶中應(yīng)該有大約n/m個(gè)元素。假定散列函數(shù)的計(jì)算時(shí)間是常量,確定一個(gè)元素對(duì)應(yīng)的桶里只需要常數(shù)時(shí)間,那么,在這個(gè)散列表里增加一個(gè)元素,花費(fèi)的時(shí)間主要就是向一個(gè)有n/m個(gè)元素的桶中增加一個(gè)元素所需要的時(shí)間。對(duì)于有k個(gè)元素的AVL樹(shù)而言,加一個(gè)元素的時(shí)間代價(jià)是O(logk),這意味著在對(duì)應(yīng)散列結(jié)構(gòu)里,增加一個(gè)元素的時(shí)間為O(logn/m)。如果參加表中的數(shù)據(jù)元素個(gè)數(shù)有限,能夠保證n/m非常小,在一定的限制范圍里O(logn/m)幾乎就可以看作常數(shù)。
69算法與數(shù)據(jù)結(jié)構(gòu)10.5桶散列結(jié)構(gòu)的遍歷器
為了遍歷一個(gè)桶散列表,我們必須遍歷表中的每個(gè)桶,因此,對(duì)整個(gè)結(jié)構(gòu)的遍歷過(guò)程實(shí)際上包含了散列表向量本身的遍歷和各個(gè)桶的遍歷兩個(gè)層次。要實(shí)現(xiàn)這種遍歷過(guò)程,在桶散列表遍歷器里就必須必須保存兩個(gè)值,以便能刻畫(huà)遍歷過(guò)程的當(dāng)前進(jìn)展情況:一個(gè)值是當(dāng)時(shí)正在被遍歷的那個(gè)桶的下標(biāo);另一個(gè)值是個(gè)指針,它指向在桶散列表遍歷過(guò)程中動(dòng)態(tài)生成的當(dāng)前桶的遍歷器。
70算法與數(shù)據(jù)結(jié)構(gòu)
template<classB,classH,classT>classbucketHashVectorIterator:publiciterator<T>
{
public:
//構(gòu)造函數(shù)
bucketHashVectorIterator(bucketHashVector<B,H,T>&v);
bucketHashVectorIterator(constbucketHashVectorIterator&);
//遍歷器原型
virtualintinit();
virtualToperator()();
virtualintoperator!();
virtualintoperator++();
virtualvoidoperator=(Tvalue);
protected:
constbucketHashVector<B,H,T>&base;
unsignedintcurrentIndex;
iterator<T>itr;
intgetNextIterator();
};
類10.5bucketHashVectorIterator類的標(biāo)準(zhǔn)說(shuō)明
71算法與數(shù)據(jù)結(jié)構(gòu)在開(kāi)始散列表遍歷時(shí),首先必須創(chuàng)立一個(gè)桶遍歷器。由于未必每個(gè)桶都實(shí)際存有元素,所以在創(chuàng)立遍歷器時(shí)就需要進(jìn)行檢查,可能要連續(xù)檢測(cè)假設(shè)干個(gè)桶才能找到第一個(gè)非空的桶。創(chuàng)立遍歷器的工作由getNextIterator函數(shù)完成。
72算法與數(shù)據(jù)結(jié)構(gòu)template<classB,classH,classT>intbucketHashVectorIterator<B,H,T>::init()
{//初始化遍歷器
currentIndex=0;//尋找第一個(gè)桶
itr=0;
returngetNextIterator();
}
73算法與數(shù)據(jù)結(jié)構(gòu)template<classB,classH,classT>intbucketHashVectorIterator<B,H,T>::getNextIterator()
{//如果存在舊遍歷器那么刪除
if(itr!=0)deleteitr;
//尋找一個(gè)新的遍歷器
for(;currentIndex<base.tablesize;currentIndex++)
{//在當(dāng)前位置上創(chuàng)立一個(gè)新的遍歷器
itr=base.makeIterator(currentIndex);
assert(itr!=0);
//遍歷器有元素〔找到有元素的桶〕那么繼續(xù)執(zhí)行
if(itr->init())return1;
//否那么刪除它,再試下一個(gè)桶
deleteitr;
}
//沒(méi)有下一個(gè)有效遍歷器時(shí)結(jié)束
itr=0;
return0;
}
74算法與數(shù)據(jù)結(jié)構(gòu)在遞增運(yùn)算中也會(huì)遇到類似的情況,如果當(dāng)前桶的遍歷器本身還能執(zhí)行增量操作,那么這個(gè)遍歷器所訪問(wèn)的元素就是下一個(gè)元素;否那么就需要調(diào)用getNextIterator函數(shù),到隨后的桶中去尋找下一個(gè)元素。
75算法與數(shù)據(jù)結(jié)構(gòu)template<classB,clasH,classT>intbucketHashVectorIterator<B,H,T>::operator++()
{//檢查當(dāng)前遍歷器是否還能找到下一個(gè)元素
if(itr&&(itr)++)return1;
//假設(shè)不能,那么轉(zhuǎn)到下一個(gè)遍歷器
currentIndex++;
returngetNextIterator();
}
76算法與數(shù)據(jù)結(jié)構(gòu)bucketHashVectorIterator類的子類也只是對(duì)其父類的一些標(biāo)識(shí)符換名。下面給出散列樹(shù)遍歷器類的定義,其中散列樹(shù)是桶類為AVL樹(shù)的桶散列表。
77算法與數(shù)據(jù)結(jié)構(gòu)template<classT>classhashTreeIterator
:publicbucketHashVectorIterator<avlTree<T>,T,T>
{
public:
hashTreeIterator(hashTree<T>&x);
};
78算法與數(shù)據(jù)結(jié)構(gòu)template<classT>hashTreeIterator<T>::hashTreeIterator
(hashTree<T>&x):bucketHashVectorIterator<avlTree<T>,T,T>(x)
{}
79算法與數(shù)據(jù)結(jié)構(gòu)10.6用散列表實(shí)現(xiàn)集合
采用存儲(chǔ)桶方式的散列表,元素分別放在各個(gè)桶里,每桶存放假設(shè)干元素??疾煸貢r(shí)首先用散列函數(shù)確定對(duì)應(yīng)的桶,然后再進(jìn)行桶內(nèi)操作。
這種實(shí)現(xiàn)方法的一個(gè)優(yōu)點(diǎn)是能有效地實(shí)現(xiàn)集合求并與求交等運(yùn)算,對(duì)兩個(gè)集合的操作可以通過(guò)兩個(gè)集合中對(duì)應(yīng)的各對(duì)桶的操作完成。80算法與數(shù)據(jù)結(jié)構(gòu)類10.7是基于桶散列表定義的集合的標(biāo)準(zhǔn),提供的操作包括相關(guān)的插入、刪除、判斷元素包含關(guān)系等等,要實(shí)現(xiàn)這些運(yùn)算,只需把有關(guān)參數(shù)散列到適當(dāng)?shù)耐袄?,然后再在各個(gè)桶中進(jìn)行下一步的操作。
81算法與數(shù)據(jù)結(jié)構(gòu)
template<classT>classsetTable:publicbucketHashVector<setList<T>,T,T>
{
public:
//構(gòu)造函數(shù)
setTable(unsignedintmax,unsignedint(f)(constT&));
//成員函數(shù)
void add(Tval);
void differenceFrom(setTable<T>&);
int includes(Tval)const;
void intersectWith(setTable<T>&);
void remove(T val);
void unionWith(setTable<T>&);
int subset(setTable<T>&)const;
int operator==(setTable<T>&)const;
protected:
iterator<T>makeIterator(unsignedinti);
};
類10.7setTable類的標(biāo)準(zhǔn)說(shuō)明。
82算法與數(shù)據(jù)結(jié)構(gòu)這里我們沒(méi)有把setTable定義為set類的子類,主要是考慮集合操作的效率,考慮到上面提出的集合運(yùn)算實(shí)現(xiàn)方式。如果把一個(gè)桶散列表作為這個(gè)集合的數(shù)據(jù)成分,程序里就不能直接訪問(wèn)其中的各個(gè)桶了。在這里采用了繼承方式,把bucketHashVector作為基類,集合的有關(guān)操作都可以通過(guò)直接訪問(wèn)bucketHashVector的數(shù)據(jù)成分buckets而實(shí)現(xiàn)。83算法與數(shù)據(jù)結(jié)構(gòu)下面是一些操作的實(shí)現(xiàn):
template<classT>voidsetTable<T>::add(Tval)
{
buckets[hash(val)].add(val);
}
template<classT>setTable<T>::includes(Tval) const
{
returnbuckets[hash(val)].includes(val);
}
84算法與數(shù)據(jù)結(jié)構(gòu)求并集和交集的運(yùn)算,通過(guò)對(duì)桶向量進(jìn)行循環(huán),一桶一桶地實(shí)施相應(yīng)操作來(lái)完成。
template<classT>voidsetTable<T>::unionWith(setTable<T>&right)
{
assert(tablesize==right.tablesize);
for(inti=0;i<tablesize;i++)
buckets[i].unionWith(right.buckets[i]);
}
85算法與數(shù)據(jù)結(jié)構(gòu)template<classT>intsetTable<T>::subset(setTable<T>&right)
{
assert(tablesize==right.tablesize);
for(inti=0;i<tablesize;i++)
if(!buckets[i].subset(right.buckets[i]))
return0;
return1;
}
86算法與數(shù)據(jù)結(jié)構(gòu)注意,在上面實(shí)現(xiàn)中,參加運(yùn)算的兩個(gè)集合(接受者和參數(shù))必須同是setTable<T>類型。實(shí)際上,這里不僅要求它們都是桶散列表,而且要求它們具有相同的散列函數(shù),相同的桶類型和tablesize值。只有這樣才能保證操作的正確性87算法與數(shù)據(jù)結(jié)構(gòu)10.7用桶散列表實(shí)現(xiàn)字典
下面用的是繼承方式,從bucketHashVector類出發(fā)建立dictionaryTable類。dictionaryTable類從bucketHashVector類那里繼承了所有行為,定義了作為字典的必要操作。88算法與數(shù)據(jù)結(jié)構(gòu)template<classK,classV>classdictionaryTable:public
bucketHashVector<dictionaryList<K,V>,K,association<K,V>>
{
public:
//構(gòu)造函數(shù)
dictionaryTable(unsignedintmax,unsignedint(f)(constK&));
dictionaryTable(unsignedintmax,unsignedint(f)(constK&),V&);
//新的dictionaryTable類的操作
V&operator[](Kkey);
voidremoveKey(Kkey);
voidsetInitial(VinitialValue);
protected:
iterator<association<K,V>>makeIterator(unsignedinti);
}
類10.8dictionaryTable類的標(biāo)準(zhǔn)說(shuō)明
89算法與數(shù)據(jù)結(jié)構(gòu)我們?cè)谶@里沒(méi)有把dictionaryTable類定義為dictionary類的子類,考慮的問(wèn)題與前面定義setTable時(shí)類似。90算法與數(shù)據(jù)結(jié)構(gòu)
template<classK,classV>dictionaryTable<K,V>::dictionaryTable
(unsignedintmax,unsignedint(
f)(constK&),V&v)
:bucketHashVector<dictionaryList<K,V>,K,association<K,V>
>(max,f)
{//在每一個(gè)桶中置初值
setInitial(v);
}
91算法與數(shù)據(jù)結(jié)構(gòu)成員函數(shù)setInitial設(shè)置各個(gè)桶的初值。注意桶的類型是dictionaryList,實(shí)現(xiàn)這里的初值設(shè)置,只要對(duì)每個(gè)桶(buckets[i])執(zhí)行dictionaryList類的setInitial函數(shù)即可。
92算法與數(shù)據(jù)結(jié)構(gòu)template<classK,classV>voiddictionaryTable<K,V>::setInitial(Vval)
{//在每一個(gè)桶中置初值
for(inti=0;i<tablesize;i++)
buckets[i].setInitial(val);
}
93算法與數(shù)據(jù)結(jié)構(gòu)下標(biāo)操作和removeKey函數(shù)的實(shí)現(xiàn)很簡(jiǎn)單,先根據(jù)給定關(guān)鍵碼確定桶對(duì)象(dictionaryList類的對(duì)象),然后對(duì)該桶執(zhí)行下標(biāo)或removeKey操作。
94算法與數(shù)據(jù)結(jié)構(gòu)template<classK,classV>V&dictionaryTable<K,V>::operator[](Kkey)
{//尋找正確的字典,然后對(duì)字典進(jìn)行下標(biāo)操作
returnbuckets[hash(key)][key];
}
template<classK,classV>voiddictionaryTable<K,V>::removeKey(Kkey)
{//尋找正確的桶,然后刪除該元素
buckets[hash(key)].removeKey(key);
}
95算法與數(shù)據(jù)結(jié)構(gòu)注意,在dictionaryTable類中,繼承而來(lái)的bucketHashVector類的第二、第三個(gè)模板參數(shù)具有不同類型,這一點(diǎn)與前面使用bucketHashVector類的情況都不同。這里對(duì)關(guān)鍵碼進(jìn)行散列處理,而遍歷器的返回值是對(duì)關(guān)聯(lián)的引用。
96算法與數(shù)據(jù)結(jié)構(gòu)template<classK,classV>classdictionaryTableIterator
:publicbucketHashVectorIterator<dictionaryList<K,V>,K,association<K,V>
>
{
public:
//構(gòu)造函數(shù)
dictionaryTableIterator(dictionaryTable<K,V>&v);
};
類10.9dictionaryTableIterator類
97算法與數(shù)據(jù)結(jié)構(gòu)新類的構(gòu)造函數(shù)非常簡(jiǎn)單,只要將被遍歷字典(函數(shù)參數(shù)d)從原來(lái)的dictionaryTable<
溫馨提示
- 1. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒(méi)有圖紙預(yù)覽就沒(méi)有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 二手房客戶維護(hù)培訓(xùn)課件
- 食品安全課件關(guān)于野生菌
- 2025-2030安防攝像機(jī)行業(yè)市場(chǎng)深度分析及發(fā)展策略研究報(bào)告
- 2025-2030中國(guó)汽車工程塑料行業(yè)發(fā)展分析及發(fā)展趨勢(shì)研究報(bào)告
- 2025-2030中國(guó)水質(zhì)監(jiān)測(cè)行業(yè)發(fā)展建議及前景運(yùn)營(yíng)模式分析研究報(bào)告
- 2025至2030中國(guó)工業(yè)互聯(lián)網(wǎng)平臺(tái)應(yīng)用市場(chǎng)格局及商業(yè)模式研究報(bào)告
- 2025至2030中國(guó)改性樹(shù)脂產(chǎn)品差異化競(jìng)爭(zhēng)策略及客戶需求變化趨勢(shì)研究報(bào)告
- 2025-2030中國(guó)大功率半導(dǎo)體器件市場(chǎng)前景展望與重點(diǎn)企業(yè)動(dòng)態(tài)分析研究報(bào)告
- 2025至2030包裝行業(yè)數(shù)字化轉(zhuǎn)型案例研究及經(jīng)驗(yàn)借鑒與實(shí)施路徑研究報(bào)告
- 2026年陽(yáng)宗海風(fēng)景名勝區(qū)“社會(huì)救助服務(wù)人員”公開(kāi)招聘?jìng)淇碱}庫(kù)含答案詳解
- 2024年全國(guó)職業(yè)院校技能大賽(節(jié)水系統(tǒng)安裝與維護(hù)賽項(xiàng))考試題庫(kù)(含答案)
- GB/T 4706.9-2024家用和類似用途電器的安全第9部分:剃須刀、電理發(fā)剪及類似器具的特殊要求
- 2019年急性腦梗死出血轉(zhuǎn)化專家共識(shí)解讀
- 電力工程有限公司管理制度制度范本
- 科研倫理與學(xué)術(shù)規(guī)范-課后作業(yè)答案
- 安全防范系統(tǒng)安裝維護(hù)員題庫(kù)
- mbd技術(shù)體系在航空制造中的應(yīng)用
- 苗木育苗方式
- 通信原理-脈沖編碼調(diào)制(PCM)
- 省直單位公費(fèi)醫(yī)療管理辦法實(shí)施細(xì)則
- 附錄 阿特拉斯空壓機(jī)操作手冊(cè)
評(píng)論
0/150
提交評(píng)論