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

【C++】開散列哈希表封裝實(shí)現(xiàn)unordered_map和unordered_set

這篇具有很好參考價(jià)值的文章主要介紹了【C++】開散列哈希表封裝實(shí)現(xiàn)unordered_map和unordered_set。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。

在未達(dá)成目的之前,一切具有誘惑力的事物都顯得那么不堪一擊

【C++】開散列哈希表封裝實(shí)現(xiàn)unordered_map和unordered_set



一、unordered系列關(guān)聯(lián)式容器

1.
在C++98中,STL提供了底層為紅黑樹結(jié)構(gòu)的一系列關(guān)聯(lián)式容器,在查詢時(shí)效率可達(dá)到 l o g 2 N log_2 N log2?N,即最差情況下需要比較紅黑樹的高度次,當(dāng)樹中的節(jié)點(diǎn)非常多時(shí),查詢效率也不理想。
最好的查詢是,只要進(jìn)行很少的比較次數(shù)就能夠?qū)⒃卣业剑虼嗽贑++11中,STL又提供了4個(gè)unordered系列的關(guān)聯(lián)式容器,這四個(gè)容器與紅黑樹結(jié)構(gòu)的關(guān)聯(lián)式容器使用方式基本類似,只是其底層結(jié)構(gòu)不同,他們不再以紅黑樹作為底層結(jié)構(gòu),而是以掛哈希桶的哈希表作為底層結(jié)構(gòu),就是用存儲(chǔ)結(jié)點(diǎn)指針的vector來實(shí)現(xiàn)哈希表,哈希表的每個(gè)位置是一個(gè)桶,桶結(jié)構(gòu)是一個(gè)存儲(chǔ)value的單鏈表,unordered_set的桶中結(jié)點(diǎn)存儲(chǔ)的是一個(gè)key值,unordered_map的桶中結(jié)點(diǎn)存儲(chǔ)的是一個(gè)鍵值對(duì)。

2.
哈希最大的作用就是查找,如果你想進(jìn)行排序什么的,哈希迭代器遍歷的結(jié)果是無序的,只有map和set遍歷的結(jié)果才是有序的,所以哈希并不具有排序的功能,unordered_map和unordered_set僅僅只有去重的功能而已。
所以如果你想快速查找一個(gè)值,那就用哈希,如果你想排序什么的,就不要用哈希了,哈希只能幫助你快速查找,因?yàn)樗牟檎倚驶旧辖咏?shù)次,效率很快。

二、哈希函數(shù)和哈希沖突

1.通過某種映射關(guān)系得到關(guān)鍵碼在哈希表中的哈希地址,這樣的計(jì)算關(guān)系其實(shí)就是哈希函數(shù)。

  1. 直接定址法–(常用)
    取關(guān)鍵字的某個(gè)線性函數(shù)為散列地址:Hash(Key)= A*Key + B,常用的A是1,B是0。
    優(yōu)點(diǎn):簡(jiǎn)單、均勻
    缺點(diǎn):需要事先知道關(guān)鍵字的分布情況,若分布較廣,則空間消耗比較高。
    使用場(chǎng)景:適合查找比較小且連續(xù)的情況
  2. 除留余數(shù)法–(常用)
    設(shè)散列表中允許的地址數(shù)為m,取一個(gè)不大于m,但最接近或者等于m的質(zhì)數(shù)p作為除數(shù),
    按照哈希函數(shù):Hash(key) = key% p(p<=m),將關(guān)鍵碼轉(zhuǎn)換成哈希地址,一般不這么干,最常用的就是拿vector.size()作為除數(shù),每次擴(kuò)容將vector.size()擴(kuò)容二倍。但后面開散列的解決方式那里,我們會(huì)仿照庫(kù),用質(zhì)數(shù)的集合作為vector.size(),然后用其作為除數(shù)。

下面是SGI版本的質(zhì)數(shù)集合,容器每次擴(kuò)容的大小用的都是下面集合中的某一個(gè)質(zhì)數(shù)。

【C++】開散列哈希表封裝實(shí)現(xiàn)unordered_map和unordered_set
2.
當(dāng)多個(gè)關(guān)鍵碼key在通過哈希函數(shù)映射之后,得到了相同的哈希地址,也就是多個(gè)key映射到同一個(gè)位置上時(shí),這種現(xiàn)象稱為哈希沖突或哈希碰撞。解決哈希沖突的辦法一般為兩種,一種是閉散列的方式解決,即用線性探測(cè)或二次探測(cè)的方式向后尋找空的哈希位置,一種是開散列的方式解決,即將哈希沖突的元素通過單鏈表鏈接,邏輯上像哈希表掛了一個(gè)個(gè)的桶,所以這樣的解決方式也可稱為拉鏈法,或哈希桶方式。

數(shù)據(jù)集合{1, 7, 6, 4, 5, 9}
在下面的哈希表中插入一個(gè)31,則其映射位置是1,但是1位置已經(jīng)有元素1了,此時(shí)就會(huì)發(fā)生哈希沖突,那就需要向后找空的位置插入31,這就是閉散列。
【C++】開散列哈希表封裝實(shí)現(xiàn)unordered_map和unordered_set

數(shù)據(jù)集合{3, 13, 4, 7, 17, 1, 21, 6, 46, 2, 16}
發(fā)生哈希沖突的元素都被掛到原有結(jié)點(diǎn)的下面去了,邏輯上就像哈希表掛了一個(gè)個(gè)的桶。桶里面是哈希沖突元素的集合。
【C++】開散列哈希表封裝實(shí)現(xiàn)unordered_map和unordered_set

三、閉散列(你搶我的位置,我搶他的位置)

1.哈希表結(jié)構(gòu)

1.
由于這里的閉散列方法無須重點(diǎn)掌握,所以在實(shí)現(xiàn)時(shí)我們就不分key和鍵值對(duì)分別為存儲(chǔ)元素時(shí)的情況了,這里只用鍵值對(duì)作為存儲(chǔ)元素講解哈希閉散列的方法。

2.
對(duì)于閉散列結(jié)構(gòu),我們采用vector作為底層容器,用vector來存儲(chǔ)哈希結(jié)點(diǎn),哈希結(jié)點(diǎn)是一個(gè)結(jié)構(gòu)體,其中存儲(chǔ)鍵值對(duì)和狀態(tài)值,_state用于標(biāo)定哈希映射位置為空、存在、刪除三種狀態(tài)。
為了判斷什么時(shí)候進(jìn)行哈希表的擴(kuò)容,在hashTable類中多增加了一個(gè)無符號(hào)整型的_n變量,表示當(dāng)前哈希表中存儲(chǔ)數(shù)據(jù)的個(gè)數(shù),方便我們用數(shù)據(jù)個(gè)數(shù)和vector.size()作除法,看結(jié)果是否大于負(fù)載因子,如果大于則擴(kuò)容,如果不大于則繼續(xù)插入。

