什麼是 Map? Link to heading

std::map 是 C++ STL 中的關聯容器,儲存鍵值對(key-value pairs),鍵是唯一的,並且自動按鍵排序

與其他容器的主要區別

  • unordered_map:map 有序(紅黑樹),unordered_map 無序(哈希表)
  • multimap:map 的鍵唯一,multimap 允許重複鍵
  • set:map 儲存鍵值對,set 只儲存鍵
  • vector:map 通過鍵訪問,vector 通過索引訪問

適用場景

  • 需要鍵值對映射關係
  • 需要按鍵排序
  • 需要快速查找、插入、刪除(O(log n))
  • 鍵必須唯一
  • 需要範圍查詢或有序遍歷

內部實現原理 Link to heading

底層數據結構 Link to heading

std::map 使用紅黑樹(Red-Black Tree)實現,這是一種自平衡二元搜尋樹。

紅黑樹特性

  1. 每個節點是紅色或黑色
  2. 根節點是黑色
  3. 所有葉子節點(NIL)是黑色
  4. 紅色節點的兩個子節點都是黑色(不能有連續的紅色節點)
  5. 從任一節點到其每個葉子的路徑都包含相同數量的黑色節點

這些特性保證了樹的大致平衡,確保操作的時間複雜度為 O(log n)。

記憶體布局示意圖 Link to heading

std::map<int, string> myMap = {{3, "three"}, {1, "one"}, {5, "five"}, {2, "two"}, {4, "four"}};

紅黑樹結構(示意):
                    ┌─────────┐
                    │ 3:"three"│ (黑)
                    └─────────┘
                   /             \
            ┌─────────┐       ┌─────────┐
            │ 1:"one" │ (黑)  │ 5:"five"│ (黑)
            └─────────┘       └─────────┘
                 \            /
            ┌─────────┐  ┌─────────┐
            │ 2:"two" │  │ 4:"four"│
            └─────────┘  └─────────┘
              (紅)          (紅)

中序遍歷結果(自動排序):1, 2, 3, 4, 5

每個節點包含:
- 鍵(key)
- 值(value)
- 左子節點指標
- 右子節點指標
- 父節點指標
- 顏色標記(紅/黑)

特點

  1. 自動排序:中序遍歷得到有序序列
  2. 平衡性:樹高保持在 O(log n)
  3. 唯一鍵:每個鍵只出現一次
  4. 非連續記憶體:節點散布在堆記憶體中

關鍵操作的實現機制 Link to heading

查找操作

從根節點開始二元搜尋:
- 如果 key == 當前節點的鍵,找到
- 如果 key < 當前節點的鍵,往左子樹查找
- 如果 key > 當前節點的鍵,往右子樹查找
時間複雜度:O(log n)

插入操作

1. 按照 BST 規則插入新節點(紅色)
2. 如果違反紅黑樹性質,進行旋轉和重新著色
3. 最多 O(log n) 次旋轉
時間複雜度:O(log n)

刪除操作

1. 按照 BST 規則刪除節點
2. 如果違反紅黑樹性質,進行旋轉和重新著色
3. 最多 O(log n) 次旋轉
時間複雜度:O(log n)

基本使用 Link to heading

頭文件和宣告 Link to heading

#include <map>
#include <iostream>
#include <string>

std::map<int, std::string> myMap;                    // 空 map
std::map<int, std::string> myMap2 = {{1, "one"}, {2, "two"}};  // 初始化列表
std::map<std::string, int> myMap3;                   // string -> int 映射

構造方法 Link to heading

#include <map>
#include <iostream>
#include <string>

int main() {
    // 預設構造
    std::map<int, std::string> map1;

    // 初始化列表
    std::map<int, std::string> map2 = {
        {1, "one"},
        {2, "two"},
        {3, "three"}
    };

    // 拷貝構造
    std::map<int, std::string> map3(map2);

    // 移動構造
    std::map<int, std::string> map4(std::move(map3));

    // 範圍構造
    std::pair<int, std::string> arr[] = {{4, "four"}, {5, "five"}};
    std::map<int, std::string> map5(arr, arr + 2);

    // 自訂比較函式
    std::map<int, std::string, std::greater<int>> map6;  // 降序排列

    return 0;
}

常用成員函式 Link to heading

