前面部分轉自C++ STL map的自定義排序,
std::map 的定義與特性,用法詳解參考C++ map用法詳解。
1 //所在頭文件:<map>, std::map 類模板, std::map 通常由二叉搜索樹實現。 2 template < class Key, // map::key_type 3 class T, // map::mapped_type 4 class Compare = less<Key>, // map::key_compare 5 class Alloc = allocator<pair<const Key,T> > // map::allocator_type 6 > class map;
std::unorder_map的定義如下:
1 //頭文件unorder_map, 2 template<class Key, 3 class Ty, 4 class Hash = std::hash<Key>, 5 class Pred = std::equal_to<Key>, 6 class Alloc = std::allocator<std::pair<const Key, Ty> > > 7 class unordered_map; 8 > class unordered_map
一、map按鍵值Key排序
1. 默認按照less<key>
升序排列
輸入8,Key升序,Value隨機:

1 #include<iostream> 2 #include<map> 3 using namespace std; 4 int main() 5 { 6 srand((unsigned)time(NULL)); 7 multimap<int,int>mp; 8 // multimap第三個參數默認為less<Key>,即 less<int> 9 int n; 10 cin>>n; 11 int a,b; 12 for(int i=0; i<n; i++) 13 { 14 a=rand()%4; 15 b=rand()%4; 16 //插入 17 mp.insert(pair<int,int>(a,b)); 18 } 19 map<int,int>::iterator iter; 20 //遍歷輸出 21 for(iter=mp.begin(); iter!=mp.end(); iter++) 22 cout<<iter->first<<" "<<iter->second<<endl; 23 return 0; 24 }
2. 定義map時,用greater< Key>實現按Key值遞減插入數據
1 multimap<int,int,greater<int> >mp; 2 //注意<int>后空一格
3. 當Key值為自定義的類時
方法1:寫一個函數對象1(仿函數),重載operator()
注意:函數對象:即調用操作符的類,其對象常稱為函數對象(function object),它們是行為類似函數的對象。表現出一個函數的特征,就是通過“對象名+(參數列表)”的方式使用一個 類,其實質是對operator()操作符的重載。

1 #include<iostream> 2 #include<map> 3 using namespace std; 4 typedef struct tagIntPlus 5 { 6 int num,i; 7 }IntPlus; 8 //自定義比較規則 9 //注意operator是(),不是< 10 struct Cmp 11 { 12 bool operator () (IntPlus const &a,IntPlus const &b)const 13 { 14 if(a.num!=b.num) 15 return a.num<b.num; 16 else return a.i<b.i; 17 } 18 }; 19 int main() 20 { 21 srand((unsigned)time(NULL)); 22 //注意此處一定要有Cmp,否則無法排序會報錯 23 multimap<IntPlus,int,Cmp>mp; 24 int n; 25 cin>>n; 26 int a,b; 27 IntPlus intplus; 28 for(int i=0; i<n; i++) 29 { 30 a=rand()%4; 31 b=rand()%4; 32 intplus.num=a; 33 intplus.i=b; 34 mp.insert(pair<IntPlus,int>(intplus,i)); 35 } 36 map<IntPlus,int>::iterator iter; 37 for(iter=mp.begin(); iter!=mp.end(); iter++) 38 cout<<iter->first.num<<" "<<iter->first.i<<" "<<iter->second<<endl; 39 return 0; 40 }
方法2:在類里重載重載operator<