enum state
{
	EMPTY,
	EXIST,
	DELETE
};
template <class K, class V>
struct HashNode
{
	HashNode()
		: _state(EMPTY)
	{}
	HashNode(const pair<K, V>& kv)
		:_kv(kv)
		, _state(EMPTY)
	{}
	pair<K, V> _kv;
	enum state _state;
};
template <class K, class V, class Hash = BKDRHash<K>>
class HashTable
{
public:
	typedef HashNode<K, V> Node;
	HashTable()
		:_n(0)
	{
		_tables.resize(10);//需要給vector的size一個(gè)初始值,否則計(jì)算負(fù)載因子可能產(chǎn)生除0錯(cuò)誤
	}
	………………省略
private:
	vector<Node> _tables;
	size_t _n;//利用_n和_tables.size()去搞負(fù)載因子
};

2.Insert()

1.
閉散列的解決方式即為通過哈希函數(shù)求出key對(duì)應(yīng)的映射位置后,如果自己的映射位置已存在元素,則線性探測(cè)向后尋找空的位置進(jìn)行插入,比如下面的21的映射位置應(yīng)該是1,但是1號(hào)位有元素1了,那21只能向后探測(cè)為空的位置進(jìn)行插入,此時(shí)就會(huì)引發(fā)一個(gè)問題,21占了別的元素的映射位置,如果此時(shí)插入一個(gè)元素2,則2的映射位置也被搶了,那2就只能向后探測(cè)為空的位置了。
所以閉散列的解決方法說白了就是你搶我的位置,那我就會(huì)去搶別人的位置。

2.
我們不希望哈希表的所有空間都被占用,這樣在查找的時(shí)候,哈希表的效率會(huì)非常的低,因?yàn)樾枰闅v,所以在哈希表中存儲(chǔ)元素到達(dá)一定程度后,要對(duì)哈希表進(jìn)行擴(kuò)容,重新建立映射關(guān)系,緩解哈希沖突。

【C++】開散列哈希表封裝實(shí)現(xiàn)unordered_map和unordered_set
3.
在實(shí)現(xiàn)擴(kuò)容時(shí),可以新建立一個(gè)vector,然后遍歷原來的vector的每一個(gè)元素,重新計(jì)算每一個(gè)元素在新vector的映射關(guān)系,然后將每一個(gè)元素進(jìn)行插入,交換兩個(gè)vector,則哈希表的擴(kuò)容就完成了,但是這樣的寫法代碼有點(diǎn)冗余,因?yàn)橹匦掠?jì)算哈希映射位置的過程還需要重寫一份。
所以另一種寫法就是代碼復(fù)用,我們不再新建立vector,而是新建立一個(gè)哈希表,對(duì)新哈希表中的vector進(jìn)行擴(kuò)容,然后調(diào)用哈希表的Insert函數(shù),將原vector中的鍵值對(duì)的關(guān)鍵碼插入到新哈希表當(dāng)中,這樣就不需要自己在寫代碼,進(jìn)行代碼復(fù)用即可。最后將新哈希表中的vector和原哈希表的vector進(jìn)行swap即可,這樣就完成了原有數(shù)據(jù)到新表中的挪動(dòng),然后再插入要插入的kv即可。
在函數(shù)調(diào)用結(jié)束之后,臨時(shí)對(duì)象newHT會(huì)被銷毀,那我們還需要寫哈希表的析構(gòu)函數(shù)嗎?其實(shí)是不需要的,哈希表類默認(rèn)生成的析構(gòu)函數(shù)對(duì)內(nèi)置類型_n不處理,對(duì)自定義類型vector調(diào)用其析構(gòu)函數(shù),vector存儲(chǔ)內(nèi)容都可以看作是內(nèi)置類型,因?yàn)殒I值對(duì)說到底也就是單一的結(jié)構(gòu)體,所以vector的析構(gòu)函數(shù)直接將vector所占用的一大塊空間全部還給操作系統(tǒng)即可。那么我們就不需要自己寫哈希表的析構(gòu)函數(shù),vector會(huì)幫我們做析構(gòu)處理,并且內(nèi)置類型_n成員還不用處理。

bool Insert(const pair<K, V>& kv)
{
	if (Find(kv.first))
			return false;
	
	//大于標(biāo)定的負(fù)載因子,進(jìn)行擴(kuò)容,降低哈希沖突的概率
	if (_n * 10 / _tables.size() > 7)//可能會(huì)出現(xiàn)除0錯(cuò)誤
	{
		//舊表數(shù)據(jù),重新計(jì)算,映射到新表
		/*vector<Node> newtables;
		newtables.resize(2 * _tables.size());	*/

		HashTable<K, V, BKDRHash<K>> newHT;
		newHT._tables.resize(2 * _tables.size());
		for (auto& e : _tables)
		{
			if (e._state == EXIST)
			{
				newHT.Insert(e._kv);
				//取原表中的數(shù)據(jù)插入到新表的vector里面,鍵值對(duì)之間發(fā)生賦值重載。因?yàn)閚ewHT是新開的初始化好的哈希表
				//遞歸通常是自己調(diào)用自己,這里不是遞歸,僅僅是代碼復(fù)用而已。
			}
		}

		_tables.swap(newHT._tables);
	}

	size_t hashi = Hash()(kv.first) % _tables.size();//這里不能%capacity,某些位置不是可用的,vector[]會(huì)對(duì)下標(biāo)檢查
	while (_tables[hashi]._state == EXIST)
	{
		//線性探測(cè)
		++hashi;
		//二次探測(cè)
		//hashi = hashi + i * i;//降低沖突概率,但還是有可能會(huì)沖突,占其他位置

		hashi %= _tables.size();
	}

	/*_tables[hashi] = Node(kv);
	_tables[hashi]._state = EXIST;*/
	//在構(gòu)造新表對(duì)象時(shí),默認(rèn)構(gòu)造已經(jīng)初始化好哈希表里面的結(jié)點(diǎn)空間了,你再開空間拷貝數(shù)據(jù)浪費(fèi)。
	_tables[hashi]._kv = kv;
	_tables[hashi]._state = EXIST;
	++_n;

	return true;
}

