C++ STL中Map的按Key排序和按Value排序

  原文  http://blog.csdn.net/iicy266/article/details/11906189java

  map是用來存放<key, value>鍵值對的數據結構,能夠很方便快速的根據key查到相應的value。假如存儲學生和其成績(假定不存在重名,固然能夠對重名加以區分),咱們用map來進行存儲就是個不錯的選擇。 咱們這樣定義,map<string, int>,其中學生姓名用string類型,做爲Key;該學生的成績用int類型,做爲value。這樣一來,咱們能夠根據學生姓名快速的查找到他的成績。python

        可是,咱們除了但願可以查詢某個學生的成績,或許還想看看總體的狀況。咱們想把全部同窗和他相應的成績都輸出來,而且按照咱們想要的順序進行輸出:好比按照學生姓名的順序進行輸出,或者按照學生成績的高低進行輸出。換句話說,咱們但願可以對map進行按Key排序或按Value排序,而後按序輸出其鍵值對的內容。ios

1、C++ STL中Map的按Key排序算法

       其實,爲了實現快速查找,map內部自己就是按序存儲的(好比紅黑樹)。在咱們插入<key, value>鍵值對時,就會按照key的大小順序進行存儲。這也是做爲key的類型必須可以進行<運算比較的緣由。如今咱們用string類型做爲key,所以,咱們的存儲就是按學生姓名的字典排序儲存的。數據結構

【參考代碼】less

#include<map>
#include<string> #include<iostream> using namespace std; typedef pair<string, int> PAIR; ostream& operator<<(ostream& out, const PAIR& p) { return out << p.first << "\t" << p.second; } int main() { map<string, int> name_score_map; name_score_map["LiMin"] = 90; name_score_map["ZiLinMi"] = 79; name_score_map["BoB"] = 92; name_score_map.insert(make_pair("Bing",99)); name_score_map.insert(make_pair("Albert",86)); for (map<string, int>::iterator iter = name_score_map.begin(); iter != name_score_map.end(); ++iter) { cout << *iter << endl; } return 0; }

【運行結果】dom

 

你們都知道map是stl裏面的一個模板類,如今咱們來看下map的定義:函數

template < class Key, class T, class Compare = less<Key>, class Allocator = allocator<pair<const Key,T> > > class map;


它有四個參數,其中咱們比較熟悉的有兩個: Key 和 Value。第四個是 Allocator,用來定義存儲分配模型的,此處咱們不做介紹。ui

如今咱們重點看下第三個參數: class Compare = less<Key> spa

這也是一個class類型的,並且提供了默認值 less<Key>。 less是stl裏面的一個函數對象,那麼什麼是函數對象呢?

所謂的函數對象:即調用操做符的類,其對象常稱爲函數對象(function object),它們是行爲相似函數的對象。表現出一個函數的特徵,就是經過「對象名+(參數列表)」的方式使用一個 類,其實質是對operator()操做符的重載。

如今咱們來看一下less的實現:

template <class T> struct less : binary_function <T,T,bool> { bool operator() (const T& x, const T& y) const {return x<y;} };


它是一個帶模板的struct,裏面僅僅對()運算符進行了重載,實現很簡單,但用起來很方便,這就是函數對象的優勢所在。stl中還爲四則運算等常見運算定義了這樣的函數對象,與less相對的還有greater:

template <class T> struct greater : binary_function <T,T,bool> { bool operator() (const T& x, const T& y) const {return x>y;} };


map這裏指定less做爲其默認比較函數(對象),因此咱們一般若是不本身指定Compare,map中鍵值對就會按照Key的less順序進行組織存儲,所以咱們就看到了上面代碼輸出結果是按照學生姓名的字典順序輸出的,即string的less序列。

咱們能夠在定義map的時候,指定它的第三個參數Compare,好比咱們把默認的less指定爲greater:

【參考代碼】

