C++容器基礎之map詳解

一、概述

1、是什麼

    map是一類關聯式容器,關聯的本質在於元素值與某個特定的鍵相關聯。增刪節點對迭代器影響很小,對於迭代器而言,不可修改鍵,只能修改其對應的值。map內部自建一棵紅黑樹,對內部元素有自動排序的功能。

    紅黑樹:一種二叉查找樹,此外在每個節點上增加一個存儲位表示節點的顏色,可以是red,也可以是black。通過對一條從根到葉子節點的路徑上各個節點着色方式的限制,紅黑樹確保沒有一條路徑會比其他路徑長兩倍,因此是接近平衡的。作爲一個二叉查找樹,滿足二叉查找樹的一般性質。

    二叉查找樹,即有序二叉樹,有如下性質:

  • 任意節點的左子樹不空,則左子樹上所有節點的值均小於它的跟節點的值
  • 任意節點的右子樹不空,則右子樹上所有節點的值均大於它的跟節點的值
  • 任意節點的左、右子樹也分別是二叉查找樹
  • 沒有鍵值相等的節點

    二叉查找樹高度爲lgn,一般操作執行時間爲O(logn),如果二叉查找樹退化爲一棵具有n個節點的線性鏈後,則這些操作最壞情況的運行時間爲O(n)。而紅黑樹增加了着色和相關性質,是的紅黑樹相對平衡,從而保證了紅黑樹的查找、插入、刪除的時間複雜度最壞爲O(logn)。

    紅黑樹性質

  • 每個節點要麼是red,要麼是black
  • 跟節點是black
  • 每個葉子結點都是black
  • 如果一個節點是red,那麼它的兩個兒子都是black
  • 對於任意節點而言,其到葉子結點的每條路徑都包含相同數目的black節點

   這5個性質保證了一棵有n個節點的紅黑樹始終保持logn的高度。

2、功能

     首先,使用map需要包含頭文件#include<map>。自動簡歷key-value的一一對應關係,key和value可以是任意類型,但是key的類型需要支持<操作符。由上述可知,查找的複雜度基本是logn,如果有1000個記錄,則最多需要查詢10次,如果有1000000個記錄,最多需要查詢20次。當然也有添加、刪除、修改value、遍歷等功能。

二、map的使用

1、插入

    map<int, string> m_str;
    pair<map<int, string>::iterator, bool> is_suc;
    //map的插入有三種方式
    //1、insert(pair<>)
    is_suc = m_str.insert(pair<int, string>(2, "yuhy"));
    //可以用is_suc判斷是否插入成功
    if(is_suc.second){
        cout << "successful" << endl;
    }else{
        cout << "failed" << endl;
    }
    //2、insert (value_type)
    m_str.insert(map<int, string>::value_type(5, "scott"));
    //3、用數組方式插入數據
    m_str[1] = "LiMing";

2、遍歷

    //遍歷方式四種:
    //1、for each
    for(auto it : m_str){
        cout << "key:" << it.first << ", value:" << it.second << endl;
    }
    cout << "-------" << endl;
    //2、前向迭代器
    for(auto it = m_str.begin(); it != m_str.end(); it++){
        cout << "key:" << it->first << ", value:" << it->second << endl;
    }
    cout << "-------" << endl;
    //3、反向迭代器
    for(auto it = m_str.rbegin(); it != m_str.rend(); it++){
        cout << "key:" << it->first << ", value:" << it->second << endl;
    }
    cout << "-------" << endl;
    //4、數組遍歷方式
    for(int i=0; i<m_str.size(); i++){
        cout << "value:" << m_str[i] << endl;
    }

3、訪問

    //operator[]和at()
    map<int, int> t_map;
    t_map[2] = 2;
    t_map[4] = 4;
    t_map[6] = 6;

    cout << "key=2, value=" << t_map.at(2) << endl;

    for(auto it : t_map){
        cout << "key:" << it.first << ",value:" << it.second << endl;
    }
    t_map.at(2) += 1;
    cout << "key=8, value=" << t_map[8] << endl;
    for(auto it : t_map){
        cout << "key:" << it.first << ",value:" << it.second << endl;
    }

operator[]和at()的區別:

使用map[key],如果key不存在,則會自動爲您添加一個鍵,並對值進行初始化。

使用map.at(key), 會檢查key,如果key不存在則會跑出異常。

4、查找

    //查找,兩種
    //1、count,查詢關鍵字是否出現,無法判定其位置,返回值0或者1,1表示存在
    cout << "5 是否出現 :" ;
    if(m_str.count(5) == 1){
        cout << "是";
    }else{
        cout << "否";
    }
    cout << endl;
    //2、find,返回值是迭代器,如果不存在則返回end()
    map<int, string>::iterator res_find = m_str.find(5);
    if(res_find == m_str.end()){
        cout << "yuhy 不存在" << endl;
    }else{
        cout << "yuhy key:" << res_find->first << ", value:" << res_find->second << endl;
    }