3.Erase()(標(biāo)記的偽刪除法)

1.
大部分?jǐn)?shù)據(jù)結(jié)構(gòu)容器的刪除其實(shí)都是偽刪除或者叫做惰性刪除,因?yàn)槲覀儫o法做到釋放一大塊空間的某一部分空間,所以在數(shù)據(jù)結(jié)構(gòu)這里的刪除基本都是用標(biāo)記的偽刪除,另一種常見的方式就是,堆排序的刪除,我們當(dāng)時(shí)用的也是size標(biāo)識(shí)可用空間大小的方式,實(shí)際空間并沒有改變,我們只是改變了自己的訪問邏輯,不去訪問array的size下標(biāo)空間,但空間其實(shí)還原封不動(dòng)的在那里。

【C++】開散列哈希表封裝實(shí)現(xiàn)unordered_map和unordered_set

2.
哈希表的刪除也一樣,我們?cè)诿總€(gè)結(jié)點(diǎn)里面增加一個(gè)狀態(tài)標(biāo)記,用狀態(tài)來標(biāo)記當(dāng)前結(jié)點(diǎn)是否被刪除。如果刪除結(jié)點(diǎn)不存在,則返回false。

【C++】開散列哈希表封裝實(shí)現(xiàn)unordered_map和unordered_set

bool Erase(const K& key)
{
	Node* ret = Find(key);
	if (ret == nullptr)
		return false;

	ret->_state = DELETE;
	--_n;
	return true;
}

4.Find()

1.
查找的代碼其實(shí)比較簡(jiǎn)單,只需要先利用要查找的key值求出映射的哈希地址,如果映射地址對(duì)應(yīng)的key值不是我們要查找的key值,則說明發(fā)生了哈希沖突,那就需要映射的哈希地址向后線性探測(cè)尋找要查找的鍵值對(duì),找到后返回對(duì)應(yīng)鍵值對(duì)的地址。

2.
那什么時(shí)候查找結(jié)束呢?或者說向后循環(huán)查找的條件是什么呢?一般來說,如果映射位置的key和要查找的key不匹配,則一定說明要查找的key是沖突元素,如果這個(gè)沖突元素在哈希表里面,就比如31和21,則從1位置向后找,一定能夠找到他們。如果這個(gè)沖突元素不在哈希表里面,比如27,那從7位置開始找,7后面是空,此時(shí)就不用在向后繼續(xù)查找了,因?yàn)橹灰?7存在,那7和27之間一定是連續(xù)的,所以如果從映射位置向后找,遇到empty狀態(tài)的結(jié)點(diǎn),就不用查找了,你要查找的哈希沖突元素一定不在哈希表里面。

【C++】開散列哈希表封裝實(shí)現(xiàn)unordered_map和unordered_set

3.
在線性探測(cè)中,如果查找到尾部了,則讓hashi%=vector的size即可,讓hashi回到開頭的位置。但有一種極端特殊情況,就是邊插入邊刪除,這樣整個(gè)哈希表中的結(jié)點(diǎn)狀態(tài)有可能都是delete或exist,則在線性探測(cè)中不會(huì)遇到empty,while會(huì)陷入死循環(huán),所以在while里面多加一層判斷,如果start等于hashi,說明在哈希表中已經(jīng)線性探測(cè)一圈了,那此時(shí)就返回,因?yàn)檎伊艘蝗Χ紱]找到key,那就說明key不在哈希表里面。

4.
上面所說的是一種解決方式,但還有另外一種解決方式,由于delete和exist的存在,不是找不到empty嗎?而且delete的作用和empty的作用是一致的,那我們可以直接摒棄掉原來的枚舉值,將枚舉值縮減為empty和exist,去掉delete,在erase之后,將結(jié)點(diǎn)狀態(tài)設(shè)置為empty,這樣哈希表中的結(jié)點(diǎn)狀態(tài)只有空和存在,那就不會(huì)發(fā)生下面這樣沒有empty的情況,其實(shí)在枚舉的設(shè)計(jì)上確實(shí)有點(diǎn)問題,empty和exist已經(jīng)足夠了,完全不需要delete。(兄弟們,這樣的解決方式是錯(cuò)誤的,因?yàn)槿绻@么干了,比如一片哈希沖突,刪除中間的某個(gè)元素之后,后面的沖突元素就找不到了,因?yàn)槲覀冋J(rèn)為的是沖突元素之間一定是連續(xù)的,不能出現(xiàn)empty,delete狀態(tài)的出現(xiàn)就是為了應(yīng)對(duì)這樣的情況的)

Node* Find(const K& key)
{
	size_t hashi = Hash()(key) % _tables.size();
	size_t start = hashi;
	while (_tables[hashi]._state != EMPTY)
	{
		if (_tables[hashi]._kv.first == key && _tables[hashi]._state == EXIST)
		{
			return &_tables[hashi];
		}

		++hashi;
		hashi %= _tables.size();//防止越界
		if (start == hashi)
			break;
	}
	return nullptr;
}

5.
上面閉散列的哈希表是不用寫拷貝,賦值,析構(gòu)的,因?yàn)槟J(rèn)生成的拷貝對(duì)vector會(huì)調(diào)用他的拷貝,pair也有自己的拷貝,析構(gòu)也不用,直接釋放vector的空間即可。

5.哈希表key值不能取模無法映射的解決方法(BKDRHash)

1.
上面舉例子時(shí),鍵值對(duì)的key值都是整型,整型當(dāng)然可以完成映射,那如果是自定義類型string呢?string如何對(duì)vector的size取模呢?此時(shí)就需要仿函數(shù)來完成自定義類型轉(zhuǎn)換為整型的操作了,只有轉(zhuǎn)換為整型,我們才能取模,進(jìn)而才能完成哈希映射的工作。
對(duì)于其他類型,比如int,char,short,double等,我們直接強(qiáng)轉(zhuǎn)為size_t,這樣就可以完成哈希映射。

2.
哈希和紅黑樹都有key,兩者的key各自都有什么要求呢?紅黑樹要求key能比較大小,因?yàn)樗尨蟮膋ey到右面,小的key到左面。哈希要求key能夠取模,也就是要求key能轉(zhuǎn)成整型,因?yàn)樗瓿晒S成?/strong>。

