stl map之從入門到真的很懂

介紹

  • map是有序鍵-值(key-value)容器
  • 在數據獲取速度方面map通常慢於 unordered_map(爲什麼時通常?這個下一節介紹unordered_map時將會講解)
  • map通常是基於紅黑樹實現的
  • 本文中的關於容器實現的代碼均來自msvc std

何爲有序

有序,即key有序,map是基於紅黑樹的,每個節點爲key-value,
value省略不寫。紅黑樹是二叉查找樹的一種,先看一個二叉查找樹實現的map,結構如下:

    8
   / \
  3   10
 / \    \
1   6    14
   / \   /
  4   7 13

可以看到,按照先序遍歷的話key值依次是1 3 4 6 7 8 10 13 14是有序的。

二叉查找樹有以下性質

  • 所有左子樹節點上的值<根節點上的值
  • 所有右子樹節點上的值>根節點上的值
  • 任意節點的左右子樹分別爲二叉查找樹

如果到此爲止用二叉查找樹實現map的話會有一個問題,即如果插入的元素的key值本來就是有序的,那麼形成的二叉樹將是以下形式:

                14
                /
               13
              /
             10
            /
           8
          /
         7
        /
       6
      /
     4
    /
   3
  /
 1

這樣帶來的問題便是複雜度的上升,查找的複雜度由O(log(n))退化爲O(n),而避免複雜度退化的方法便是使用紅黑樹實現map,紅黑樹實質上是一種自平衡二叉查找樹。

什麼類型可以作爲key

既然map是基於紅黑樹實現的那麼也就決定了,map的key是惟一的,不能有重複key.
另外,應爲是有序的,所以要想有資格作爲map的key,就必須具備可比較這個硬條件。
比如,基本類型中的int,char,string,等都是可以比較大小的,當然可以作爲key使用。

自定義類型作爲key

自己實現的類或者結構體可以作爲key麼?
比如我們隨便寫一個類作爲key的例子,下邊的這段代碼是無法編譯通過的

#include <iostream>
#include <map>

class CKeyTest
{
public:
   CKeyTest() {};
   ~CKeyTest() {};
   int _age = 0;
};

int main() {
   CKeyTest x1, x2;
   std::map<CKeyTest, int> _map;
   _map[x1] = 1; // 這一行編譯不過
   return 0;
}

CKeyTest這個類只需稍作修改,即可以了。

#include <iostream>
#include <map>

class CKeyTest
{
public:
   CKeyTest() {};
   ~CKeyTest() {};

   int _age = 0;

   bool operator <(const CKeyTest& obj) const {
      return _age < obj._age;
   }
};

自定義類型作爲key的注意事項

將自定義類用作key有以下需要注意:

  1. 要重載比較操作符,如果類的“大小”無法比較,因爲那麼在將新元素插入套紅黑樹的時候,咦?我的位置在哪呢?
  2. 程序實現時候要注意如果在類內重載<,那麼這個“函數”必須是const的,,即this指針的類型等價於const &否則還是不能用
  3. 上邊的操作符重載也可以通過友元實現,但是與第二條一樣參數類型必須是const &, 如下:
class CKeyTest
{
public:
   CKeyTest() {};
   ~CKeyTest() {};

   int _age = 0;
   friend bool operator <(const CKeyTest& obj, const CKeyTest& obj2);
};

bool operator <(const CKeyTest& obj, const CKeyTest& obj2)
{
   return obj._age < obj._age;
}

使用

map 容器的使用需要添加<map>頭文件, 並且使用命名空間std,命名空間的使用,以下兩種方法都是可以的,
推薦使用第二種。

建議不要在函數外直接使用using namespace std;

#include <iostream>
#include <map>

int main ()
{
  std::map<char,int> _map;
  return 0;
}
#include <iostream>
#include <map>

int main ()
{
  using std::map;
  std::map<char,int> _map;
  return 0;
}

常用成員函數

  1. size()
    函數原型size_type size() const;
    返回容器的元素數目,時間複雜度爲O(1)O(1),我們看代碼實現:
_NODISCARD size_type size() const noexcept {
      return _Get_scary()->_Mysize;
}
  1. empty()
    函數原型bool empty() const noexcept;(c++11)
    函數判斷map是否爲空,時間複雜度爲O(1)O(1),代碼實現如下,只是調用了一下上邊的size函數,在使用中遇到需要判斷是否爲空的場景建議使用empty()而不是size() == 0.
_NODISCARD bool empty() const noexcept {
        return size() == 0;
    }
  1. max_size
    函數原型size_type max_size() const;
    map可以容納的最大元素數,有下面幾點需要知道:
  • c++標準中並沒有對最大容量的具體值做規定,這個返回值的大小取決於編譯器的實現。
  • 這個最大值只是一個邏輯上的上限,通常在達到此上限之前已經無法爲新元素分配內存
  • 時間複雜度爲O(1)O(1)
  1. 獲取
    map可以通過以下兩種方式獲取元素,操作符[]和函數at()

前文提到過,map通常基於二叉樹實現,因此獲取的時間複雜度,即是二叉樹查找的複雜度,也就是二叉樹的深度log(n)

#include <stdio.h>
#include <map>

int main()
{
   using std::map;
   std::map<int,double> _map;

   _map[2] = 3.14; // 添加新元素,鍵值爲2
   _map[5] = 2.18; // 添加新元素,鍵值爲5
   _map[2];        // 返回鍵值2對應的引用
   _map[10];       // 添加新元素,鍵值爲10
   _map.at(10);    // 返回鍵值10對應的引用
   _map.at(9);     // 鍵值9不存在,拋出異常

   return 0;
}

這兩種方法有以下不同,

[] at()
操作符 成員函數
鍵值不存在時,添加新元素,因此[]可用於添加操作 不存在時拋出異常
返回引用 被重載爲兩種形式,可返回const 引用或者引用,可用於const &的形參
  1. 添加新元素
    前邊提到,添加新元素可以通過操作符[]實現,也可以通過函數insert完成,時間複雜度log(n)
#include <iostream>
#include <map>

int main()
{
   std::map<int, int> _map;

   _map.insert(std::pair<int, int>(3, 100));
   _map.insert(std::pair<int, int>(6, 200));
   
   return 0;
}
  1. 刪除所有元素
    函數void clear();用於刪除所有元素,時間複雜度爲O(N)O(N)
  2. 刪除指定元素
void erase (iterator position);
size_type erase (const key_type& k);
void erase (iterator first, iterator last);

以上三個成員函數分別通過迭代器,鍵,和迭代器範圍刪除元素,前兩個刪除單個元素,第三個刪除多個元素。時間複雜度分別爲O(1)O(1),O(log(n))O(log(n)),O(n)O(n)

  1. find
iterator find (const key_type& k);
const_iterator find (const key_type& k) const;

find通過鍵k查找對應元素,若從查找到則返回對應迭代器,若未找到則返回map::end,複雜度爲log(n)

  1. count
size_type count (const key_type& k) const;

返回鍵k對應的元素數,返回結果只可能爲0或1,複雜度爲log(n)

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