map/unordered_map基礎用法

map是STL裏重要容器之一。

它的特性總結來講就是:所有元素都會根據元素的鍵值key自動排序(也可根據自定義的仿函數進行自定義排序),其中的每個元素都是<key, value>的鍵值對,map中不允許有鍵值相同的元素,

因此map中元素的鍵值key不能修改,但是可以通過key修改與其對應的value。如果一定要修改與value對應的鍵值key,可將已存在的key刪除掉,然後重新插入。

定義原型:

它作用應用場景可用作 ①字典    ②統計次數

相關操作


(1)插入操作

方式有3種

 注:typedef pair<const Key, T> value_type;

通過插入新元素來擴展容器,通過插入元素的數量有效地增加容器容量。

由於映射中的元素鍵是唯一的,因此插入操作將檢查每個插入的元素是否具有與容器中已有元素相同的鍵,如果是,則不插入該元素,並將迭代器返回給此現有元素如果函數返回一個值)。

對於允許重複元素的類似容器,請參閱multimap。

在map中插入元素的另一種方法是使用成員函數map :: operator []。

在容器內部,map容器按照其比較對象指定的標準,通過鍵將所有元素進行排序。這些元素總是按照這個順序插入到相應的位置。

返回值:

1.單個元素版本(1)返回一個pair,其成員pair :: first被設置爲一個迭代器,指向新插入的元素或映射中具有等效鍵的元素。如果插入了新元素,則將pair中的pair :: second元素設置爲true;如果已存在相同的鍵,則將該元素設置爲false。

2.帶有提示(2)的版本返回一個迭代器,指向新插入的元素或映射中已經具有相同鍵的元素。 成員類型迭代器是指向元素的雙向迭代器類型

  /*make_pair內斂函數  返回一個pair對象*/
  //template<class K, class V>
  //inline pair<K, V> make_pair(const K& k, const V& v)
  //{ 
  //  return pair<K, V>( k,v);    
  //} 
  void test_map_insert( )
  {
      //實現一個字典
      typedef map<string, string>::iterator MyIterator;
      map<string, string> direct;
      direct.insert( pair<string, string>("insert", "插入" ));
      direct.insert( pair<string, string>("sort", "排序" ));
      direct.insert( make_pair("apple", "蘋果" ));
      direct.insert( make_pair("insert", "插入" ));
      direct.insert( make_pair("insert", "插入" ));
      direct.insert( make_pair("sort", "排序" ));
  
      //(1)pair<iterator, bool> insert( const value_type& V); 
      pair<MyIterator, bool> ret = direct.insert(make_pair("apple", "蘋果"));
      if(ret.second == false)  //插入元素已經存在,將second置爲false
      {
          cout<<" 'apple' is inserted!";
          //first被設置爲一個迭代器,指向新插入的元素或映射中具有等效鍵的元素
          cout<< "with value of "<<ret.first->second<<endl;
      }
      //(2)指定iterator插入
      direct.insert(direct.begin( ), make_pair("os1", "操作系統1"));
      direct.insert(direct.begin( ), make_pair("os2", "操作系統2")); //os2仍在os1後面
  
      //(3)範圍插入
      map<string, string> another;
      another.insert(direct.begin( ), direct.end( ));
  
      //遍歷(和set方式類似)
      MyIterator it = direct.begin( );
      while( it != direct.end( ))
      {
          cout<<it->first<<": "<<it->second<<endl;
          ++it;
      }
  }

(2)operator[]接口

原型如下:

map重載了“[]”運算符。重載的運算符“[]”實質上調用了前面中版本(1)的insert接口,它利用了insert的返回值(一個pair<iterator, bool>類型),最後返回pair中的迭代器所指元素value值的引用。如此,便可通過“[]” 來進行map的插入操作,與此同時,還可對新插入的元素(或插入元素在map已經存在的元素)的value值進行修改。重載[]具體實現如下:

(*((this->insert(make_pair(k,mapped_type()))).first)).second

 對它進行大致解析後,可將其修改爲:

 template<class K, class V>
 typedef map<K, V>::iterator MyIterator;
 mapped_type& operator[](const K& k)
 {
     //mapped_type是V值(value)的默認值,value爲int的話則默認爲0
     pair<MyIterator, bool> ret = this->insert(make_pair(k, mapped_type()));
     return ret.first->second;  //或者 *(ret.first ).second;      ret.first是MyIterator迭代器,最後返回迭代器所指元素的second值(也即是value)的引用。
 }

例子

// accessing mapped values
#include <iostream>
#include <map>
#include <string>

int main ()
{
  std::map<char,std::string> mymap;

  mymap['a']="an element";
  mymap['b']="another element";
  mymap['c']=mymap['b'];  //插入key爲‘c’的元素,隨後將其對應value值修改。

  //key爲'a'的元素已經插入,此時返回‘a’所對應value的值
  std::cout << "mymap['a'] is " << mymap['a'] << '\n'; 
  //key爲'b'的元素已經插入,此時返回‘b’所對應value的值
  std::cout << "mymap['b'] is " << mymap['b'] << '\n'; 
  //key爲'c'的元素已經插入,此時返回‘c’所對應value的值
  std::cout << "mymap['c'] is " << mymap['c'] << '\n';  
  //直接插入key爲‘a’的元素,元素對應value值爲默認的“”
  std::cout << "mymap['d'] is " << mymap['d'] << '\n';  
  std::cout << "mymap now contains " << mymap.size() << " elements.\n";

  return 0;
}
//結果
/*mymap['a'] is an element
  mymap['b'] is another element
  mymap['c'] is another element
  mymap['d'] is
  mymap now contains 4 elements*/

