日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

boost::unorder_map如何插入元素_链表和有序二叉树插入元素时真的比数组快吗?

發布時間:2025/3/21 编程问答 24 豆豆
生活随笔 收集整理的這篇文章主要介紹了 boost::unorder_map如何插入元素_链表和有序二叉树插入元素时真的比数组快吗? 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

腳本之家

你與百萬開發者在一起

作者 |?focuscode出品 | 腳本之家(ID:jb51net)

公司有位C++標準委員會的顧問大佬,一年會有幾次視頻講座,分享一些編程要點或者經驗。很多時候都是C++很基礎的方面,但是他的講解視頻真的很深入淺出,有時候會“打破”一些理所應當的觀點,這篇文章就是讓我覺得很有趣,并且意想不到的地方,在這里分享一下。

1. 非關聯容器

在我們看到的眾多數據結構書籍中,最開始介紹過時間復雜度和空間復雜度后,接下來由簡入難,分別是數組,鏈表和樹。很多程序語言都提供了自己的標準實現,這里我們以C++為例。在C++標準庫(STL)中,有兩個基于堆分配的容器,分別對應數組和雙向鏈表,std::vector和std::list。在后續的說明中,所有的實驗都是基于這兩個容器,但是其適用于任何基于節點的數據結構,不只是C++標準庫中的。開始之前,都知道這兩種數據結構在內存的中的存儲形式是不同的,數組在內存中地址是連續的,鏈表的內存地址是非連續的(如下圖)。

插入元素比較?

在數組中插入一個元素,需要將插入位置后面的元素向后移動,時間復雜度是O(N);但是向雙向鏈表中插入一個元素需要分配一個節點,并設置4個指針,時間復雜度是O(1)。自然,對于很大的常數N,在鏈表中插入要快速的多

讀取元素比較

對于這兩種容器,按順序讀取所有元素的時間復雜度是O(N)。但是,數組的讀取速度仍舊比鏈表快。緩存(cache)和預取(prefetch)機制對數組讀取是有利的,但是對鏈表的讀取沒有任何幫助。

從理論上來說,以上的說法是正確的,但在現代計算機上,真正的性能是如何的?我們可以通過實驗來驗證,通過不停地修改N的值,該程序可以得到在數組和雙向鏈表的中間值右邊插入數值0所消耗的時間。所用機器配置如下圖:

#include#include#include#include#include#includeusingnamespace std;constsize_t N = 50000;int main(){doublestart_t, end_t; std::vector vec(N); std::list lst(N);//The vector and list has same N elementsfor(size_t i = 0; i < vec.size(); ++i){ vec[i] = i + 1; lst.push_back(i + 1);}//insert 100 right before the the first occurence of 3start_t= clock(); vec.insert(std::find(vec.begin(), vec.end(), 3), 100); //Tvend_t= clock(); std::cout << "vector cost time: "<< ((end_t- start_t) * 1000000) << " ns"<< std::endl;start_t= clock(); lst.insert(std::find(lst.begin(), lst.end(), 3), 100); // Tlend_t= clock(); std::cout << "list cost time: "<< ((end_t- start_t) * 1000000) << " ns"<< std::endl;return0;}

很明顯,這樣的線性搜索對于兩種容器來說都是O(N),正如我們已經看到的,向vector插入一個元素是一個O(N)操作,向list插入一個元素是O(1)操作。向數組插入元素時,把少量的元素向右移動代價是很小的,毫無疑問應該期待Tv < Tl;但是當N是多少時,移動元素的代價會使Tl < Tv? 應該存在這樣的一個臨界值N。下面的表格是插入元素的實驗結果:

Sizestd::vectorstd::listlist/vector
100000ns0nsnull
500000ns1e+06nsnull
1000000ns2e+06nsnull
10000006e+06ns2.1e+07ns3.5
100000005.5e+07ns2.03e+08ns3.69
1000000005.79e+08ns2.169e+09ns3.746
5000000003.6342e+10ns2.7049e+11ns5.638

我們發現,沒有這樣的一個臨界值N,使得在雙向鏈表中插入元素比數組快。線性內存訪問模式會使用CPU的pre-fetcher,其作為一個無限大小的LN+1緩存。但是指針節點的數據結構沒有這點好處,所以總是更慢一點。所以,如果你的關心迭代速度,就不要使用基于節點的容器,大多數情況下,程序更關注迭代速度。實際上,不管是插入或者刪除操作,當你試圖去尋找正確的位置,這時程序就需要迭代。所以,一般不要使用鏈表。