3.
字符串轉(zhuǎn)換為整型的場(chǎng)景還是比較常見的,所以有人整理了一篇字符串哈希算法,思路就是將每一個(gè)字符對(duì)應(yīng)的ascll碼分別拆下來,每次的hash值都為上一次的hash值×131后再加上字符的ascll碼值,遍歷完字符串后,最后的hash為字符串轉(zhuǎn)成整型的結(jié)果,這樣每個(gè)字符串轉(zhuǎn)換后的整型是極大概率不重復(fù)的,是一個(gè)非常不錯(cuò)的哈希算法,被人們稱為BKDRHash。

字符串哈希算法

template <class K>
struct BKDRHash
{
	size_t operator()(const K& key)
	{
		return (size_t)key;//只要這個(gè)地方能轉(zhuǎn)成整型,那就可以映射,指針浮點(diǎn)數(shù)負(fù)數(shù)都可以,但string不行
	}
};
template <>
struct BKDRHash<string>
{
	size_t operator()(const string& key)
	{
		//return key[0];//字符串第一個(gè)字符是整型,那就可以整型提升,只要是個(gè)整型能進(jìn)行%模運(yùn)算,完成映射即可。

		size_t hash = 0;
		for (auto ch : key)
		{
			hash = hash * 131 + ch;
		}
		return hash;
	}
};

四、開散列(掛哈希桶的方式)

1.哈希表結(jié)構(gòu) && 構(gòu)造和析構(gòu)函數(shù)

1.
開散列的哈希表是最常用的方式,庫(kù)里面的unordered_map和unordered_set用的也是哈希桶的方式實(shí)現(xiàn)的,我們模擬實(shí)現(xiàn)的哈希桶也仿照庫(kù)實(shí)現(xiàn),哈希結(jié)點(diǎn)node里面存儲(chǔ)鍵值對(duì)和下一個(gè)結(jié)點(diǎn)指針。
在哈希表的模板參數(shù)中,也多加了一個(gè)缺省仿函數(shù)類的參數(shù),也就是Hash,因?yàn)槲覀冃枰狧ash的仿函數(shù)對(duì)象或匿名構(gòu)造,將key轉(zhuǎn)成整型。

【C++】開散列哈希表封裝實(shí)現(xiàn)unordered_map和unordered_set

template <class K, class V>
struct hashNode
{
	hashNode(const pair<K,V>& kv)
		:_kv(kv)
		,_next(nullptr)
	{}

	pair<K, V> _kv;
	hashNode<K, V>* _next;
};
template <class K, class V, class Hash = BKDRHash<K>>
class hashTable
{
public:
	typedef hashNode<K, V> Node;

	…………省略
private:
	vector<Node*> _table;
	size_t _n;
};

2.
對(duì)于哈希桶,我們必須寫出析構(gòu)函數(shù),因?yàn)榫幾g器默認(rèn)生成的析構(gòu)函數(shù)會(huì)調(diào)用vector的析構(gòu),而vector的析構(gòu)僅僅只能將自己的空間還給操作系統(tǒng),如果某些節(jié)點(diǎn)指針指向了具體的節(jié)點(diǎn),則只歸還vector的空間是不夠的,還需要?dú)w還那些申請(qǐng)的節(jié)點(diǎn)空間。
所以需要遍歷每一個(gè)哈希桶,將每一個(gè)桶里面的節(jié)點(diǎn)都還給操作系統(tǒng),這里就用到單鏈表的節(jié)點(diǎn)刪除的知識(shí)了,在刪除前需要保留下一個(gè)位置,要不然delete歸還空間之后就找不到下一個(gè)節(jié)點(diǎn)的位置了。

hashTable()
	:_n(0)
{
	_table.resize(10);
}
~hashTable()
{
	for (size_t i = 0; i < _table.size(); i++)
	{
		Node* cur = _table[i];
		while (cur)
		{
			Node* next = cur->_next;
			delete cur;//把桶中所有結(jié)點(diǎn)空間的使用權(quán)還給操作系統(tǒng),這就是析構(gòu)函數(shù)的作用
			cur = next;
		}
		_table[i] = nullptr;
	}
}

2.Insert()(單鏈表的頭插)

1.
我下面畫的圖只是想說明一下哈希桶的邏輯結(jié)構(gòu)和擴(kuò)容之后緩解哈希沖突的場(chǎng)景,但實(shí)際在插入節(jié)點(diǎn)時(shí)并不是像我下面畫的那樣對(duì)單鏈表進(jìn)行尾插,因?yàn)槲膊暹€需要找尾,那就需要遍歷桶,這樣的效率太低,并且桶中也不要求次序什么的,所以我們直接進(jìn)行頭插即可,頭插的效率很高,因?yàn)橛成湔业焦5刂分蠹纯蛇M(jìn)行頭插。

【C++】開散列哈希表封裝實(shí)現(xiàn)unordered_map和unordered_set
2.
哈希桶的負(fù)載因子,官方默認(rèn)值為1.0,那就是_n和vector.size()相等的時(shí)候進(jìn)行擴(kuò)容,擴(kuò)容的目的還是重新建立映射關(guān)系,緩解哈希沖突,因?yàn)槿绻骋粋€(gè)哈希桶的結(jié)點(diǎn)個(gè)數(shù)過多,在哈希映射之后還需要遍歷哈希桶尋找結(jié)點(diǎn),會(huì)降低哈希查找的效率,所以擴(kuò)容就是多增加哈希桶的個(gè)數(shù),減少平均哈希桶中結(jié)點(diǎn)的個(gè)數(shù),提高哈希查找的效率。

【C++】開散列哈希表封裝實(shí)現(xiàn)unordered_map和unordered_set
3.
擴(kuò)容這里的思路和閉散列的哈希表比較相似,如果我們遍歷原有結(jié)點(diǎn)的數(shù)據(jù),將每個(gè)結(jié)點(diǎn)的數(shù)據(jù)重新new一個(gè)結(jié)點(diǎn)出來,然后插入到新的vector里面,或者是代碼復(fù)用的方式進(jìn)行插入,這兩種都可以,但是效率太低了,上面所說的兩種代碼寫法都是新new結(jié)點(diǎn)插入到新表的,如果有1w個(gè)結(jié)點(diǎn)呢?我們平白無故的拷貝出來1w個(gè)結(jié)點(diǎn),然后臨時(shí)對(duì)象銷毀的時(shí)候又析構(gòu)1w個(gè)結(jié)點(diǎn)?這不是冤大頭么。為什么不將原來的結(jié)點(diǎn)鏈接到新的vector上面呢?