#include<map>
#include<string> #include<iostream> using namespace std; typedef pair<string, int> PAIR; ostream& operator<<(ostream& out, const PAIR& p) { return out << p.first << "\t" << p.second; } int main() { map<string, int, greater<string> > name_score_map; name_score_map["LiMin"] = 90; name_score_map["ZiLinMi"] = 79; name_score_map["BoB"] = 92; name_score_map.insert(make_pair("Bing",99)); name_score_map.insert(make_pair("Albert",86)); for (map<string, int>::iterator iter = name_score_map.begin(); iter != name_score_map.end(); ++iter) { cout << *iter << endl; } return 0; }

【運行結果】

 

如今知道如何爲map指定Compare類了,若是咱們想本身寫一個compare的類,讓map按照咱們想要的順序來存儲,好比,按照學生姓名的長短排序進行存儲,那該怎麼作呢?

其實很簡單,只要咱們本身寫一個函數對象,實現想要的邏輯,定義map的時候把Compare指定爲咱們本身編寫的這個就ok啦。

struct CmpByKeyLength {
  bool operator()(const string& k1, const string& k2) { return k1.length() < k2.length(); } };

 

是否是很簡單!這裏咱們不用把它定義爲模板,直接指定它的參數爲string類型就能夠了。

【參考代碼】

int main() {
  map<string, int, CmpByKeyLength> name_score_map; name_score_map["LiMin"] = 90; name_score_map["ZiLinMi"] = 79; name_score_map["BoB"] = 92; name_score_map.insert(make_pair("Bing",99)); name_score_map.insert(make_pair("Albert",86)); for (map<string, int>::iterator iter = name_score_map.begin(); iter != name_score_map.end(); ++iter) { cout << *iter << endl; } return 0; }

【運行結果】

 

 

2、C++ STL中Map的按Value排序

        在第一部分中,咱們藉助map提供的參數接口,爲它指定相應Compare類,就能夠實現對map按Key排序,是在建立map並不斷的向其中添加元素的過程當中就會完成排序。

如今咱們想要從map中獲得學生按成績的從低到高的次序輸出,該如何實現呢?換句話說,該如何實現Map的按Value排序呢?

        第一反應是利用stl中提供的sort算法實現,這個想法是好的,不幸的是,sort算法有個限制,利用sort算法只能對序列容器進行排序,就是線性的(如vector,list,deque)。map也是一個集合容器,它裏面存儲的元素是pair,可是它不是線性存儲的(前面提過,像紅黑樹),因此利用sort不能直接和map結合進行排序。

       雖然不能直接用sort對map進行排序,那麼咱們可不能夠迂迴一下,把map中的元素放到序列容器(如vector)中,而後再對這些元素進行排序呢?這個想法看似是可行的。要對序列容器中的元素進行排序,也有個必要條件:就是容器中的元素必須是可比較的,也就是實現了<操做的。那麼咱們如今就來看下map中的元素知足這個條件麼?

       咱們知道map中的元素類型爲pair,具體定義以下:

template <class T1, class T2> struct pair { typedef T1 first_type; typedef T2 second_type; T1 first; T2 second; pair() : first(T1()), second(T2()) {} pair(const T1& x, const T2& y) : first(x), second(y) {} template <class U, class V> pair (const pair<U,V> &p) : first(p.first), second(p.second) { } }


pair也是一個模板類,這樣就實現了良好的通用性。它僅有兩個數據成員first 和 second,即 key 和 value,並且

在 <utility>頭文件中,還爲pair重載了 < 運算符, 具體實現以下: 

template<class _T1, class _T2> inline bool operator<(const pair<_T1, _T2>& __x, const pair<_T1, _T2>& __y) { return __x.first < __y.first || (!(__y.first < __x.first) && __x.second < __y.second); }


重點看下其實現:

__x.first < __y.first || (!(__y.first < __x.first) && __x.second < __y.second)

這個less在兩種狀況下返回true,第一種狀況:__x.first < __y.first  這個好理解,就是比較key,若是__x的key 小於 __y的key 則返回true。

第二種狀況有點費解:  !(__y.first < __x.first) && __x.second < __y.second