但是,在寫程序時,總是會有適合鏈表的時刻(雖然很少):

1.用分離鏈表法去解決hash沖突的時候,對于相同的hash key值,使用鏈表來存儲,將新的節點插入鏈表的頭部;2.如果需要引用穩定性,則可能需要一個鏈表,因為節點不會移動。如果指向一個節點,并對除了所指向的節點之外的任何內容執行插入或擦除操作,該指針將保持有效。3.實現一個軟件LRU或MRU緩存,通常使用鏈表,并有指針指向緩存的所有元素。當使用其中一個時,將其從鏈表中拉出,并將其放在開頭或結尾。

別的一般情況下,相比std::list,更傾向于選擇std::array和std::vector。

2. 關聯容器

“關聯容器”是一個來自c++標準的術語。關聯容器通常稱為映射或字典。它們將一組鍵一對一地關聯到一組值上。換句話說,您可以將一個鍵及其關聯值插入到容器中,然后通過傳遞該鍵請求該值。在C++ STL中,包括std::map和std::set。這兩個容器都以紅黑樹的方式實現,因此可以按照鍵排序的順序遍歷元素。

std::map is a sorted associative container that contains key-value pairs with unique keys. Keys are sorted by using the comparison function Compare. Search, removal, and insertion operations have logarithmic complexity. Maps are usually implemented as red-black trees.

從c++ 11開始,還有散列關聯容器std::unordered_map和std::unordered_set。顧名思義,這些元素沒有按順序排列。

這里,我們討論有序的,其底層結構是紅黑樹的容器,內存存儲結構如下圖:

在有序數組和樹中,查找一個特定值的時間復雜度是O(log(N)),并且由于在任何二叉搜索中都要在內存中跳躍,所以數組的速度并沒有加快。

將一個元素插入到已排序的數組中需要先進行搜索,然后再進行插入(包括將后面的所有元素后移),時間消耗為O(log(N) + N)或者說O(N)。插入到樹中需要搜索、分配,可能還需要重新平衡樹,時間復雜度是O (log (N))。對于較大的N,在樹中插入自然要快得多。

需要注意的是,由于插入時間的xx性,填充一個排序數組是O(N * N),填充紅黑樹是O(N * log(N))。對于兩個容器,按順序讀取所有元素的時間是O(N)。同樣,讀取數組中的值要快得多。事實上,樹中的迭代速度甚至比鏈表中的還要慢。下面的測試代碼是基于vector_vs_map稍作改動。

#include#include#include#include#includeconstsize_t N = 1000000;volatilesize_t acc = 0;typedef std::vector<:pair>size_t, size_t>> vecss;typedef std::map<size_t, size_t> mapss;struct firstPairComp{booloperator()(const vecss::value_type& lhs, const vecss::value_type& rhs){return lhs.first < rhs.first;}};void insert_in_vec(const std::vector& orderVec){ vecss vals; vals.reserve(N);doublestart_t, end_t;start_t= clock();for(size_t i = 0; i < N; ++i){ vecss::value_type val(orderVec[i], rand()); std::pair<:iterator vecss::iterator> res = std::equal_range(vals.begin(), vals.end(), val, firstPairComp()); vals.insert(res.second, val);}end_t= clock(); std::cout << "vector insert cost time: "<< ((end_t- start_t) * 1000000) << " ns"<< std::endl;}void find_in_vec(const std::vector& orderVec){ vecss vals; vals.reserve(N);for(size_t i = 0; i < N; ++i){ vecss::value_type val(orderVec[i], rand()); std::pair<:iterator vecss::iterator> res = std::equal_range(vals.begin(), vals.end(), val, firstPairComp()); vals.insert(res.second, val);}doublestart_t, end_t;start_t= clock();for(size_t i = 0; i < N; ++i){ vecss::value_type val(orderVec[i], 0); std::pair<:iterator vecss::iterator> res = std::equal_range(vals.begin(), vals.end(), val, firstPairComp()); acc += res.first->second;}end_t= clock(); std::cout << "vector find cost time: "<< ((end_t- start_t) * 1000000) << " ns"<< std::endl;}void insert_in_map(const std::vector& orderVec){ mapss vals;doublestart_t, end_t;start_t= clock();for(size_t i = 0; i < N; ++i){ mapss::value_type val(orderVec[i], rand()); vals.insert(val);}end_t= clock(); std::cout << "map insert cost time: "<< ((end_t- start_t) * 1000000) << " ns"<< std::endl;}void find_in_map(const std::vector& orderVec){ mapss vals;for(size_t i = 0; i < N; ++i){ mapss::value_type val(orderVec[i], rand()); vals.insert(val);}doublestart_t, end_t;start_t= clock();for(size_t i = 0; i < N; ++i){ mapss::iterator it = vals.find(orderVec[i]); acc += it->second;}end_t= clock(); std::cout << "map insert cost time: "<< ((end_t- start_t) * 1000000) << " ns"<< std::endl;}void iter_vec(){ vecss vals; vals.reserve(N);for(size_t i = 0; i < N; ++i) vals.push_back(vecss::value_type(i, rand())); vecss::value_type *begin= &vals[0];doublestart_t, end_t;start_t= clock();for(size_t i = 0; i < N; ++i) acc += begin[i].second;end_t= clock(); std::cout << "vector iterator cost time: "<< ((end_t- start_t) * 1000000) << " ns"<< std::endl;}void iter_map(){ mapss vals;for(size_t i = 0; i < N; ++i) vals.insert(mapss::value_type(i, rand()));doublestart_t, end_t;start_t= clock(); mapss::const_iterator end= vals.end();for(mapss::const_iterator it = vals.begin(); it != end; ++it) acc += it->second;end_t= clock(); std::cout << "map iterator cost time: "<< ((end_t- start_t) * 1000000) << " ns"<< std::endl;}int main(){ std::vector orderVec(N);for(size_t i = 0; i < N; i++){ orderVec[i] = i + 1;} insert_in_vec(orderVec); insert_in_map(orderVec); find_in_vec(orderVec); find_in_map(orderVec); iter_vec(); iter_map(); system("pause");return0;}