4.
所以另一種寫法就是遍歷原表的每個(gè)結(jié)點(diǎn)指針,將每個(gè)指針指向結(jié)點(diǎn)的key重新計(jì)算哈希映射關(guān)系,頭插到新的vector里面,在每完成一個(gè)桶的重新映射關(guān)系后,將原vector中的桶位置的指針置為空,否則析構(gòu)的時(shí)候,結(jié)點(diǎn)會(huì)被析構(gòu)兩遍。等到原表的所有結(jié)點(diǎn)遍歷完之后,將新的vector和原來的vector一交換即可,臨時(shí)對(duì)象_newtable在離開函數(shù)棧幀時(shí)會(huì)被銷毀,調(diào)用vector的默認(rèn)析構(gòu)完成空間的歸還即可。

5.
研究表明,每次除留余數(shù)法最好模一個(gè)素?cái)?shù),這會(huì)大概率降低哈希沖突的可能性。所以我們下面的擴(kuò)容大小每次挑選小于2倍的最大素?cái)?shù)作為擴(kuò)容后的vector大小,這里復(fù)用了一下stl庫(kù)里面的素?cái)?shù)表。

inline unsigned long __stl_next_prime(unsigned long n)
{
	static const int __stl_num_primes = 28;
	static const unsigned long __stl_prime_list[__stl_num_primes] =
	{
	  53,         97,         193,       389,       769,
	  1543,       3079,       6151,      12289,     24593,
	  49157,      98317,      196613,    393241,    786433,
	  1572869,    3145739,    6291469,   12582917,  25165843,
	  50331653,   100663319,  201326611, 402653189, 805306457,
	  1610612741, 3221225473, 4294967291
	};

	for (size_t i = 0; i < __stl_num_primes; i++)
	{
		if (__stl_prime_list[i] > n)
		{
			return __stl_prime_list[i];
		}
	}

	return __stl_prime_list[__stl_num_primes - 1];
}
bool Insert(const pair<K, V>& kv)
{
	if (Find(kv.first))//不允許重復(fù)元素
		return false;

	//負(fù)載因子控制在1,超過就擴(kuò)容
	if (_n == _table.size())
	{
		/*hashTable<K, V, Hash> _newHT;
		_newHT._table.resize(2 * _table.size());*/

		//for (int hashi = 0; hashi < _table.size(); hashi++)
		//{
		//	Node* cur = _table[hashi];
		//	while (cur)
		//	{
		//		_newtable.Insert(cur);//需要重新改變映射關(guān)系
		//		cur = cur->_next;
		//	}
		//}
		//_table.swap(_newHT._table);
		
		//上面是冤大頭的做法,下面是正常的做法。
		vector<Node*> _newtable;
		_newtable.resize(__stl_next_prime(_table.size()), nullptr);//resize開空間后,默認(rèn)值為Node*()的構(gòu)造,我們也可以自己寫
		for (int i = 0; i < _table.size(); i++)
		{
			Node* cur = _table[i];
			while (cur)
			{
				Node* next = cur->_next;
				size_t hashi = Hash()(cur->_kv.first) % _newtable.size();
				cur->_next = _newtable[hashi];
				_newtable[hashi] = cur;

				cur = next;
			}

			_table[i] = nullptr;
		}
		_table.swap(_newtable);
	}

	size_t hashi = Hash()(kv.first) % _table.size();
	Node* newnode = new Node(kv);
	newnode->_next = _table[hashi];//newnode的next指向當(dāng)前表哈希映射位置的結(jié)點(diǎn)地址
	_table[hashi] = newnode;//讓newnode做頭

	++_n;
	return true;
}

3.Erase()(歸還結(jié)點(diǎn)空間的使用權(quán))

1.
哈希桶的erase其實(shí)就是單鏈表結(jié)點(diǎn)的刪除,如果是頭刪,那就是下一個(gè)指針作頭,如果是中間刪除,則記錄前一個(gè)結(jié)點(diǎn)位置,讓前一個(gè)結(jié)點(diǎn)的next指向刪除結(jié)點(diǎn)的next。
然后歸還結(jié)點(diǎn)空間的使用權(quán),即為delete結(jié)點(diǎn)指針。

bool Erase(const K& key)
{
	Node* ret = Find(key);
	if (!ret)
		return false;

	size_t hashi = Hash()(key) % _table.size();
	Node* cur = _table[hashi];
	if (cur->_kv.first == key)//頭刪
	{
		_table[hashi] = cur->_next;
		delete cur;
		cur = nullptr;
	}
	else//中間刪除
	{
		while (cur)
		{
			Node* prev = cur;
			cur = cur->_next;
			if (cur->_kv.first == key)
			{
				prev->_next = cur->_next;
				delete cur;
				cur = nullptr;
			}
		}
	}
	--_n;
	return true;
}

4.Find()

1.
哈希桶的查找和閉散列的哈希表很相似,先通過key找到映射的哈希桶,然后去對(duì)應(yīng)的哈希桶里面找查找的結(jié)點(diǎn)即可,找到返回結(jié)點(diǎn)地址,未找到返回nullptr即可。

Node* Find(const K& key)
{
	size_t hashi = Hash()(key) % _table.size();
	Node* cur = _table[hashi];
	while (cur)
	{
		if (cur->_kv.first == key)
			return cur;
		cur = cur->_next;
	}
	return nullptr;
}

五、封裝實(shí)現(xiàn)unordered系列容器(不一樣的const迭代器)

1.普通迭代器(單向迭代器)

1.
封裝實(shí)現(xiàn)unordered系列容器的insert,find,erase等接口并不是什么難事,直接調(diào)用開散列哈希表的接口即可,而封裝的主要關(guān)鍵點(diǎn)其實(shí)是實(shí)現(xiàn)容器的迭代器操作,只要實(shí)現(xiàn)了迭代器的操作,那我們自己封裝的unordered系列容器基本上就能跑起來了。

2.
如果要實(shí)現(xiàn)迭代器++的操作,如果我們只有結(jié)點(diǎn)的指針是無法完成迭代器++的,因?yàn)槿绻闅v所有的哈希桶的結(jié)點(diǎn),則必須需要哈希表本身,只有這樣才能確定下一個(gè)哈希桶的位置,所以開散列哈希表的迭代器需要多封裝一個(gè)哈希表指針,通過哈希表指針和哈希結(jié)點(diǎn)的指針,就可以訪問整個(gè)表里面所有的結(jié)點(diǎn)了。