值得注意的是,通過"[]"操作,若能確保查找元素在map中存在,還可以用它進行查找操作。因爲在執行“[]”操作的過程中,插入失敗會返回與查找元素擁有相同key值的一個iterator。

(3)按自定義順序排序

 通常map對傳入的元素,默認是按元素中key值進行排序(即前面定義的Less<Key>),通過前面的map原型定義不難看出它同樣支持按自定義的順序進行比較排序。例如我們自定義的Stu的對象,就可按對象中的年齡來進行排序:

 struct Stu
 {
     string name;
     int age;
     Stu(const string& na="", int a = 0)
         :name( na), age(a) { };
 };
 //定置一個仿函數
 struct compare
 {
     bool operator( )(const Stu& l, const Stu& r)
     { return l.age < r.age;}
 };
 void test_insert_compare()
 {//按傳入Stu對象的年齡排序
     multimap<Stu, int, compare>sortmap;
     Stu s1("jack", 18);
     sortmap.insert(make_pair(s1, 1));
     Stu s2( "mike", 20);
     sortmap.insert(make_pair(s2, 1));
     Stu s3( "zede", 19);
     sortmap.insert(make_pair(s3, 1));
 
     map<Stu,int, compare>::iterator it = sortmap.begin( );
     while( it != sortmap.end( ))
     {
         cout<<it->first.name<<": "<<it->first.age<<" --"<<it->second<<endl;
         ++it;
     }
    //結果:
    //jack: 18 --1
    //zede: 19 --1
    //mike: 20 --1

 }

小應用:據出現次數,統計前K項語言


 //定製一個仿函數
 typedef map<string, int>::iterator CountIte;
 struct compare
 {
     bool operator()(CountIte lhs, CountIte rhs){
         return lhs->second > rhs->second;
     }
 };
 void get_topK_gramar(const vector<string>& v, int k)
 {
     //統計vector中各種相同key出現的次數
     map<string, int> countMap;
     for( int i =0; i< v.size( ); ++i)
     {
     //  map<string, int>::iterator it = countMap.find(v[ i]);
     //  if(it != countMap.end( ))//countmap中存在v[ i] 
     //      ++it->second;
     //  else
     //      countMap.insert( make_pair(v[i], 1);
         countMap[v[i]]++;
     }
 
     //定置仿函數,以每種編程語言出現次數進行排序
     //注意:不能用set來排序,因爲它會去重,即其會將具有相同value值的某種語言過濾掉
     multiset<CountIte, compare> sortSet;
     CountIte cit = countMap.begin( );
     while( cit != countMap.end( ))
     {
         sortSet.insert(cit);
         ++cit;
     }
 
     multiset<CountIte, compare>::iterator it1 =  sortSet.begin( );
     for(; it1 != sortSet.end( ); ++it1)
     {
         if( k--)
             cout<<(*it1)->first<<":"<<(*it1)->second<<endl;
     }
 }
 void test_map_question( )
 {
     vector<string> v;
     v.push_back("python" );
     v.push_back("PHP" );
     v.push_back("PHP" );
     v.push_back("PHP" );
     v.push_back("PHP" );
     v.push_back("Java" );
     v.push_back("PHP" );
     v.push_back("C/C++" );
     v.push_back("C/C++" );
     v.push_back("python" );
     v.push_back("Java" );
     v.push_back("Java" );
     //統計語言次數,或者前K種語言
     get_topK_gramar(v, 3);
 }

結果:

multimap


multimap和map的唯一差別是map中key必須是唯一的,而multimap中的key是可以重複的。由於不用再判斷是否插入了相同key的元素,所以multimap的單個元素版本的insert的返回值不再是一個pair, 而是一個iterator。也正是如此,所以multimap也不再提供operator[]接口。

multimap和map的其它用法基本類似。

點擊回頂部

unordered_map/unordered_multimap


在C++11中有新出4個關聯式容器:unordered_map/unordered_set/unordered_multimap/unordered_multiset。

這4個關聯式容器與map/multimap/set/multiset功能基本類似,最主要就是底層結構不同,使用場景不容。

如果需要得到一個有序序列,使用紅黑樹系列的關聯式容器,如果需要更高的查詢效率,使用以哈希表爲底層的關聯式容器。 

此處只列舉unordered_map,其它用法類似可自行查閱 可參考cplusplus

 unordered_map底層實現是用哈希桶實現的:

定義原型

在cplusplus的解釋:

無序映射是關聯容器,用於存儲由鍵值和映射值組合而成的元素,並允許基於鍵快速檢索各個元素。