測試結果在下面的表格(注意:這里的vector是有序的),單位是納秒(ns):

SizeInsert ComparisonFind ComparisonIterator Comparison
VectorMapMap/VectorVectorMapMap/VectorVectorMapMap/Vector
100001e+062e+0621e+061e+06100null
500004e+067e+061.753e+063e+06101e+06null
1000008e+061.5e+071.8757e+067e+06102e+06null
10000009.6e+071.74e+081.8137.2e+077.2e+0712e+062.1e+0710.5
100000001.097e+091.908e+091.748.23e+087.9e+080.961.5e+072.2e+0814.67
1000000001.3488e+102.3679e+101.767.83e+098.888e+091.131.84e+082.117e+0911.51
從上面可以看出,對于迭代,沒有比線性內存訪問更好的方法了,并且插入操作,map也沒有比有序數組更好。如果只關心快速插入、刪除和查找怎么辦?使用散列表,如std::unordered_map或std::unordered_set。這些操作平攤了O(1)的運行時間,比O(log(N))要好。當你決定使用樹的時候,記住:

?對于關聯數據結構的插入、擦除和查找操作,哈希幾乎總是優于樹。?對于相當小的工作集,排序數組具有無與倫比的迭代速度,但代價昂貴的插入和擦除。

因此,如果您關心迭代速度或插入、刪除和查找的性能,就不要使用基于樹的數據結構。同樣,可能會發現少數場景下需要考慮穩定性。

3. 總結

避免使用鏈表,除非:

?你需要參考穩定性;?你正在實現一個軟件MRU/LRU緩存。

使用數組來代替。std::vector生成一個良好的堆分配數組,而std::array生成一個適合堆棧的固定大小的良好數組。

避免使用樹結構,除非:

?需要參考穩定性;?操作的成本并不依賴于樹中的元素數量(如trie)。

對于小的N值,使用排序數組(boost::container::flat_map構造了一個良好排序的數組,就像一個有序的std::vector一樣)。

flat_map is similar to std::map but it's implemented by as an?ordered sequence container. The underlying sequence container is by default vector but it can also work user-provided vector-like SequenceContainers (like static_vector or small_vector).

對于大的N值,使用哈希表(std::unordered_map和std::unordered_set)。

本文作者:focuscode,16年控制工程畢業,不慎誤入C/C++懷抱,希望有一天可以給簡歷的精通C++加上雙引號~。

聲明:本文為 腳本之家專欄作者 投稿,未經允許請勿轉載。

- END -

●??腳本之家粉絲福利,請查看?

●??人人都欠微軟一個正版??

●??什么是RSA算法

●?加密算法的前世今生

?HTTPS 到底加密了什么?

總結

以上是生活随笔為你收集整理的boost::unorder_map如何插入元素_链表和有序二叉树插入元素时真的比数组快吗?的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。