3.
迭代器的++就是看當(dāng)前指針的下一個(gè)是否為空,如果為空說明當(dāng)前哈希桶里面只有他一個(gè)結(jié)點(diǎn),那就需要尋找下一個(gè)哈希桶,將結(jié)點(diǎn)指針的指向移動(dòng)到下一個(gè)哈希桶的結(jié)點(diǎn)指針處,如果向后找的過程中結(jié)點(diǎn)指針都是nullptr,那就是沒有哈希桶,我們將_node置為空即可,說明此時(shí)迭代器已經(jīng)到end()的位置了,不能繼續(xù)遍歷下去了。

需要額外多說一點(diǎn)的是,由于迭代器內(nèi)部用了哈希表指針,所以需要在迭代器的前面加上哈希表的模板聲明。而且在迭代器內(nèi)部還訪問了哈希表的私有成員_table,則需要在哈希表里面進(jìn)行友元聲明,將迭代器模板聲明為哈希表的友元。
【C++】開散列哈希表封裝實(shí)現(xiàn)unordered_map和unordered_set

//前置聲明
template <class K, class T, class Hash, class KeyOfT>
class hashTable;

template <class K, class T, class Hash, class KeyOfT>
struct __HTIterator
{
	typedef hashNode<T> Node;
	typedef hashTable<K, T, Hash, KeyOfT> HT;
	typedef __HTIterator<K, T, Hash, KeyOfT> Self;
	Node* _node;
	HT* _ht;

	__HTIterator(Node* node, HT* ht)
		:_node(node)
		,_ht(ht)
	{}

	Self& operator++()
	{
		if (_node->_next)
		{
			_node = _node->_next;
		}
		else
		{
			//當(dāng)前桶走完了,要去哈希表里面找下一個(gè)桶
			size_t hashi = Hash()(KeyOfT()(_node->_data)) % _ht->_table.size();
			hashi++;

			while (hashi != _ht->_table.size() && _ht->_table[hashi] == nullptr)
			{
				hashi++;
			}

			if (hashi == _ht->_table.size())
				_node = nullptr;
			else
				_node = _ht->_table[hashi];
		}

		return *this;
	}

	T& operator->()
	{
		return &_node->_data;
	}
	T* operator*()
	{
		return _node->_data;
	}

	bool operator!=(const Self& it)const
	{
		return _node != it._node;
	}
	bool operator==(const Self& it)const
	{
		return _node == it._node;
	}
};

2.為什么hashTable的const迭代器要重新寫一個(gè)類?

2.1 庫(kù)里面是怎么做的?

1.
我們可以先來看一下常規(guī)的迭代器操作,比如紅黑樹的迭代器,他的const迭代器并沒有重寫一個(gè)類,而是直接增加模板參數(shù)Ref和Ptr來完成對(duì)應(yīng)普通引用和常引用的值的返回。
并且支持普通迭代器構(gòu)造const迭代器的操作,實(shí)際上STL的所有容器在實(shí)現(xiàn)迭代器的時(shí)候,都會(huì)用下面的方式來支持普通迭代器構(gòu)造const迭代器,如果是普通迭代器調(diào)用,那這里就是普通和普通之間的拷貝,沒啥用因?yàn)榫幾g器也支持這樣的操作,但下面那段代碼目的不是支持普通迭代器之間的拷貝,而是支持普通到const迭代器的構(gòu)造操作。

【C++】開散列哈希表封裝實(shí)現(xiàn)unordered_map和unordered_set

2.
但是哈希表的迭代器卻沒有通過增加模板參數(shù)來解決,而是重寫了一個(gè)struct __hashtable_const_iterator { }類,以這樣的方式來實(shí)現(xiàn)unordered_map和unordered_set的const迭代器操作。

【C++】開散列哈希表封裝實(shí)現(xiàn)unordered_map和unordered_set

2.2 不能通過增加模板參數(shù)解決嗎?(權(quán)限不能放大)

1.
其實(shí)能否通過增加模板參數(shù)解決const迭代器主要取決于迭代器類中的構(gòu)造函數(shù),之前能通過增加模板參數(shù)解決是因?yàn)闊o論是構(gòu)造const迭代器還是構(gòu)造普通迭代器,我們傳給構(gòu)造函數(shù)的指針都是普通指針,當(dāng)然可以構(gòu)造出普通迭代器和const迭代器了,因?yàn)閮蓚€(gè)迭代器的差別主要是*和→重載的返回值是可修改的還是不可修改的,這個(gè)并不難解決。

2.
而現(xiàn)在不一樣了,哈希迭代器傳的指針不是普通指針了,而是const指針,當(dāng)調(diào)用begin()或end()函數(shù)的對(duì)象是const對(duì)象時(shí),this指針指向的內(nèi)容是不可修改的,并且由于對(duì)象是const對(duì)象,vector的[ ]返回的也是const引用,而vector存儲(chǔ)的是指針,那就說明哈希node結(jié)點(diǎn)指針指向的內(nèi)容也是不可修改的。
如果我們此時(shí)用這兩個(gè)指針去構(gòu)造const迭代器,而哈希迭代器類成員變量是兩個(gè)普通指針,那在構(gòu)造函數(shù)處就會(huì)發(fā)生const指針拷貝給普通指針的情況,此時(shí)權(quán)限會(huì)放大,所以如果你用增加模板參數(shù)來實(shí)現(xiàn)const迭代器,則編譯器一定會(huì)報(bào)錯(cuò),因?yàn)橹羔樅鸵迷谫x值時(shí),權(quán)限只能平移和縮小,不能放大。

3.
所以唯一的解決方式就是重寫const迭代器類,將const迭代器類的私有成員變量改為兩個(gè)const指針,這樣在構(gòu)造函數(shù)處構(gòu)造const迭代器時(shí),就不會(huì)出現(xiàn)權(quán)限的放大了,只是發(fā)生權(quán)限的平移。

template <class K, class T, class Hash, class KeyOfT>
class hashTable
{
public:
	friend struct __HTIterator<K, T, Hash, KeyOfT>;

	typedef hashNode<T> Node;
	typedef __HTIterator<K, T, Hash, KeyOfT> iterator;

