完整教程:STL map/set 全家桶完全攻略:从原理到实战(含 LeetCode 题解)

在 C++ STL 容器中,map 和 set 系列是高频使用的关联式容器。与 vector、list 等序列式容器不同,它们基于红黑树实现,具备自动排序、高效查找等特性,在去重、键值映射、区间查询等场景中堪称 "神器"。本文将从底层原理、核心接口、使用差异到实战应用,全面拆解 map/set 全家桶的使用技巧,所有示例均来自提供的代码文件及 PDF 文档,确保实用性和准确性。

一、容器分类:序列式 vs 关联式

在学习 map/set 之前,我们需要先明确 STL 容器的核心分类:

类型代表容器核心特征访问方式
序列式容器vector、list、deque线性存储,元素间无强关联按存储位置访问
关联式容器map/set、unordered_map/unordered_set非线性结构(红黑树 / 哈希表),元素间通过关键字关联按关键字访问

本文重点讲解基于红黑树实现的 map/set 系列(包括 multimap/multiset),其核心优势是:

  • 增删查效率稳定在 O (log N)
  • 迭代遍历为中序遍历,天然有序
  • 支持关键字排序、区间查询等高级操作

二、set 系列:有序去重的 "集合神器"

set 系列以关键字为核心,专注于 "集合" 相关操作,分为 set 和 multiset 两个版本。

2.1 核心特性与模板参数

set 的模板声明如下:

template <
    class T,  // 关键字类型(也是值类型)
    class Compare = less,  // 比较仿函数(默认升序)
    class Alloc = allocator  // 空间配置器(默认无需修改)
> class set;

关键特性:

  • 底层红黑树实现,元素自动排序
  • 不支持重复元素(去重特性)
  • 迭代器为 const 属性,无法修改元素(避免破坏红黑树结构)
  • 支持正向 / 反向迭代,默认升序遍历

2.2 核心接口使用

(1)构造方式

支持 4 种常用构造,满足不同初始化需求:

// 1. 无参构造
set s1;
// 2. 迭代器区间构造(常用数组转set)
int arr[] = {4,2,7,2,8};
set s2(arr, arr+sizeof(arr)/sizeof(int));
// 3. 拷贝构造
set s3(s2);
// 4. 初始化列表构造
set s4 = {1,3,5,7,9};
(2)增删查操作
#include 
#include 
using namespace std;
int main() {
    set s;
    // 插入:重复元素插入失败
    s.insert(5);
    s.insert(2);
    s.insert(7);
    s.insert(5);  // 插入失败
    s.insert({2,8,3,9});  // 列表插入
    // 遍历:默认升序,支持范围for
    for (auto e : s) {
        cout << e << " ";  // 输出:2 3 5 7 8 9
    }
    cout << endl;
    // 查找:find返回迭代器,count返回存在个数(0或1)
    auto pos = s.find(3);
    if (pos != s.end()) {
        cout << "找到元素:" << *pos << endl;
    }
    // 删除:支持迭代器、值、区间三种方式
    s.erase(5);  // 按值删除
    s.erase(s.begin());  // 按迭代器删除
    auto itlow = s.lower_bound(7);  // >=7的迭代器
    auto itup = s.upper_bound(8);   // >8的迭代器
    s.erase(itlow, itup);  // 区间删除
    for (auto e : s) {
        cout << e << " ";  // 输出:3
    }
    return 0;
}
(3)count 接口判重示例
#include 
#include 
#include 
using namespace std;
int main() {
	set s = { 4,2,7,2,8,5,9 };
	int x;
	cin >> x;
	if (s.count(x)) { // count用来统计指定字符在set中的个数
		cout << x << "在!" << endl;
	}
	else {
		cout << x << "不存在!" << endl;
	}
	return 0;
}
(4)lower_bound 与 upper_bound 区间操作示例
#include 
#include 
using namespace std;
int main()
{
    std::set myset;
    for (int i = 1; i < 10; i++)
        myset.insert(i * 10); // 10 20 30 40 50 60 70 80 90
    for (auto e : myset)
    {
        cout << e << " ";
    }
    cout << endl;
    // 实现查找到的 [itlow, itup) 包含 [30, 60] 区间
    // 返回 >= 30
    auto itlow = myset.lower_bound(30);
    // 返回 > 60 的下一个元素的迭代器
    auto itup = myset.upper_bound(60); // 左闭右开
    // 删除这段区间的值
    myset.erase(itlow, itup);
    for (auto e : myset)
    {
        cout << e << " ";
    }
    cout << endl;
    return 0;
}

2.3 set 与 multiset 的核心差异

multiset 是 set 的 "允许重复" 版本,核心差异如下:

特性setmultiset
元素重复性不允许重复支持重复元素
find 操作返回唯一元素迭代器返回中序遍历第一个匹配元素
count 操作返回 0 或 1返回元素实际个数
erase 操作(按值)删除单个元素删除所有匹配元素