5、刪除

    //刪除
    //1、erase(k),刪除鍵爲k的元素,並返回刪除的個數
    map<int, string>::size_type res_erase = m_str.erase(5);
    cout << "num of erase:" << res_erase << endl;
    //2、erase(it),刪除迭代器it指向的元素
    m_str.erase(m_str.find(1));
    //3、erase(it_f, it_e),刪除一段範圍內的元素
    m_str.erase(m_str.begin(), m_str.end());

6、排序

map默認key是從小到大排序。

map定義:

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

有四個參數,第一個是key,第二個是value,第三個參數提供了默認的less,less是STL中的一個函數對象,調用操作符的類,其對象常常稱爲函數對象,他們是行爲類似函數的對象。表現出一個函數的特徵,就是通過“對象名 + (參數列表)”的方式使用一個類,其實質是對operator()操作符的重載。

less的實現:

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

裏面僅僅是對()運算符的重載,調用的是T的<運算符。當然,與less相對的還有greater。

方式一:如果key是自定義對象,重寫<操作符

class Student{
public:
    std::string name_;
    int age_;
    Student(std::string name, int age):name_(name),age_(age){}
    Student(){}
    bool operator < (const Student &s) const {
        std::cout << "operator : < " << std::endl;
        if(name_ != s.name_){
            return name_ < s.name_;
        }else{
            return age_ < s.age_;
        }
    }

};

    map<Student, string> m_stu;
    m_stu.insert(pair<Student, string>(Student("yyy", 20), "yyy"));
    m_stu.insert(pair<Student, string>(Student("aaa", 20), "aaa"));
    m_stu.insert(pair<Student, string>(Student("yyy", 18), "yyy"));
    cout << "--- 排序 ---" << endl;
    for(auto it : m_stu){
        cout << "key-name:" << it.first.name_ << ", key-age:" << it.first.age_ << ", value:" << it.second << endl;
    }

方式二、重載()

class Comp{
public:
    bool operator () (const Student &left, const Student &right) const {
        std::cout << "operator ()" << std::endl;
        if(left.name_ != right.name_){
            return left.name_ < right.name_;
        }else{
            return left.age_ < right.age_;
        }
    }
};

    map<Student, string, Comp> m_stu;
    m_stu.insert(pair<Student, string>(Student("yyy", 20), "yyy"));
    m_stu.insert(pair<Student, string>(Student("aaa", 20), "aaa"));
    m_stu.insert(pair<Student, string>(Student("yyy", 18), "yyy"));
    cout << "--- 排序 ---" << endl;
    for(auto it : m_stu){
        cout << "key-name:" << it.first.name_ << ", key-age:" << it.first.age_ << ", value:" << it.second << endl;
    }

方式三:

class Student{
public:
    std::string name_;
    int age_;
    Student(std::string name, int age):name_(name),age_(age){}
    Student(){}

    std::string show(){
        return "name:" + name_ + ", age:" + std::to_string(age_);
    }

    bool operator < (const Student &s) const {
        std::cout << "operator : < " << std::endl;
        if(name_ != s.name_){
            return name_ < s.name_;
        }else{
            return age_ < s.age_;
        }
    }

};

bool comp (const Student &left, const Student &right){
    cout << "comp method" << endl;
    if(left.name_ != right.name_){
        return left.name_ < right.name_;
    }else{
        return left.age_ < right.age_;
    }
}

map<Student, string, decltype(comp)*> m_stu(comp);
    m_stu.insert(pair<Student, string>(Student("yyy", 20), "yyy"));
    m_stu.insert(pair<Student, string>(Student("aaa", 20), "aaa"));
    m_stu.insert(pair<Student, string>(Student("yyy", 18), "yyy"));
    cout << "--- 排序 ---" << endl;
    for(auto it : m_stu){
        cout << "key-name:" << it.first.name_ << ", key-age:" << it.first.age_ << ", value:" << it.second << endl;
    }

方式四:對map中的value進行排序

    首先想到的是STL中的sort算法,但是sort只能對序列容器進行排序,只能是線性的(如vector、list、deque)。map是個集合容器,存儲的元素是pair,不是線性存儲的,所以不能直接用sort。

   但是我們可以將map中的元素放到序列容器中,再對序列容器中的元素進行排序。有一個必要條件,序列容器中的元素必須是可比較的,即實現了<操作的。

    在使用sort的時候,傳入比較函數,實現對pair中的value進行比較。

bool cmp_value(const pair<Student, string>& a, const pair<Student, string>& b) {
    return b.second < a.second;
}
    map<Student, string, decltype(comp)*> m_stu(comp);
    m_stu.insert(pair<Student, string>(Student("yyy", 20), "yyy"));
    m_stu.insert(pair<Student, string>(Student("aaa", 20), "aaa"));
    m_stu.insert(pair<Student, string>(Student("yyy", 18), "yyy"));
    cout << "--- 排序 ---" << endl;
    for(auto it : m_stu){
        cout << "key-name:" << it.first.name_ << ", key-age:" << it.first.age_ << ", value:" << it.second << endl;
    }

    vector<pair<Student, string>> vec(m_stu.begin(), m_stu.end());
    sort(vec.begin(), vec.end(), cmp_value);
    for(auto it : vec){
        cout << "key-name:" << it.first.name_ << ", key-age:" << it.first.age_ << ", value:" << it.second << endl;
    }

 

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