固然因爲||運算具備短路做用,即當前面的條件不知足是,才進行第二種狀況的判斷 。第一種狀況__x.first < __y.first 不成立,即__x.first >= __y.first 成立,在這個條件下,咱們來分析下  !(__y.first < __x.first)  && __x.second < __y.second

 !(__y.first < __x.first) ,看清出,這裏是y的key不小於x的key ,結合前提條件,__x.first < __y.first 不成立,即x的key不小於y的key 

即:  !(__y.first < __x.first)  &&   !(__x.first < __y.first )   等價於   __x.first == __y.first ,也就是說,第二種狀況是在key相等的狀況下,比較二者的value(second)。

這裏比較使人費解的地方就是,爲何不直接寫 __x.first == __y.first 呢? 這麼寫看似費解,但其實也不無道理:前面講過,做爲map的key必須實現<操做符的重載,可是並不保證==符也被重載了,若是key沒有提供==,那麼 ,__x.first == __y.first 這樣寫就錯了。因而可知,stl中的代碼是至關嚴謹的,值得咱們好好研讀。

 如今咱們知道了pair類重載了<符,可是它並非按照value進行比較的,而是先對key進行比較,key相等時候纔對value進行比較。顯然不能知足咱們按value進行排序的要求。

並且,既然pair已經重載了<符,並且咱們不能修改其實現,又不能在外部重複實現重載<符。

typedef pair<string, int> PAIR; bool operator< (const PAIR& lhs, const PAIR& rhs) { return lhs.second < rhs.second; }


若是pair類自己沒有重載<符,那麼咱們按照上面的代碼重載<符,是能夠實現對pair的按value比較的。如今這樣作不行了,甚至會出錯(編譯器不一樣,嚴格的就報錯)。

那麼咱們如何實現對pair按value進行比較呢? 第一種:是最原始的方法,寫一個比較函數;  第二種:剛纔用到了,寫一個函數對象。這兩種方式實現起來都比較簡單。

typedef pair<string, int> PAIR; bool cmp_by_value(const PAIR& lhs, const PAIR& rhs) { return lhs.second < rhs.second; } struct CmpByValue { bool operator()(const PAIR& lhs, const PAIR& rhs) { return lhs.second < rhs.second; } };


接下來,咱們看下sort算法,是否是也像map同樣,可讓咱們本身指定元素間如何進行比較呢?

template <class RandomAccessIterator> void sort ( RandomAccessIterator first, RandomAccessIterator last ); template <class RandomAccessIterator, class Compare> void sort ( RandomAccessIterator first, RandomAccessIterator last, Compare comp );

咱們看到,使人興奮的是,sort算法和map同樣,也可讓咱們指定元素間如何進行比較,即指定Compare。須要注意的是,map是在定義時指定的,因此傳參的時候直接傳入函數對象的類名,就像指定key和value時指定的類型名同樣;sort算法是在調用時指定的,須要傳入一個對象,固然這個也簡單,類名()就會調用構造函數生成對象。

這裏也能夠傳入一個函數指針,就是把上面說的第一種方法的函數名傳過來。(應該是存在函數指針到函數對象的轉換,或者二者調用形式上是一致的,具體確切緣由還不明白,但願知道的朋友給講下,先謝謝了。)

【參考代碼】

int main() {
  map<string, int> name_score_map; name_score_map["LiMin"] = 90; name_score_map["ZiLinMi"] = 79; name_score_map["BoB"] = 92; name_score_map.insert(make_pair("Bing",99)); name_score_map.insert(make_pair("Albert",86)); //把map中元素轉存到vector中 vector<PAIR> name_score_vec(name_score_map.begin(), name_score_map.end()); sort(name_score_vec.begin(), name_score_vec.end(), CmpByValue()); // sort(name_score_vec.begin(), name_score_vec.end(), cmp_by_value); for (int i = 0; i != name_score_vec.size(); ++i) { cout << name_score_vec[i] << endl; } return 0; }

【運行結果】

相關文章
相關標籤/搜索