multiset 使用示例:

#include 
#include 
using namespace std;
int main()
{
    // 排序但不去重
    multiset ms = {4,2,7,2,4,8,4,5,4,9};
    for (auto e : ms) {
        cout << e << " ";  // 输出:2 2 4 4 4 4 5 7 8 9
    }
    cout << endl;
    // 查找并遍历所有重复元素
    int x = 4;
    auto pos = ms.find(x);
    while (pos != ms.end() && *pos == x) {
        cout << *pos << " ";  // 输出:4 4 4 4
        ++pos;
    }
    cout << endl;
    // 统计重复个数
    cout << "元素" << x << "的个数:" << ms.count(x) << endl;  // 输出:4
    // 删除所有匹配元素
    ms.erase(x);
    for (auto e : ms) {
        cout << e << " ";  // 输出:2 2 5 7 8 9
    }
    return 0;
}

三、map 系列:键值映射的 "字典利器"

map 系列专注于 "键值对"(key-value)存储,是实现字典、哈希表功能的核心容器,分为 map 和 multimap 两个版本。

3.1 核心特性与模板参数

map 的模板声明如下:

template <
    class Key,  // 关键字类型
    class T,    // 映射值类型
    class Compare = less,  // 关键字比较仿函数
    class Alloc = allocator>  // 空间配置器
> class map;

关键特性:

  • 底层红黑树实现,按 key 自动排序
  • 每个节点存储 pair<const Key, T> 键值对
  • key 不允许重复,支持通过 key 快速查找 value
  • 支持修改 value,但不允许修改 key(避免破坏红黑树结构)

3.2 核心接口与 pair 类型

map 的元素是 pair 类型,常用构造 pair 的方式:

#include 
#include 
#include 
using namespace std;
int main() {
	// initializer_list构造及迭代遍历
	pair kv1("left", "左边");  // 正常创建pair对象
	map dict;
	dict.insert(kv1);
	dict.insert(pair("right", "右边"));  // 使用匿名对象创建
	dict.insert(make_pair("insert", "插入"));  // 使用模版函数创建
	dict.insert({ "string","字符串" });  // 走隐式类型转换(C++11及以上支持)
	auto it = dict.begin();
	while (it != dict.end()) {
		//(*it).first = "sd";  // key不支持修改
		(*it).second += "250";  // value支持修改
		cout << it->first << ":" << it->second << "  ";  // 使用pair重定义的->
		it++;
	}
	cout << endl;
}

3.3 核心操作:增删查改

(1)operator [] 的强大功能

map 的 operator [] 是多功能复合接口,支持插入、查找、修改三种操作,使用示例:

#include 
#include 
#include 
using namespace std;
int main()
{
    map dict;
    dict.insert(make_pair("sort", "排序"));
    // key不存在->插入 {"insert", string()}
    dict["insert"];
    // 插入+修改
    dict["left"] = "左边";
    // 修改
    dict["left"] = "左边、剩余";
    // key存在->查找(必须得确保left存在,不然就变插入了)
    cout << dict["left"] << endl;
    return 0;
}
(2)经典场景:统计元素出现次数

利用 operator [] 的简洁性,可快速实现元素计数:

#include 
#include 
#include 
using namespace std;
int main()
{
    // 利用find和iterator修改功能,统计水果出现的次数
    string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜",
                    "苹果", "香蕉", "苹果", "香蕉" };
    map countMap;
    // 利用[]插入+修改功能,巧妙实现统计
    for (const auto& str: arr) {
        countMap[str]++;  // []能有插入修改的功能,返回对应键的value引用
    }
    for (const auto& e : countMap)
    {
        cout << e.first << ":" << e.second << endl;
    }
    cout << endl;
    return 0;
}

3.4 map 与 multimap 的核心差异

multimap 支持 key 重复,核心差异如下:

特性mapmultimap
key 重复性不允许重复支持重复 key
operator[]支持(插入 / 查找 / 修改)不支持(key 重复无法定位唯一 value)
find 操作返回唯一 key 的迭代器返回中序第一个匹配 key 的迭代器
遍历重复 key无需特殊处理需用 equal_range 获取迭代器区间

multimap 使用示例:

#include 
#include 
int main()
{
    std::multimap mymm;
    mymm.insert(std::pair('a', 10));
    mymm.insert(std::pair('b', 20));
    mymm.insert(std::pair('b', 30));
    mymm.insert(std::pair('b', 40));
    mymm.insert(std::pair('c', 50));
    mymm.insert(std::pair('c', 60));
    mymm.insert(std::pair('d', 60));
    std::cout << "mymm contains:\n";
    for (char ch = 'a'; ch <= 'd'; ch++)
    {
        std::pair ::iterator, std::multimap::iterator> ret;
        ret = mymm.equal_range(ch);  // 返回一段迭代区间
        std::cout << ch << " =>";
        for (std::multimap::iterator it = ret.first; it != ret.second; ++it)
            std::cout << ' ' << it->second;
        std::cout << '\n';
    }
    return 0;
}