函式功能時間複雜度
operator[]訪問或插入元素O(log n)
at(key)訪問元素(有邊界檢查)O(log n)
insert(pair)插入鍵值對O(log n)
emplace(key, val)原地構造並插入O(log n)
erase(key)刪除指定鍵O(log n)
find(key)查找鍵,返回迭代器O(log n)
count(key)計算鍵的出現次數(0 或 1)O(log n)
lower_bound(key)返回 >= key 的第一個元素O(log n)
upper_bound(key)返回 > key 的第一個元素O(log n)
equal_range(key)返回等於 key 的範圍O(log n)
size()返回元素個數O(1)
empty()判斷是否為空O(1)
clear()清空所有元素O(n)

性能分析 Link to heading

時間複雜度表格 Link to heading

操作mapunordered_mapvector
查找O(log n)O(1) 平均O(n)
插入O(log n)O(1) 平均O(n)
刪除O(log n)O(1) 平均O(n)
有序遍歷O(n)需排序 O(n log n)
範圍查詢O(log n + k)O(n)
空間開銷大(節點 + 指標 + 顏色)大(哈希表)

空間複雜度分析 Link to heading

記憶體開銷

// 64 位元系統
sizeof(map<int, int>::node):
  key:    4 bytes
  value:  4 bytes
  left:   8 bytes (指標)
  right:  8 bytes (指標)
  parent: 8 bytes (指標)
  color:  1 byte
  總計:  ~40 bytes(含對齊)

100 個元素的 map<int, int>  4000 bytes
100 個元素的 vector<pair<int, int>>  800 bytes

map 記憶體開銷約為 vector  5 

何時性能最優/最差 Link to heading

性能最優

  • 需要有序儲存和遍歷
  • 需要範圍查詢(lower_bound, upper_bound)
  • 插入/刪除/查找頻繁且分散
  • 需要穩定的 O(log n) 性能保證

性能最差

  • 只需要快速查找(unordered_map 更快)
  • 資料量很小(vector 可能更快)
  • 不需要有序性
  • 記憶體非常受限

常見操作示例 Link to heading

插入元素 Link to heading

#include <map>
#include <iostream>
#include <string>

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

    // 方法1:使用 insert()
    myMap.insert({1, "one"});
    myMap.insert(std::make_pair(2, "two"));
    myMap.insert(std::pair<int, std::string>(3, "three"));

    // 方法2:使用 operator[]
    myMap[4] = "four";
    myMap[5] = "five";

    // 方法3:使用 emplace()(原地構造)
    myMap.emplace(6, "six");

    // 檢查插入是否成功
    auto result = myMap.insert({1, "ONE"});  // 鍵 1 已存在
    if (!result.second) {
        std::cout << "插入失敗,鍵 1 已存在" << std::endl;
    }

    // 輸出
    for (const auto& pair : myMap) {
        std::cout << pair.first << ": " << pair.second << std::endl;
    }

    return 0;
}

輸出

插入失敗,鍵 1 已存在
1: one
2: two
3: three
4: four
5: five
6: six

訪問元素 Link to heading

#include <map>
#include <iostream>
#include <string>

int main() {
    std::map<int, std::string> myMap = {
        {1, "one"}, {2, "two"}, {3, "three"}
    };

    // 方法1:使用 operator[](如果鍵不存在會插入)
    std::cout << "myMap[1] = " << myMap[1] << std::endl;
    std::cout << "myMap[10] = " << myMap[10] << std::endl;  // 插入 {10, ""}
    std::cout << "myMap.size() = " << myMap.size() << std::endl;  // 4

    // 方法2:使用 at()(如果鍵不存在會拋出異常)
    try {
        std::cout << "myMap.at(2) = " << myMap.at(2) << std::endl;
        std::cout << "myMap.at(20) = " << myMap.at(20) << std::endl;  // 異常
    } catch (const std::out_of_range& e) {
        std::cout << "錯誤: " << e.what() << std::endl;
    }

    // 方法3:使用 find()(最安全)
    auto it = myMap.find(3);
    if (it != myMap.end()) {
        std::cout << "找到 3: " << it->second << std::endl;
    }

    it = myMap.find(30);
    if (it == myMap.end()) {
        std::cout << "未找到 30" << std::endl;
    }

    return 0;
}

輸出

myMap[1] = one
myMap[10] =
myMap.size() = 4
myMap.at(2) = two
錯誤: map::at
找到 3: three
未找到 30

刪除元素 Link to heading

#include <map>
#include <iostream>
#include <string>