	iterator begin()
	{
		for (size_t i = 0; i < _table.size(); i++)
		{
			if (_table[i])
			{
				return iterator(_table[i], this);//如果是const對(duì)象,這里返回const指針
			}
		}

		return iterator(nullptr, this);//如果哈希表是空的或者沒有桶,則構(gòu)造空迭代器。
	}
	iterator end()
	{
		return iterator(nullptr, this);
	}
	

2.3 哈希表和其他STL容器 支持普通迭代器構(gòu)造const迭代器的本質(zhì)是什么?(權(quán)限的縮小和平移)

1.
哈希表的迭代器是個(gè)特殊的存在,因?yàn)樗腸onst和非const迭代器是兩個(gè)類模板,而STL的其他容器的const和非const迭代器都是出自一個(gè)類模板。

2.
本質(zhì)還是因?yàn)楣1淼腸onst迭代器的私有成員變量得是const指針,而其他容器的const迭代器的私有成員變量只是普通指針。所以哈希表的普通迭代器構(gòu)造const迭代器其實(shí)是權(quán)限的縮小,由普通指針賦值到const指針。而其他容器的普通迭代器構(gòu)造const迭代器其實(shí)是權(quán)限的平移,因?yàn)闊o論是普通迭代器還是const迭代器,他們的成員變量都是普通指針

3.unordered_map的[ ]操作

1.
支持map的[ ]操作并不困難,因?yàn)樗腫 ]操作其實(shí)就是通過insert返回迭代器和bool的鍵值對(duì)來實(shí)現(xiàn)的。當(dāng)[ ]內(nèi)的key不存在,則調(diào)用哈希表的Inset完成key和V()構(gòu)造的鍵值對(duì)的插入,并返回插入鍵值對(duì)的迭代器和true的bool值構(gòu)造的鍵值對(duì)。當(dāng)[ ]內(nèi)的key在哈希表中存在時(shí),則哈希表的Insert也會(huì)返回指向key和value鍵值對(duì)的迭代器以及false的bool值構(gòu)造的鍵值對(duì)。

2.
所以實(shí)現(xiàn)[ ]的重?fù)?dān)主要是在Insert上面,只要Insert返回迭代器,那就能通過迭代器拿到鍵值對(duì)的value值,再通過返回value值的引用就可以修改哈希表中某一鍵值對(duì)的value值了。

下面是[ ]函數(shù)和哈希表底層的Insert函數(shù),Insert的邏輯沒有變,只是將他的返回值從bool改為了鍵值對(duì)而已。文章來源地址http://www.zghlxwxcb.cn/news/detail-408260.html

V& operator[](const K& key)
{
	pair<iterator, bool> ret = _ht.Insert(make_pair(key, V()));

	return ret.first->second;
}

pair<iterator,bool> Insert(const T& data)
{
	KeyOfT kot;
	iterator it = Find(kot(data));
	if (it != end())
		return make_pair(it, false);//如果插入的值已經(jīng)存在那就不再進(jìn)行插入,返回對(duì)應(yīng)位置迭代器即可。

	if (_n == _table.size())
	{

		vector<Node*> _newtable;
		_newtable.resize(__stl_next_prime(_table.size()), nullptr);
		for (int i = 0; i < _table.size(); i++)
		{
			Node* cur = _table[i];
			while (cur)
			{
				Node* next = cur->_next;
				size_t hashi = Hash()(kot(cur->_data)) % _newtable.size();
				cur->_next = _newtable[hashi];
				_newtable[hashi] = cur;

				cur = next;
			}

			_table[i] = nullptr;
		}
		_table.swap(_newtable);
	}

	size_t hashi = Hash()(kot(data)) % _table.size();
	Node* newnode = new Node(data);
	newnode->_next = _table[hashi];
	_table[hashi] = newnode;

	++_n;
	return make_pair(iterator(newnode, this), true);
}

void test_unordered_map()
{
	string arr[] = { "蘋果", "西瓜", "香蕉", "草莓", "蘋果", "西瓜", "蘋果", "蘋果", "西瓜",
		"蘋果", "香蕉", "蘋果", "香蕉" , "藍(lán)莓" ,"草莓" };

	unordered_map<string, int> countMap;
	for (auto& e : arr)
	{
		countMap[e]++;
	}
	unordered_map<string, int>::iterator it = countMap.begin();
	//1.不用語(yǔ)法糖,一點(diǎn)一點(diǎn)遍歷也可以
	while (it != countMap.end())
	{
		cout << it->first << ":" << it->second << endl;
		++it;
	}
	//2.我們實(shí)現(xiàn)了迭代器,直接用語(yǔ)法糖也可以。
	for (const auto& kv : countMap)//將解引用后的迭代器賦值給kv
	{
		cout << kv.first << ":" << kv.second << endl;
	}
}