1 typedef struct tagIntPlus 2 { 3 int num,i; 4 bool operator < (tagIntPlus const& intplus)const 5 { 6 //當num不等時 7 //前一個對象的num>后一個時返回true,降序排列。 8 //反之升序排列 9 if(num!=intplus.num) 10 return num>intplus.num; 11 //當num相等時 12 //前一個對象的i<后一個時返回true,升序排列 13 else return i<intplus.i; 14 } 15 }IntPlus;
注意只重載小於號,不要去重載大於號,如果想改變為 升 / 降序列,只需改變判斷條件即可。
1 multimap<IntPlus,int>mp;
主函數只需將第一種方法中的map中的Cmp去掉即可。
4. 用char*類型作為map的主鍵
find或count時,默認使用== 進行判斷,char*只是指針,如果兩個字符串值相同,但是地址不同,是無法匹配的。所以最好使用std::string。如果非要用char*,需要使用find_if函數並且用bind2sd函數指定比較函數。
1 #include <map> 2 #include <algorithm> 3 #include <iostream> 4 5 using namespace std; 6 7 bool search(pair<char*, int> a, const char* b) 8 { 9 return strcmp(a.first, b) == 0 ? true : false; 10 } 11 int main() 12 { 13 map<char*, int> test; 14 test.insert(pair<char*, int>("abc", 1)); 15 16 map<char*, int>::const_iterator iter = find_if(test.begin(), test.end(), bind2nd(ptr_fun(search), "abc")); 17 18 if (iter != test.end()) 19 { 20 cout<< "find : " << iter->first << endl; 21 } 22 23 return 0; 24 }
二、map按值Value排序
再次強調不能用sort,只能將map中數據壓入能用sort的容器,如vector
1 #include<iostream> 2 #include<map> 3 #include<vector> 4 #include<algorithm>//sort 5 using namespace std; 6 7 typedef struct tagIntPlus 8 { 9 int num,i; 10 } IntPlus; 11 12 typedef pair<tagIntPlus,int> PAIR;
必須有Cmp。雖然之后會sort,map的排序並不重要,但是map輸入數據時需要比較Key值,沒有會報錯。注意這里說的是自定義類型作為key需要加Cmp函數。

1 struct Cmp 2 { 3 bool operator () (IntPlus const &a,IntPlus const &b)const 4 { 5 if(a.num!=b.num) 6 return a.num<b.num; 7 else return a.i<b.i; 8 } 9 };
map會按鍵值Key升序排列,Value值無要求。定義vector的排序接口如下
1 bool vec_cmp(PAIR const&a,PAIR const&b) 2 { 3 if(a.first.num!=b.first.num) 4 return a.first.num<b.first.num; 5 else 6 { 7 if(a.first.i!=b.first.i) 8 return a.first.i<b.first.i; 9 else return a.second>b.second; 10 } 11 }
上面需重新定義Key升序排列,否則sort之后僅按Value降序排列,Key值被打亂。

1 int main() 2 { 3 srand((unsigned)time(NULL)); 4 multimap<IntPlus,int,Cmp>mp; 5 int n; 6 cin>>n; 7 int a,b; 8 IntPlus intplus; 9 for(int i=0; i<n; i++) 10 { 11 a=rand()%4; 12 b=rand()%4; 13 intplus.num=a; 14 intplus.i=b; 15 mp.insert(pair<IntPlus,int>(intplus,i)); 16 } 17 map<IntPlus,int>::iterator iter; 18 cout<<"排序前:"<<endl; 19 for(iter=mp.begin(); iter!=mp.end(); iter++) 20 cout<<iter->first.num<<"|"<<iter->first.i<<"|"<<iter->second<<endl;
1 cout<<"排序后:"<<endl; 2 vector<PAIR>vec(mp.begin(),mp.end()); 3 sort(vec.begin(),vec.end(),vec_cmp); 4 int size=vec.size(); 5 for(int i=0;i<size;i++) 6 cout<<vec[i].first.num<<"|"<<vec[i].first.i<<"|"<<vec[i].second<<endl; 7 return 0; 8 }
三、std::unorder_map自定義鍵值類型(轉載)
對於unordered_map而言,當我們插入<key, value>的時候,需要哈希函數的函數對象對key進行hash,又要利用等比函數的函數對象確保插入的鍵值對沒有重復。然而,當我們自定義類型時,c++標准庫並沒有對應的哈希函數和等比函數的函數對象。因此需要分別對它們進行定義。
因為都是函數對象,它們兩個的實際定義方法並沒有很大差別。不過后者比前者多了一個方法。因為等比函數的函數對象默認值std::equal_to<key>內部是通過調用操作符"=="進行等值判斷,因此我們可以直接在自定義類里面進行operator==()重載(成員和友元都可以)。
因此,如果要將自定義類型作為unordered_map的鍵值,需如下兩個步驟:
a-定義哈希函數的函數對象;
b-定義等比函數的函數對象或者在自定義類里重載operator==()。
為了避免重復,下文以討論哈希函數的函數對象為主,參數4則是通過直接在自定義類里面對operator==()進行重載。
首先簡要介紹一下函數對象 (在一、二部分已介紹) 的概念:在《C++ Primer Plus》里面,函數對象是可以以函數方式與()結合使用的任意對象。這包括函數名、指向函數的指針和重載了“operator()”操作符的類對象。基於此,我們提出3個方法。
方法1:std::function
利用std::function為person_hash()構建函數實例。初始化時,這個函數實例就會被分配那個指向person_hash()的指針(通過構造函數實現),如下所示。

1 #include <iostream> 2 #include <unordered_map> 3 #include <string> 4 #include <functional> 5 6 using namespace std; 7 8 class Person{ 9 public: 10 string name; 11 int age; 12 13 Person(string n, int a){ 14 name = n; 15 age = a; 16 } 17 bool operator==(const Person & p) const 18 { 19 return name == p.name && age == p.age; 20 } 21 }; 22 23 size_t person_hash( const Person & p ) 24 { 25 return hash<string>()(p.name) ^ hash<int>()(p.age); 26 } 27 28 int main(int argc, char* argv[]) 29 { 30 //ERRO: unordered_map<Person,int,decltype(&person_hash)> ids; 31 //ERRO: unordered_map<Person,int,person_hash> ids(100, person_hash ); 32 //OK: unordered_map<Person, int, decltype(&person_hash)> ids(100, person_hash ); 33 unordered_map<Person,int,function<size_t( const Person& p )>> ids(100, person_hash); //需要把person_hash傳入構造函數 34 ids[Person("Mark", 17)] = 40561; 35 ids[Person("Andrew",16)] = 40562; 36 for ( auto ii = ids.begin() ; ii != ids.end() ; ii++ ) 37 cout << ii->first.name 38 << " " << ii->first.age 39 << " : " << ii->second 40 << endl; 41 return 0; 42 }
因為std::function構建對象的表達過於復雜,我們可以利用C++11新增的關鍵字decltype。它可以直接獲取自定義哈希函數的類型,並把它作為參數傳送。因此,ids的聲明可以改成下面這樣。
unordered_map<Person,int,decltype(&person_hash)> ids(100, person_hash);
另外,我們還可以引入c++11新支持的lambda expression,程序如下。

1 #include <iostream> 2 #include <unordered_map> 3 #include <string> 4 #include <functional> 5 6 using namespace std; 7 8 class Person{ 9 public: 10 string name; 11 int age; 12 13 Person(string n, int a){ 14 name = n; 15 age = a; 16 } 17 18 bool operator==(const Person & p) const 19 { 20 return name == p.name && age == p.age; 21 } 22 }; 23 24 int main(int argc, char* argv[]) 25 { 26 unordered_map<Person,int,std::function<size_t (const Person & p)>> 27 ids(100, []( const Person & p) 28 { 29 return hash<string>()(p.name) ^ hash<int>()(p.age); 30 } ); 31 ids[Person("Mark", 17)] = 40561; 32 ids[Person("Andrew",16)] = 40562; 33 for ( auto ii = ids.begin() ; ii != ids.end() ; ii++ ) 34 cout << ii->first.name 35 << " " << ii->first.age 36 << " : " << ii->second 37 << endl; 38 return 0; 39 }
但是,使用lambda有2個弊端:
-
我們就無法使用decltype獲取函數對象的類型,而只能用更復雜的std::function方法。
-
程序的可讀性下降。
方法2:重載operator()的類
方法2就是利用重載operator()的類,將哈希函數打包成可以直接調用的類。此時,雖然我們仍然需要第3個參數,但是我們不需要將函數對象的引用傳入構造器里。因為unordered_map會追蹤類定義,當需要獲得哈希時,它可以動態地構造對象並傳遞數據,如下所示。

1 #include <iostream> 2 #include <string> 3 #include <unordered_map> 4 #include <functional> 5 using namespace std; 6 7 class Person{ 8 public: 9 string name; 10 int age; 11 12 Person(string n, int a){ 13 name = n; 14 age = a; 15 } 16 17 bool operator==(const Person & p) const 18 { 19 return name == p.name && age == p.age; 20 } 21 }; 22 23 struct hash_name{ 24 size_t operator()(const Person & p) const{ 25 return hash<string>()(p.name) ^ hash<int>()(p.age); 26 } 27 }; 28 29 int main(int argc, char* argv[]){ 30 unordered_map<Person, int, hash_name> ids; //不需要把哈希函數傳入構造器 31 ids[Person("Mark", 17)] = 40561; 32 ids[Person("Andrew",16)] = 40562; 33 for ( auto ii = ids.begin() ; ii != ids.end() ; ii++ ) 34 cout << ii->first.name 35 << " " << ii->first.age 36 << " : " << ii->second 37 << endl; 38 return 0; 39 }
方法3:模板定制
unordered_map第3個參數的默認參數是std::hash<Key>,實際上就是模板類。那么我們就可以對它進行模板定制,如下所示。

1 #include <iostream> 2 #include <unordered_map> 3 #include <string> 4 #include <functional> 5 using namespace std; 6 7 typedef pair<string,string> Name; 8 9 namespace std { 10 template <> //function-template-specialization 11 class hash<Name>{ 12 public : 13 size_t operator()(const Name &name ) const 14 { 15 return hash<string>()(name.first) ^ hash<string>()(name.second); 16 } 17 }; 18 }; 19 20 int main(int argc, char* argv[]) 21 { 22 unordered_map<Name,int> ids; 23 ids[Name("Mark", "Nelson")] = 40561; 24 ids[Name("Andrew","Binstock")] = 40562; 25 for ( auto ii = ids.begin() ; ii != ids.end() ; ii++ ) 26 cout << ii->first.first 27 << " " << ii->first.second 28 << " : " << ii->second 29 << endl; 30 return 0; 31 }
當我們將模板訂制包含在定義類的頭文件中時,其他人無需額外工作,就可以直接用我們的類作為任何無序容器的鍵。這對於要使用我們自定義類的人來說,絕對是最方便的。因此,如果你想要在多個地方用這個類,方法3是最好的選擇。當然,你要確保自己的hash function不會影響std空間里的其他類。
————————————————
額外案例:等比函數的函數對象
下例是哈希函數對象和等比函數對象都采用模板定制的方法。

1 #include <iostream> 2 #include <string> 3 #include <unordered_map> 4 #include <functional> 5 using namespace std; 6 7 class Person{ 8 public: 9 string name; 10 int age; 11 12 Person(string n, int a){ 13 name = n; 14 age = a; 15 } 16 }; 17 18 namespace std{ 19 template<> 20 struct hash<Person>{//哈希的模板定制 21 public: 22 size_t operator()(const Person &p) const 23 { 24 return hash<string>()(p.name) ^ hash<int>()(p.age); 25 } 26 27 }; 28 29 template<> 30 struct equal_to<Person>{//等比的模板定制 31 public: 32 bool operator()(const Person &p1, const Person &p2) const 33 { 34 return p1.name == p2.name && p1.age == p2.age; 35 } 36 37 }; 38 } 39 40 int main(int argc, char* argv[]){ 41 unordered_map<Person, int> ids; 42 ids[Person("Mark", 17)] = 40561; 43 ids[Person("Andrew",16)] = 40562; 44 for ( auto ii = ids.begin() ; ii != ids.end() ; ii++ ) 45 cout << ii->first.name 46 << " " << ii->first.age 47 << " : " << ii->second 48 << endl; 49 return 0; 50 }
————————————————