int main() {
    std::map<int, std::string> myMap = {
        {1, "one"}, {2, "two"}, {3, "three"}, {4, "four"}, {5, "five"}
    };

    // 方法1:通過鍵刪除
    myMap.erase(3);
    std::cout << "刪除 3 後,size = " << myMap.size() << std::endl;

    // 方法2:通過迭代器刪除
    auto it = myMap.find(2);
    if (it != myMap.end()) {
        myMap.erase(it);
    }

    // 方法3:刪除範圍
    auto first = myMap.find(1);
    auto last = myMap.find(5);
    myMap.erase(first, last);  // 刪除 [1, 5),即 1 和 4

    std::cout << "剩餘元素: ";
    for (const auto& pair : myMap) {
        std::cout << pair.first << " ";
    }
    std::cout << std::endl;

    return 0;
}

輸出

刪除 3 後,size = 4
剩餘元素: 5

查找與計數 Link to heading

#include <map>
#include <iostream>
#include <string>

int main() {
    std::map<std::string, int> scores = {
        {"Alice", 95},
        {"Bob", 87},
        {"Charlie", 92},
        {"David", 88}
    };

    // 使用 find() 查找
    auto it = scores.find("Bob");
    if (it != scores.end()) {
        std::cout << it->first << " 的分數: " << it->second << std::endl;
    }

    // 使用 count() 檢查是否存在
    if (scores.count("Charlie") > 0) {
        std::cout << "Charlie 存在" << std::endl;
    }

    if (scores.count("Eve") == 0) {
        std::cout << "Eve 不存在" << std::endl;
    }

    // 使用 contains()(C++20)
    // if (scores.contains("Alice")) {
    //     std::cout << "Alice 存在" << std::endl;
    // }

    return 0;
}

輸出

Bob 的分數: 87
Charlie 存在
Eve 不存在

遍歷容器 Link to heading

#include <map>
#include <iostream>
#include <string>

int main() {
    std::map<int, std::string> myMap = {
        {3, "three"}, {1, "one"}, {4, "four"}, {2, "two"}
    };

    // 方法1:範圍 for 迴圈(自動按鍵排序)
    std::cout << "方法1(自動排序): " << std::endl;
    for (const auto& pair : myMap) {
        std::cout << pair.first << ": " << pair.second << std::endl;
    }

    // 方法2:迭代器
    std::cout << "\n方法2(迭代器): " << std::endl;
    for (auto it = myMap.begin(); it != myMap.end(); ++it) {
        std::cout << it->first << ": " << it->second << std::endl;
    }

    // 方法3:反向遍歷
    std::cout << "\n方法3(反向): " << std::endl;
    for (auto it = myMap.rbegin(); it != myMap.rend(); ++it) {
        std::cout << it->first << ": " << it->second << std::endl;
    }

    // 方法4:結構化綁定(C++17)
    std::cout << "\n方法4(結構化綁定): " << std::endl;
    for (const auto& [key, value] : myMap) {
        std::cout << key << ": " << value << std::endl;
    }

    return 0;
}

輸出

方法1(自動排序):
1: one
2: two
3: three
4: four

方法2(迭代器):
1: one
2: two
3: three
4: four

方法3(反向):
4: four
3: three
2: two
1: one

方法4(結構化綁定):
1: one
2: two
3: three
4: four

範圍查詢 Link to heading

#include <map>
#include <iostream>

int main() {
    std::map<int, std::string> myMap = {
        {10, "ten"}, {20, "twenty"}, {30, "thirty"},
        {40, "forty"}, {50, "fifty"}
    };

    // lower_bound:>= key 的第一個元素
    auto lb = myMap.lower_bound(25);
    if (lb != myMap.end()) {
        std::cout << "lower_bound(25): " << lb->first << std::endl;  // 30
    }

    // upper_bound:> key 的第一個元素
    auto ub = myMap.upper_bound(30);
    if (ub != myMap.end()) {
        std::cout << "upper_bound(30): " << ub->first << std::endl;  // 40
    }

    // equal_range:等於 key 的範圍
    auto range = myMap.equal_range(30);
    std::cout << "equal_range(30): ";
    for (auto it = range.first; it != range.second; ++it) {
        std::cout << it->first << " ";
    }
    std::cout << std::endl;

    // 查詢範圍 [20, 40)
    std::cout << "\n範圍 [20, 40) 的元素: " << std::endl;
    auto start = myMap.lower_bound(20);
    auto end = myMap.lower_bound(40);
    for (auto it = start; it != end; ++it) {
        std::cout << it->first << ": " << it->second << std::endl;
    }

    return 0;
}