四、实战演练:LeetCode 经典题解

map/set 系列在算法题中应用广泛,以下是 3 道经典 LeetCode 题的最优解,均基于提供的代码实现。

4.1 两个数组的交集(LeetCode 349)

题目:给定两个数组,计算它们的交集(元素唯一)。思路:利用 set 的去重和有序特性,双指针遍历比较。

#include 
#include 
using namespace std;
class Solution {
public:
    vector intersection(vector& nums1, vector& nums2) {
        set s1(nums1.begin(), nums1.end());
        set s2(nums2.begin(), nums2.end());
        vector ret;
        auto it1 = s1.begin();
        auto it2 = s2.begin();
        while(it1 != s1.end() &&it2!= s2.end())
        {
            if(*it1<*it2)
            {
                it1++;
            }
            else if(*it1>*it2)
            {
                it2++;
            }
            else
            {
                ret.push_back(*it1);
                it1++;
                it2++;
            }
        }
        return ret;
    }
};

4.2 随机链表的复制(LeetCode 138)

题目:复制一个包含随机指针的链表。思路:利用 map 建立原节点与复制节点的映射,快速定位随机指针。

#include 
using namespace std;
struct Node {
    int val;
    Node *next;
    Node *random;
    Node(int x) : val(x), next(nullptr), random(nullptr) {}
};
class Solution {
public:
    Node* copyRandomList(Node* head) {
        Node *newhead, *newtail; // 新表的头尾指针
        newhead = newtail = nullptr;
        Node* cur = head;
        map copyMap; // 用于复制原链表来连接新链表
        // 先复制原链表
        while (cur) {
            if (newhead == nullptr) {
                newhead = newtail = new Node(cur->val); // 深拷贝
            } else {
                newtail->next = new Node(cur->val);
                newtail = newtail->next; // 跳到新结点
            }
            // 利用map将原链表与新链表建立联系
            copyMap[cur] = newtail; // 利用[]的插入修改功能
            cur = cur->next;
        }
        // 修改random域
        cur = head;
        Node* newcur = newhead;
        while (newcur) {
            newcur->random = copyMap[cur->random];
            newcur = newcur->next;
            cur = cur->next;
        }
        return newhead;
    }
};

4.3 前 K 个高频单词(LeetCode 692)

题目:统计单词出现频率,返回前 K 个高频单词,频率相同按字典序排序。思路:利用 map 统计词频,自定义仿函数排序后取前 K 个。

#include 
#include 
#include 
#include 
using namespace std;
class Solution {
public:
    // 仿函数(与sort搭配)
    struct Compare {
        bool operator()(const pair& x1,
                        const pair& x2) const {
            return x1.second > x2.second ||
                   (x1.second == x2.second && x1.first < x2.first);
        }
    };
    vector topKFrequent(vector& words, int k) {
        map wf; // 用于统计词频
        // 统计词频
        for (auto& str : words) {
            wf[str]++; // int类型默认调用构造为0
        }
        // map本质是棵红黑树,故单词本身已经是按照字典顺序排序,
        // 现在只需按照稳定排序对词频排序即可
        // sort只支持随机迭代器,把数据拷贝到vector
        vector> v(wf.begin(), wf.end());
        sort(v.begin(), v.end(), Compare());
        // 提取前k个
        vector result;
        for (int i = 0; i < k; i++) {
            result.push_back(v[i].first);
        }
        return result;
    }
};

五、总结与注意事项

5.1 核心要点总结

  1. 底层实现:map/set 系列基于红黑树,O (log N) 高效操作,天然有序;
  2. 去重特性:set/map 不允许重复,multiset/multimap 支持重复;
  3. 迭代器特性:set 迭代器不可修改元素,map 迭代器不可修改 key;
  4. operator[]:map 的核心接口,支持插入 / 查找 / 修改,multimap 不支持;
  5. 适用场景:去重排序、键值映射、元素统计、区间查询、快速判重。

5.2 常见坑点提醒

  1. 不要尝试修改 set 的元素或 map 的 key,会破坏红黑树结构;
  2. map 的 operator [] 访问不存在的 key 时,会自动插入默认值,需谨慎使用;
  3. 遍历 multimap 的重复 key 时,需用 equal_range 获取迭代器区间;
  4. 算法库的 find 函数(O (N))效率远低于 set/map 自带的 find 函数(O (log N)),优先使用容器自带接口。

map/set 系列是 C++ 开发中不可或缺的工具,掌握其底层原理和使用技巧,能大幅提升代码效率和可读性。建议结合本文示例代码反复练习,重点掌握 operator []、区间操作、mult 系列的差异及算法题应用,真正做到学以致用。

posted @ 2025-12-22 14:34  clnchanpin  阅读(148)  评论(0)    收藏  举报