介紹
- 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有以下需要注意:
- 要重載比較操作符,如果類的“大小”無法比較,因爲那麼在將新元素插入套紅黑樹的時候,咦?我的位置在哪呢?
- 程序實現時候要注意如果在類內重載
<
,那麼這個“函數”必須是const
的,,即this
指針的類型等價於const &
否則還是不能用 - 上邊的操作符重載也可以通過友元實現,但是與第二條一樣參數類型必須是
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;
}
常用成員函數
- size()
函數原型size_type size() const;
返回容器的元素數目,時間複雜度爲,我們看代碼實現:
_NODISCARD size_type size() const noexcept {
return _Get_scary()->_Mysize;
}
- empty()
函數原型bool empty() const noexcept;
(c++11)
函數判斷map是否爲空,時間複雜度爲,代碼實現如下,只是調用了一下上邊的size函數,在使用中遇到需要判斷是否爲空的場景建議使用empty()
而不是size() == 0
.
_NODISCARD bool empty() const noexcept {
return size() == 0;
}
- max_size
函數原型size_type max_size() const;
map可以容納的最大元素數,有下面幾點需要知道:
- c++標準中並沒有對最大容量的具體值做規定,這個返回值的大小取決於編譯器的實現。
- 這個最大值只是一個邏輯上的上限,通常在達到此上限之前已經無法爲新元素分配內存
- 時間複雜度爲
- 獲取
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 &的形參 |
- 添加新元素
前邊提到,添加新元素可以通過操作符[]
實現,也可以通過函數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;
}
- 刪除所有元素
函數void clear();
用於刪除所有元素,時間複雜度爲 - 刪除指定元素
void erase (iterator position);
size_type erase (const key_type& k);
void erase (iterator first, iterator last);
以上三個成員函數分別通過迭代器,鍵,和迭代器範圍刪除元素,前兩個刪除單個元素,第三個刪除多個元素。時間複雜度分別爲,,
- find
iterator find (const key_type& k);
const_iterator find (const key_type& k) const;
find通過鍵k
查找對應元素,若從查找到則返回對應迭代器,若未找到則返回map::end
,複雜度爲log(n)
。
- count
size_type count (const key_type& k) const;
返回鍵k
對應的元素數,返回結果只可能爲0或1,複雜度爲log(n)
。