輸出

lower_bound(25): 30
upper_bound(30): 40
equal_range(30): 30

範圍 [20, 40) 的元素:
20: twenty
30: thirty

迭代器支持 Link to heading

支援的迭代器類型 Link to heading

std::map 提供雙向迭代器(bidirectional iterator):

#include <map>
#include <iostream>

int main() {
    std::map<int, std::string> myMap = {{1, "one"}, {2, "two"}, {3, "three"}};

    auto it = myMap.begin();

    // 雙向迭代器支援的操作
    ++it;      // 前進
    --it;      // 後退
    it->first;   // 訪問鍵
    it->second;  // 訪問值

    // 不支援的操作
    // it + 3;    // 錯誤!不支援隨機訪問
    // it[2];     // 錯誤!不支援下標操作

    // 使用 std::advance 移動
    std::advance(it, 2);
    std::cout << "移動後: " << it->first << std::endl;

    return 0;
}

迭代器失效問題 Link to heading

map 的特點:插入操作不會使任何迭代器失效,刪除操作只使被刪除元素的迭代器失效

#include <map>
#include <iostream>

int main() {
    std::map<int, std::string> myMap = {
        {1, "one"}, {2, "two"}, {3, "three"}, {4, "four"}, {5, "five"}
    };

    auto it1 = myMap.find(1);
    auto it2 = myMap.find(3);
    auto it5 = myMap.find(5);

    std::cout << "刪除前: it1=" << it1->first
              << ", it2=" << it2->first
              << ", it5=" << it5->first << std::endl;

    // 刪除 3
    myMap.erase(3);

    // it1 和 it5 仍然有效!
    std::cout << "刪除後: it1=" << it1->first
              << ", it5=" << it5->first << std::endl;
    // it2 已失效,不能使用

    // 插入也不會使迭代器失效
    myMap.insert({6, "six"});
    std::cout << "插入後: it1=" << it1->first
              << ", it5=" << it5->first << std::endl;

    return 0;
}

輸出

刪除前: it1=1, it2=3, it5=5
刪除後: it1=1, it5=5
插入後: it1=1, it5=5

迭代器失效規則總結

操作失效範圍
insert/emplace無失效
erase(key)僅被刪除元素的迭代器失效
erase(iterator)僅該迭代器失效
clear()所有迭代器失效
operator[]無失效

常見陷阱與注意事項 Link to heading

1. operator[] 會插入不存在的鍵 Link to heading

std::map<int, std::string> myMap = {{1, "one"}, {2, "two"}};

// ❌ 常見錯誤:以為只是查詢
std::string val = myMap[3];  // 插入了 {3, ""}!
std::cout << "size: " << myMap.size() << std::endl;  // 3

// ✅ 正確:使用 find() 或 at()
auto it = myMap.find(3);
if (it != myMap.end()) {
    std::string val = it->second;
}

// 或使用 at()(會拋出異常)
try {
    std::string val = myMap.at(3);
} catch (...) {
    // 處理不存在的情況
}

2. 鍵必須支援比較運算 Link to heading

struct Point {
    int x, y;
};

// ❌ 錯誤:Point 沒有定義 operator<
// std::map<Point, std::string> myMap;  // 編譯錯誤!

// ✅ 解決方案1:定義 operator<
struct Point {
    int x, y;
    bool operator<(const Point& other) const {
        if (x != other.x) return x < other.x;
        return y < other.y;
    }
};

// ✅ 解決方案2:提供自訂比較函式
struct PointCompare {
    bool operator()(const Point& a, const Point& b) const {
        if (a.x != b.x) return a.x < b.x;
        return a.y < b.y;
    }
};
std::map<Point, std::string, PointCompare> myMap;

3. 不要在遍歷時修改容器結構 Link to heading

std::map<int, std::string> myMap = {{1, "one"}, {2, "two"}, {3, "three"}};

// ❌ 錯誤:遍歷時刪除會使迭代器失效
for (auto it = myMap.begin(); it != myMap.end(); ++it) {
    if (it->first == 2) {
        myMap.erase(it);  // it 失效,++it 未定義行為!
    }
}