到了這里,關(guān)于【C++】開散列哈希表封裝實(shí)現(xiàn)unordered_map和unordered_set的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(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)文章

  • [C++]哈希表實(shí)現(xiàn),unordered_map\set封裝

    [C++]哈希表實(shí)現(xiàn),unordered_map\set封裝

    目錄 ??????? 前言: 1 哈希 1.1 為什么有哈希 1.2 哈希結(jié)構(gòu) 1.3 哈希沖突 ?2 閉散列 2.1 閉散列結(jié)點(diǎn)結(jié)構(gòu)和位置狀態(tài)表示 2.2 哈希類結(jié)構(gòu) 2.3 插入 2.4 查找 2.5 刪除 3 開散列 3.1 哈希表結(jié)點(diǎn)結(jié)構(gòu) 3.2 哈希表結(jié)構(gòu) 3.3 插入 3.4 查找、刪除 3.5 迭代器實(shí)現(xiàn) 4 map和set的封裝 4.1 map的封

    2024年02月08日
    瀏覽(20)
  • 由[哈希/散列]模擬實(shí)現(xiàn)[unordered_map/unordered_set] (手撕迭代器)

    由[哈希/散列]模擬實(shí)現(xiàn)[unordered_map/unordered_set] (手撕迭代器)

    以下兩篇文章均為筆者的嘔心瀝血 想要搞懂本篇文章的uu請(qǐng)自行查閱 哈希/散列的細(xì)節(jié)實(shí)現(xiàn) 哈希/散列–哈希表[思想到結(jié)構(gòu)][==修訂版==] 手撕迭代器的細(xì)節(jié)處理 模擬實(shí)現(xiàn)map/set[改編紅黑樹實(shí)現(xiàn)map/set容器底層]

    2024年02月07日
    瀏覽(20)
  • 【C++學(xué)習(xí)】哈希表的底層實(shí)現(xiàn)及其在unordered_set與unordered_map中的封裝

    【C++學(xué)習(xí)】哈希表的底層實(shí)現(xiàn)及其在unordered_set與unordered_map中的封裝

    ??個(gè)人名片: ??作者簡(jiǎn)介:一名樂于分享在學(xué)習(xí)道路上收獲的大二在校生 ??個(gè)人主頁(yè)??:GOTXX ??個(gè)人WeChat:ILXOXVJE ??本文由GOTXX原創(chuàng),首發(fā)CSDN?????? ??系列專欄:零基礎(chǔ)學(xué)習(xí)C語(yǔ)言----- 數(shù)據(jù)結(jié)構(gòu)的學(xué)習(xí)之路----C++的學(xué)習(xí)之路 ??每日一句:如果沒有特別幸運(yùn),那就請(qǐng)?zhí)?/p>

    2024年04月16日
    瀏覽(38)
  • 【C++】如何用一個(gè)哈希表同時(shí)封裝出unordered_set與unordered_map

    【C++】如何用一個(gè)哈希表同時(shí)封裝出unordered_set與unordered_map

    ?? 樊梓慕: 個(gè)人主頁(yè) ??? 個(gè)人專欄: 《C語(yǔ)言》 《數(shù)據(jù)結(jié)構(gòu)》 《藍(lán)橋杯試題》 《LeetCode刷題筆記》 《實(shí)訓(xùn)項(xiàng)目》 《C++》 《Linux》 《算法》 ?? 每一個(gè)不曾起舞的日子,都是對(duì)生命的辜負(fù) 目錄 前言 1.哈希桶源碼 ?2.哈希表模板參數(shù)的控制 3.字符串作為鍵值如何映射哈希值

    2024年03月26日
    瀏覽(40)
  • 【C++】STL——用一個(gè)哈希表封裝出unordered_map和unordered_set

    【C++】STL——用一個(gè)哈希表封裝出unordered_map和unordered_set

    根據(jù)先前對(duì)unordered_map和unordered_set的學(xué)習(xí),我們得知其底層是借助哈希表來實(shí)現(xiàn)的,接下來我們會(huì)使用上篇博客實(shí)現(xiàn)的開散列哈希表來模擬實(shí)現(xiàn)unordered_map和unordered_set,哈希表源代碼鏈接: Hash/Hash/HashBucket.h · wei/cplusplus - 碼云 - 開源中國(guó) (gitee.com) 下面我們對(duì)哈希表進(jìn)行改造,

    2023年04月18日
    瀏覽(26)
  • 【C++進(jìn)階】哈希表開散列和閉散列的模擬實(shí)現(xiàn)(附源碼)

    【C++進(jìn)階】哈希表開散列和閉散列的模擬實(shí)現(xiàn)(附源碼)

    這里的閉散列和開散列解決哈希沖突的方法都是 除留余數(shù)法 。 一些哈希函數(shù):字符串哈希算法 閉散列:也叫 開放定址法 ,當(dāng)發(fā)生哈希沖突時(shí),如果哈希表未被裝滿,說明在哈希表中必然還有 空位置,那么可以 把key存放到?jīng)_突位置中的“下一個(gè)” 空位置中去 。 如何找到

    2024年02月08日
    瀏覽(23)
  • C++王牌結(jié)構(gòu)hash:哈希表開散列(哈希桶)的實(shí)現(xiàn)與應(yīng)用

    C++王牌結(jié)構(gòu)hash:哈希表開散列(哈希桶)的實(shí)現(xiàn)與應(yīng)用

    目錄 一、開散列的概念 1.1開散列與閉散列比較 二、開散列/哈希桶的實(shí)現(xiàn) 2.1開散列實(shí)現(xiàn) 哈希函數(shù)的模板構(gòu)造 哈希表節(jié)點(diǎn)構(gòu)造 開散列增容 插入數(shù)據(jù) 2.2代碼實(shí)現(xiàn) 開散列法又叫鏈地址法(開鏈法),首先對(duì)關(guān)鍵碼集合用散列函數(shù)計(jì)算散列地址,具有相同地址的關(guān)鍵碼歸于同一子集

    2024年04月17日
    瀏覽(32)
  • 【C++進(jìn)階】使用一個(gè)哈希桶封裝出unordered_map和uordered_set

    【C++進(jìn)階】使用一個(gè)哈希桶封裝出unordered_map和uordered_set

    由于要使用一個(gè)哈希桶封裝出unordered_map和uordered_set,所以要對(duì)哈希桶的模版參數(shù)進(jìn)行改造,由于unordered_map是kv模型,而uordered_set是k模型,所以必須在實(shí)現(xiàn)哈希桶的時(shí)候提供模版,在封裝uordered_set和unordered_map時(shí)傳入不同的模版參數(shù)。 還要要修改的是,如果同時(shí)封裝出,在獲得

    2024年02月12日
    瀏覽(21)
  • 【C++入門到精通】 哈希結(jié)構(gòu) | 哈希沖突 | 哈希函數(shù) | 閉散列 | 開散列 [ C++入門 ]

    【C++入門到精通】 哈希結(jié)構(gòu) | 哈希沖突 | 哈希函數(shù) | 閉散列 | 開散列 [ C++入門 ]

    在現(xiàn)代計(jì)算機(jī)科學(xué)和數(shù)據(jù)結(jié)構(gòu)中,哈希(Hash)是一項(xiàng)重要而廣泛應(yīng)用的技術(shù)。通過將輸入數(shù)據(jù)映射為固定長(zhǎng)度的哈希值,哈希函數(shù)能夠快速高效地進(jìn)行數(shù)據(jù)存儲(chǔ)、搜索和比較。然而,由于輸入數(shù)據(jù)的多樣性和哈希值的有限長(zhǎng)度,哈希沖突成為了一個(gè)不可避免的問題。本文將介

    2024年02月08日
    瀏覽(21)
  • 【C++】哈希表:開散列和閉散列

    【C++】哈希表:開散列和閉散列

    ?? 個(gè)人主頁(yè) :超人不會(huì)飛) ?? 本文收錄專欄 :《C++的修行之路》 ?? 如果本文對(duì)您有幫助,不妨 點(diǎn)贊、收藏、關(guān)注 支持博主,我們一起進(jìn)步,共同成長(zhǎng)! 在前面的文章中,我們學(xué)習(xí)了紅黑樹,其查找數(shù)據(jù)的效率很高,查找的次數(shù)最差是樹的高度logN次,并且要進(jìn)行多次比

    2023年04月27日
    瀏覽(25)

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

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

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

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

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包