在unordered_map中,鍵值通常用於唯一標識元素,而映射值是與該鍵關聯的內容的對象。鍵和映射值的類型可能不同。

在內部,unordered_map中的元素沒有按照它們的鍵值或映射值的任何順序排序,而是根據它們的散列值組織成桶以允許通過它們的鍵值直接快速訪問單個元素(具有常數平均時間複雜度)。

unordered_map容器比映射容器更快地通過它們的鍵來訪問各個元素,儘管它們通過其元素的子集進行範圍迭代通常效率較低。

無序映射實現直接訪問操作符(operator []),該操作符允許使用其鍵值作爲參數直接訪問映射值。

容器中的迭代器至少是前向迭代器。

關鍵詞:無序的 快速的檢索 達到的是更快的訪問 但是子集的範圍迭代效率低

 

相關操作


 1.插入遍歷...

 typedef unordered_map<string, double>::iterator MyIte;
 void test_unordered_map( )
 { 
     unordered_map<string, double> umap;
     umap.insert(make_pair("蘋果", 2.5));
     umap.insert(make_pair("香蕉", 3.0));
     umap.insert(make_pair("香蕉", 3.0));
     umap.insert(make_pair("西瓜", 1.5));
     umap.insert(make_pair("哈密瓜", 3.0));
     umap["榴蓮"] = 4.0;
     MyIte it = umap.begin( );
     while( it != umap.end( ))
     { 
         cout<<it->first<<" :"<<it->second<<endl;
         ++it;
     }   
     cout<<"桶數量:"<<umap.bucket_count( )<<endl;
     cout<<"負載因子:"<<umap.load_factor( )<<endl;
    //結果:
    //榴蓮 :4
    //蘋果 :2.5
    //哈密瓜 :3
    //香蕉 :3
    //西瓜 :1.5
    //桶數量:11
    //負載因子:0.454545
 }

2.自定義比較

 struct Store
 {
     string name;
     string addr;
     Store(const string& na="", const string& ad= "")
         :name(na),addr( ad){ }
 
     bool operator==(const Store& s) const     //重載==支持等於比較
     {   
         return name == s.name && addr == s.addr; 
     }
 };
 struct hash_key    //定製返回哈希值的仿函數
 {
     //BKDRHash
     size_t operator()(const Store& s) const
     {
         size_t seed = 131; /* 31 131 1313 13131 131313 etc.. */
         size_t hash = 0;
         size_t i = 0;
         for( i = 0; i < s.name.size(); ++i)
         {
             hash = ( hash * seed)  + s.name[i];
         }
         return hash;
     }
 };
 
 typedef unordered_map<Store, int, hash_key>::iterator MyIte;
 void test_unordered_map( )
 {
     unordered_map<Store, int, hash_key> umap;
     Store s1("火鍋店", "重慶");
     Store s2("涼皮店", "西安");
     Store s3("烤鴨店", "北京");
 
     umap.insert(make_pair(s1, 1));
     umap.insert(make_pair(s2, 1));
     umap[s3] = 1;
 
     MyIte it = umap.begin( );
     while( it != umap.end( ))
     {
         cout<<it->first.name<<":"<<it->second<<endl;
         ++it;
     }
 }

3. 性能測試

測試insert  比較map和unordered_map性能差異

 typedef unordered_map<int, int>::iterator MyIte;
 void test_unordered_map( )
 {
     unordered_map<int, int> umap;
     map<int, int>mp;
     srand(time( NULL));
 
     const int N = 100000;
     vector<int> a;
     for(int i=0; i< N; ++i)
         a.push_back(rand()%N);
 
     clock_t begin1,end1;
     begin1 = clock();
     umap.rehash(100000);  //通過rehash設置哈希桶數量,進一步提高效率
     for(int i =0; i< N; ++i)
         umap[a[i]];
     end1 = clock( );
     
     clock_t begin2, end2;
     begin2 = clock( );
     for( int i =0; i< N; ++i)
         mp[a[i]];
     end2= clock( );
 
     cout<<"負載因子:"<<umap.load_factor()<<" "<<"桶數:"<<umap.bucket_count( )<<endl;
    //統計運行的毫秒差
     cout<<"unordered_map:"<<(end1-begin1)/1000<<endl;
     cout<<"map:"<<(end2-begin2)/1000<<endl;//統計運行的毫秒差
 }
//結果 普通
tp@tp:~$ g++ -std=c++11 1.cc   
tp@tp:~$ ./a.out 
負載因子:0.500463 桶數:126271
unordered_map:49
map:102

//rehash之後
負載因子:0.585883 桶數:107897
unordered_map:37
map:107

unordered_map 與 map之間差異比較(Linux平臺下)

·map底層爲紅黑樹查找大致爲logN的時間複雜度;unordered_map底層是閉散列的哈希桶,查找爲O(1),性能更優。

·調用insert操作,map相較於unordered_map操作慢,大致有2到3倍差異;但是map插入更加穩定

·unordered_map的erase操作會縮容,導致元素重新映射,降低性能。

·unordered_map要求傳入的數據能夠進行大小比較,“==”關係比較;所以自定義數據需要定置hash_value仿函數同時重載operator==。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章