// ✅ 正確:使用 erase 的返回值
for (auto it = myMap.begin(); it != myMap.end(); ) {
    if (it->first == 2) {
        it = myMap.erase(it);  // erase 返回下一個有效迭代器
    } else {
        ++it;
    }
}

4. 自訂比較函式要滿足嚴格弱序 Link to heading

// ❌ 錯誤:不滿足嚴格弱序
struct BadCompare {
    bool operator()(int a, int b) const {
        return a <= b;  // 錯誤!應該是 <
    }
};
// 這會導致未定義行為

// ✅ 正確:使用 < 運算子
struct GoodCompare {
    bool operator()(int a, int b) const {
        return a < b;
    }
};

5. 值類型的拷貝開銷 Link to heading

struct LargeObject {
    char data[1000];
};

std::map<int, LargeObject> myMap;

// ❌ 效率低:返回拷貝
LargeObject obj = myMap[1];  // 拷貝 1000 bytes

// ✅ 推薦:使用參考
const LargeObject& obj = myMap.at(1);  // 無拷貝

// ✅ 或使用指標
auto it = myMap.find(1);
if (it != myMap.end()) {
    const LargeObject& obj = it->second;  // 無拷貝
}

實際應用場景 Link to heading

場景 1:單字計數器 Link to heading

統計文本中每個單字出現的次數。

#include <map>
#include <iostream>
#include <string>
#include <sstream>

std::map<std::string, int> wordCount(const std::string& text) {
    std::map<std::string, int> counts;
    std::istringstream iss(text);
    std::string word;

    while (iss >> word) {
        // 移除標點符號(簡化處理)
        word.erase(std::remove_if(word.begin(), word.end(), ::ispunct), word.end());

        // 轉小寫
        std::transform(word.begin(), word.end(), word.begin(), ::tolower);

        // 計數
        counts[word]++;  // 如果不存在會自動插入 {word, 0} 然後 +1
    }

    return counts;
}

int main() {
    std::string text = "Hello world! Hello C++ programming. "
                       "C++ is great for programming.";

    auto counts = wordCount(text);

    std::cout << "單字出現次數(按字母順序):" << std::endl;
    for (const auto& [word, count] : counts) {
        std::cout << word << ": " << count << std::endl;
    }

    return 0;
}

輸出

單字出現次數(按字母順序):
c: 2
for: 1
great: 1
hello: 2
is: 1
programming: 2
world: 1

場景 2:學生成績管理系統 Link to heading

#include <map>
#include <iostream>
#include <string>
#include <iomanip>

class GradeManager {
private:
    std::map<std::string, std::map<std::string, double>> grades;
    // 外層 map:學生名 -> 內層 map
    // 內層 map:科目 -> 分數

public:
    void addGrade(const std::string& student, const std::string& subject, double score) {
        grades[student][subject] = score;
    }

    double getGrade(const std::string& student, const std::string& subject) const {
        auto studentIt = grades.find(student);
        if (studentIt != grades.end()) {
            auto subjectIt = studentIt->second.find(subject);
            if (subjectIt != studentIt->second.end()) {
                return subjectIt->second;
            }
        }
        return -1;  // 未找到
    }

    double getAverage(const std::string& student) const {
        auto it = grades.find(student);
        if (it == grades.end() || it->second.empty()) {
            return 0;
        }

        double sum = 0;
        for (const auto& [subject, score] : it->second) {
            sum += score;
        }
        return sum / it->second.size();
    }

    void printReport() const {
        std::cout << std::fixed << std::setprecision(2);
        for (const auto& [student, subjects] : grades) {
            std::cout << "\n學生: " << student << std::endl;
            for (const auto& [subject, score] : subjects) {
                std::cout << "  " << subject << ": " << score << std::endl;
            }
            std::cout << "  平均: " << getAverage(student) << std::endl;
        }
    }
};

int main() {
    GradeManager gm;

    gm.addGrade("Alice", "Math", 95);
    gm.addGrade("Alice", "English", 88);
    gm.addGrade("Alice", "Physics", 92);

    gm.addGrade("Bob", "Math", 87);
    gm.addGrade("Bob", "English", 90);

    gm.addGrade("Charlie", "Math", 78);
    gm.addGrade("Charlie", "Physics", 85);

    gm.printReport();

    std::cout << "\nAlice 的數學成績: " << gm.getGrade("Alice", "Math") << std::endl;

    return 0;
}

輸出

學生: Alice
  English: 88.00
  Math: 95.00
  Physics: 92.00
  平均: 91.67

學生: Bob
  English: 90.00
  Math: 87.00
  平均: 88.50

學生: Charlie
  Math: 78.00
  Physics: 85.00
  平均: 81.50

Alice 的數學成績: 95.00

場景 3:配置檔案解析器 Link to heading

#include <map>
#include <iostream>
#include <string>
#include <fstream>
#include <sstream>

class ConfigParser {
private:
    std::map<std::string, std::string> config;

public:
    bool loadFromString(const std::string& content) {
        std::istringstream iss(content);
        std::string line;

        while (std::getline(iss, line)) {
            // 移除註解
            size_t commentPos = line.find('#');
            if (commentPos != std::string::npos) {
                line = line.substr(0, commentPos);
            }

            // 解析 key=value
            size_t equalPos = line.find('=');
            if (equalPos != std::string::npos) {
                std::string key = line.substr(0, equalPos);
                std::string value = line.substr(equalPos + 1);

                // 移除前後空白
                key.erase(0, key.find_first_not_of(" \t"));
                key.erase(key.find_last_not_of(" \t") + 1);
                value.erase(0, value.find_first_not_of(" \t"));
                value.erase(value.find_last_not_of(" \t") + 1);

                if (!key.empty()) {
                    config[key] = value;
                }
            }
        }

        return true;
    }

    std::string get(const std::string& key, const std::string& defaultValue = "") const {
        auto it = config.find(key);
        return (it != config.end()) ? it->second : defaultValue;
    }

    int getInt(const std::string& key, int defaultValue = 0) const {
        auto it = config.find(key);
        if (it != config.end()) {
            try {
                return std::stoi(it->second);
            } catch (...) {
                return defaultValue;
            }
        }
        return defaultValue;
    }

    void print() const {
        std::cout << "配置項目:" << std::endl;
        for (const auto& [key, value] : config) {
            std::cout << "  " << key << " = " << value << std::endl;
        }
    }
};

int main() {
    std::string configContent = R"(
# 伺服器配置
server_host = localhost
server_port = 8080

# 資料庫配置
db_host = 127.0.0.1
db_port = 3306
db_name = mydb
db_user = admin

# 其他設定
max_connections = 100
timeout = 30  # 
)";

    ConfigParser parser;
    parser.loadFromString(configContent);

    parser.print();

    std::cout << "\n查詢配置:" << std::endl;
    std::cout << "Server: " << parser.get("server_host")
              << ":" << parser.getInt("server_port") << std::endl;
    std::cout << "Max connections: " << parser.getInt("max_connections") << std::endl;

    return 0;
}

輸出

配置項目:
  db_host = 127.0.0.1
  db_name = mydb
  db_port = 3306
  db_user = admin
  max_connections = 100
  server_host = localhost
  server_port = 8080
  timeout = 30

查詢配置:
Server: localhost:8080
Max connections: 100

與其他容器的選擇 Link to heading

對比表格 Link to heading

特性mapunordered_mapmultimapset
有序性✅ 有序❌ 無序✅ 有序✅ 有序
鍵唯一✅ 唯一✅ 唯一❌ 可重複✅ 唯一
查找O(log n)O(1) 平均O(log n)O(log n)
插入O(log n)O(1) 平均O(log n)O(log n)
刪除O(log n)O(1) 平均O(log n)O(log n)
範圍查詢
記憶體開銷
最壞情況O(log n)O(n)O(log n)O(log n)

選擇建議 Link to heading

選擇 map 當

  • ✅ 需要鍵值對映射
  • ✅ 需要按鍵排序
  • ✅ 需要範圍查詢
  • ✅ 需要穩定的 O(log n) 性能
  • ✅ 鍵必須唯一

選擇 unordered_map 當

  • ✅ 只需要快速查找(不需要排序)
  • ✅ 插入/刪除/查找是主要操作
  • ✅ 記憶體不是極度受限
  • ✅ 可以接受最壞情況 O(n)

選擇 multimap 當

  • ✅ 需要允許重複鍵
  • ✅ 需要按鍵排序
  • ✅ 一對多映射關係

選擇 set 當

  • ✅ 只需要儲存鍵(不需要值)
  • ✅ 需要去重和排序

實務建議 Link to heading

DO(應該做的) Link to heading

1. ✅ 使用 find() 而非 operator[] 查詢 Link to heading

std::map<int, std::string> myMap = {{1, "one"}, {2, "two"}};

// ✅ 推薦:使用 find()(不會插入)
auto it = myMap.find(3);
if (it != myMap.end()) {
    std::cout << it->second << std::endl;
}

// ❌ 避免:使用 operator[](會插入 {3, ""})
// if (!myMap[3].empty()) {  // 已經插入了!
//     std::cout << myMap[3] << std::endl;
// }

2. ✅ 使用 emplace 減少複製 Link to heading

std::map<int, std::string> myMap;

// ✅ 推薦:使用 emplace(原地構造)
myMap.emplace(1, "one");

// ❌ 避免:使用 insert(可能有額外複製)
// myMap.insert({1, "one"});
// myMap.insert(std::make_pair(1, "one"));

3. ✅ 使用結構化綁定簡化代碼(C++17) Link to heading

std::map<std::string, int> myMap = {{"Alice", 95}, {"Bob", 87}};

// ✅ 推薦:結構化綁定(清晰易讀)
for (const auto& [name, score] : myMap) {
    std::cout << name << ": " << score << std::endl;
}

// ❌ 較繁瑣:使用 pair
// for (const auto& pair : myMap) {
//     std::cout << pair.first << ": " << pair.second << std::endl;
// }

4. ✅ 利用 lower_bound/upper_bound 進行範圍查詢 Link to heading

std::map<int, std::string> myMap = {
    {10, "ten"}, {20, "twenty"}, {30, "thirty"}, {40, "forty"}
};

// ✅ 推薦:使用 lower_bound/upper_bound
auto start = myMap.lower_bound(15);
auto end = myMap.upper_bound(35);

std::cout << "範圍 [15, 35] 的元素:" << std::endl;
for (auto it = start; it != end; ++it) {
    std::cout << it->first << ": " << it->second << std::endl;
}

5. ✅ 使用 at() 進行安全訪問 Link to heading

std::map<int, std::string> myMap = {{1, "one"}, {2, "two"}};

// ✅ 推薦:使用 at()(有異常保護)
try {
    std::string val = myMap.at(10);
} catch (const std::out_of_range&) {
    std::cout << "鍵不存在" << std::endl;
}

// ❌ 危險:使用 operator[]
// std::string val = myMap[10];  // 插入 {10, ""}

6. ✅ 遍歷時刪除使用 erase 返回值 Link to heading

std::map<int, std::string> myMap = {{1, "one"}, {2, "two"}, {3, "three"}};

// ✅ 推薦:使用 erase 的返回值
for (auto it = myMap.begin(); it != myMap.end(); ) {
    if (it->first % 2 == 0) {
        it = myMap.erase(it);  // 返回下一個有效迭代器
    } else {
        ++it;
    }
}

7. ✅ 使用 count() 檢查存在性 Link to heading

std::map<std::string, int> myMap = {{"Alice", 95}, {"Bob", 87}};

// ✅ 推薦:使用 count()(清晰表達意圖)
if (myMap.count("Charlie") > 0) {
    // 存在
}

// ✅ 也可以:使用 find()
if (myMap.find("Charlie") != myMap.end()) {
    // 存在
}

8. ✅ 使用 const 參考傳遞 map Link to heading

// ✅ 推薦:const 參考(避免拷貝)
void processMap(const std::map<int, std::string>& myMap) {
    for (const auto& [key, value] : myMap) {
        // 處理...
    }
}

// ❌ 避免:值傳遞(拷貝整個 map)
// void processMap(std::map<int, std::string> myMap) {
//     // 拷貝開銷大!
// }

DON’T(不應該做的) Link to heading

1. ❌ 不要使用 operator[] 檢查存在性 Link to heading

std::map<int, std::string> myMap = {{1, "one"}, {2, "two"}};

// ❌ 錯誤:會插入不存在的鍵
if (myMap[3].empty()) {  // 已經插入 {3, ""}!
    std::cout << "不存在" << std::endl;
}
std::cout << "size: " << myMap.size() << std::endl;  // 3

// ✅ 正確:使用 find() 或 count()
if (myMap.find(3) == myMap.end()) {
    std::cout << "不存在" << std::endl;
}
std::cout << "size: " << myMap.size() << std::endl;  // 2

2. ❌ 不要在不需要排序時使用 map Link to heading

// ❌ 浪費:只需要快速查找,不需要排序
std::map<std::string, int> cache;  // O(log n) 查找

// ✅ 推薦:使用 unordered_map
std::unordered_map<std::string, int> cache;  // O(1) 平均查找

3. ❌ 不要修改鍵 Link to heading

std::map<int, std::string> myMap = {{1, "one"}, {2, "two"}};

auto it = myMap.find(1);
if (it != myMap.end()) {
    // ❌ 錯誤:不能修改鍵(編譯錯誤)
    // it->first = 10;

    // ✅ 正確:只能修改值
    it->second = "ONE";
}

4. ❌ 不要忘記檢查 insert 的返回值 Link to heading

std::map<int, std::string> myMap = {{1, "one"}};

// ❌ 忽略返回值:不知道是否插入成功
myMap.insert({1, "ONE"});  // 插入失敗,鍵 1 已存在

// ✅ 檢查返回值
auto [it, success] = myMap.insert({1, "ONE"});
if (!success) {
    std::cout << "插入失敗,鍵已存在" << std::endl;
    // 如果需要更新,使用 operator[] 或 insert_or_assign
    myMap[1] = "ONE";
}

5. ❌ 不要對大型值使用值傳遞 Link to heading

struct LargeValue {
    char data[10000];
};

std::map<int, LargeValue> myMap;

// ❌ 避免:返回值拷貝
void processValue(LargeValue val) {  // 拷貝 10000 bytes
    // 處理...
}

auto it = myMap.find(1);
if (it != myMap.end()) {
    processValue(it->second);  // 拷貝!
}

// ✅ 推薦:使用 const 參考
void processValue(const LargeValue& val) {
    // 處理...
}

auto it = myMap.find(1);
if (it != myMap.end()) {
    processValue(it->second);  // 無拷貝
}

6. ❌ 不要假設插入順序 Link to heading

std::map<int, std::string> myMap;

myMap.insert({3, "three"});
myMap.insert({1, "one"});
myMap.insert({2, "two"});

// ❌ 錯誤假設:按插入順序遍歷
// 實際上會按鍵排序:1, 2, 3

for (const auto& [key, value] : myMap) {
    std::cout << key << " ";  // 輸出: 1 2 3(不是 3 1 2)
}

7. ❌ 不要在性能關鍵路徑頻繁插入刪除 Link to heading

std::map<int, int> myMap;

// ❌ 效率低:頻繁插入刪除(每次 O(log n) + 平衡操作)
for (int i = 0; i < 1000000; ++i) {
    myMap[i] = i * 2;
}
for (int i = 0; i < 500000; ++i) {
    myMap.erase(i);
}

// ✅ 考慮:如果不需要排序,使用 unordered_map
// ✅ 或者:批次操作,減少插入刪除次數

8. ❌ 不要忽略自訂型別的比較函式 Link to heading

struct Person {
    std::string name;
    int age;
};

// ❌ 錯誤:Person 沒有定義 operator<
// std::map<Person, int> myMap;  // 編譯錯誤!

// ✅ 解決方案:定義 operator< 或提供比較函式
struct Person {
    std::string name;
    int age;

    bool operator<(const Person& other) const {
        if (name != other.name) return name < other.name;
        return age < other.age;
    }
};

std::map<Person, int> myMap;  // OK

小結 Link to heading

核心概念總結 Link to heading

  1. 有序鍵值對:map 儲存鍵值對並自動按鍵排序,基於紅黑樹實現
  2. 唯一鍵:每個鍵只能出現一次,重複插入會失敗
  3. O(log n) 操作:插入、刪除、查找都是 O(log n),穩定可靠
  4. 範圍查詢:支援 lower_bound、upper_bound 等範圍操作
  5. 迭代器穩定:插入不使迭代器失效,刪除只使被刪除元素失效

關鍵記憶點 Link to heading

  • 適合場景:需要有序鍵值對、範圍查詢、穩定性能保證
  • 不適合場景:只需快速查找(用 unordered_map)、不需排序
  • 🔧 核心優勢:自動排序、範圍查詢、穩定 O(log n)
  • ⚠️ 主要問題:operator[] 會插入、比 unordered_map 慢、記憶體開銷大

一句話總結 Link to heading

std::map 是基於紅黑樹的有序關聯容器,儲存唯一鍵值對並自動排序,提供穩定的 O(log n) 查找、插入、刪除性能和範圍查詢能力,適合需要有序性和穩定性能的鍵值映射場景。