跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++算法

set 与 map 底层实现及高频算法实战

深入解析了 C++ STL 中 set 和 map 的底层红黑树实现原理,涵盖构造、迭代器操作、增删查改细节及常见陷阱。结合力扣高频算法题,演示了如何利用 set 去重排序特性解决数组交集问题,利用 map 映射关系处理链表复制与环检测,并通过统计频率配合稳定排序完成 Top K 单词查找。旨在通过实战巩固数据结构思维,提升编码效率。

WenxuanMa发布于 2026/3/30更新于 2026/9/957 浏览
set 与 map 底层实现及高频算法实战

set 与 map 底层实现及高频算法实战

在 C++ STL 中,set 和 map 是处理键值对和集合操作的核心容器。它们底层通常基于红黑树(Red-Black Tree)实现,保证了增删查改的时间复杂度为 O(logN)。理解它们的内部机制,对于解决算法题至关重要。

set 类的实现与特性

set 默认要求元素类型支持比较运算。如果默认行为不符合需求,可以通过仿函数自定义排序规则。内存分配方面,它使用空间配置器,高级场景下可替换为自定义内存池。

由于底层是红黑树,set 中的元素天然有序(中序遍历为升序)。迭代器遍历时,顺序即为排序后的顺序。

构造与迭代

插入数据时,set 会自动完成去重和排序操作。

#include <iostream>
#include <set>
using namespace std;

int main() {
    set<int> s;
    // set<int, greater<int>> s; // 降序排列
    s.insert(4);
    s.insert(3);
    s.insert(8);
    s.insert(9);
    s.insert(2);
    s.insert(6);

    auto it = s.begin();
    while (it != s.end()) {
        cout << *it << " ";
        ++it;
    }
    cout << endl; // 输出:2 3 4 6 8 9
    return 0;
}

注意,set 不支持直接修改元素值,因为一旦修改可能破坏红黑树的平衡结构,导致迭代器失效或逻辑错误。

支持 initializer_list 初始化,重复插入的值会被忽略。

s.insert({2, , , });
 ( e : s) {
    cout << e << ;
}
3
4
5
for
auto
" "

erase 和 find

删除最小值可以直接删除起始迭代器:

s.erase(s.begin());

删除指定值时,erase 返回被删除元素的个数(对于 set 通常是 0 或 1)。

int x;
cin >> x;
int num = s.erase(x);
if (num == 0) {
    cout << x << " 不存在" << endl;
} else {
    cout << x << " 删除成功" << endl;
}

迭代器失效问题: 如果使用迭代器定位后删除,该迭代器会立即失效,访问会导致未定义行为。

auto pos = s.find(x);
if (pos != s.end()) {
    s.erase(pos); // pos 在此处之后失效
}

查找效率上,容器自带的 find 方法利用树结构,复杂度 O(logN),优于算法库的 std::find(O(N))。

lower_bound 和 upper_bound

这两个函数用于区间查找,常用于二分搜索场景。

set<int> mset;
for (int i = 1; i < 10; i++) {
    mset.insert(i * 10);
}
// 10 20 30 40 50 60 70 80 90

auto itlow = mset.lower_bound(30); // 返回 >= 30 的第一个位置
auto itup = mset.upper_bound(50);  // 返回 > 50 的第一个位置
mset.erase(itlow, itup);           // 删除 [30, 50] 区间内的元素

multiset 与 set 的区别

multiset 允许键值重复,仅排序不去重。查找时,find 返回的是第一个匹配项,通过不断递增迭代器可以获取所有相同值的节点。

map 的核心用法

map 存储 <key, value> 对,同样基于红黑树,Key 有序且唯一。

insert 返回值

insert 返回一个 pair<iterator, bool>。如果插入成功,second 为 true;如果 Key 已存在,插入失败,second 为 false,且 first 指向已存在的节点。

这意味着 insert 兼具插入和查找功能。

operator[] 的陷阱与便利

operator[] 是 map 最常用的操作符,其内部实现大致如下:

mapped_type& operator[](const key_type& k) {
    pair<iterator, bool> ret = insert({k, mapped_type()});
    iterator it = ret.first;
    return it->second;
}

关键点在于:

  1. 自动创建:如果 Key 不存在,它会先创建一个默认初始化的 Value,然后返回引用。这可以用来方便地计数。
  2. 修改便捷:如果 Key 存在,直接返回现有 Value 的引用。
string myarray[] = {"秋", "冬", "夏", "春", "夏", "秋", "夏", "春", "夏", "秋", "秋", "冬"};
map<string, int> countMap;
for (const auto& e : myarray) {
    countMap[e]++; // 若不存在则初始化为 0,再自增
}
for (const auto& it : countMap) {
    cout << it.first << ":" << it.second << endl;
}

multimap 差异

multimap 支持 Key 冗余,因此不支持 operator[] 进行单值修改,因为无法确定要修改哪一个重复的 Key。

力扣算法实战

掌握容器特性后,我们可以更高效地解决经典算法题。

两个数组的交集

题目要求返回两个数组的交集。利用 set 的去重和有序特性,可以将问题转化为双指针遍历。

class Solution {
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        vector<int> ret;
        set<int> s1(nums1.begin(), nums1.end());
        set<int> s2(nums2.begin(), nums2.end());

        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;
    }
};

思路很简单:既然有序,小的那个肯定不是交集,移动小指针即可。相等时记录并同步移动。

环形链表

检测链表环并找到入口。利用 set 的唯一性来记录访问过的节点。

class Solution {
public:
    ListNode* detectCycle(ListNode* head) {
        set<ListNode*> s;
        ListNode* cur = head;
        while (cur) {
            if (s.count(cur)) return cur; // 已访问,说明有环
            s.insert(cur);
            cur = cur->next;
        }
        return nullptr;
    }
};

虽然快慢指针法更节省空间,但 set 方案代码直观,适合快速验证逻辑。

随机链表的复制

深拷贝带随机指针的链表。核心难点在于 random 指针指向的节点可能尚未创建。使用 map 建立原节点到新节点的映射关系。

class Solution {
public:
    Node* copyRandomList(Node* head) {
        map<Node*, Node*> randomMap;
        Node* copyhead = nullptr, *copytail = nullptr;
        Node* ptr = head;

        // 第一遍:构建 next 链并建立映射
        while (ptr) {
            if (!copytail) {
                copytail = copyhead = new Node(ptr->val);
            } else {
                copytail->next = new Node(ptr->val);
                copytail = copytail->next;
            }
            randomMap[ptr] = copytail;
            ptr = ptr->next;
        }

        // 第二遍:处理 random 指针
        Node* copy = copyhead;
        ptr = head;
        while (ptr) {
            if (ptr->random) {
                copy->random = randomMap[ptr->random];
            }
            copy = copy->next;
            ptr = ptr->next;
        }
        return copyhead;
    }
};

前 k 个高频单词

统计词频并排序。map 负责计数,vector + sort 负责排序。

class Solution {
public:
    struct kvFunction {
        bool operator()(const pair<string, int>& w1, const pair<string, int>& w2) {
            return w1.second > w2.second; // 频率高的在前
        }
    };

    vector<string> topKFrequent(vector<string>& words, int k) {
        map<string, int> countMap;
        for (auto& it : words) {
            countMap[it]++;
        }

        vector<pair<string, int>> v(countMap.begin(), countMap.end());
        stable_sort(v.begin(), v.end(), kvFunction());

        vector<string> ret;
        for (int i = 0; i < k; i++) {
            ret.push_back(v[i].first);
        }
        return ret;
    }
};

这里有个细节:当频率相同时,需要按字典序排序。map 本身按键字典序存储,配合 stable_sort 的稳定性,可以保证频率相同的情况下保持原有的字典序。

set 和 map 对比总结

特性setmap
底层结构红黑树红黑树
元素类型Tpair<const K, V>
有序性升序(默认)Key 升序
迭代器修改不支持修改 Key不支持修改 Key,支持修改 Value
主要用途去重、集合运算键值映射、查找

在实际开发中,如果需要频繁查找或统计,优先考虑 set 或 map。理解它们的底层机制,能帮你写出性能更优的代码。

目录

  1. set 与 map 底层实现及高频算法实战
  2. set 类的实现与特性
  3. 构造与迭代
  4. erase 和 find
  5. lowerbound 和 upperbound
  6. multiset 与 set 的区别
  7. map 的核心用法
  8. insert 返回值
  9. operator[] 的陷阱与便利
  10. multimap 差异
  11. 力扣算法实战
  12. 两个数组的交集
  13. 环形链表
  14. 随机链表的复制
  15. 前 k 个高频单词
  16. set 和 map 对比总结

更多推荐文章

查看全部
  • Python 基础语法入门:常量、变量与运算符
  • Python 开源 AI 模型引入与测试实战
  • 无人机 AI 算法全景图:7 大场景 50+ 算法详解
  • 10 个 GitHub 热门开源项目:AI Agent、Rust 架构与开发者工具
  • AI 绘画:DALL·E 3 绘图功能与 API 使用指南
  • Python HTTP 请求库对比:requests、aiohttp 与 httpx
  • AI 产品经理产品开发全流程解析
  • Python+AI 入门指南:从零基础到实战落地
  • AI动画剧本、脚本、分镜头生成提示词
  • 数据库事务核心解析:ACID 特性、并发异常与隔离级别
  • LLaMA-2 与 Mixtral 的提示词调优技巧
  • LeetCode 替换所有问号与提莫攻击解题思路
  • Anything to RealCharacters 2.5D 转真人引擎 AIGC 集成方案
  • Python 兼职接单指南:需求评估与平台选择
  • 深度学习模型优化策略与实战调参
  • VS Code 连接 Gitee 上传代码实战指南
  • jQuery 前端开发核心指南:语法、DOM 操作及 Validate 插件
  • AI 提示词基础:从零构建高效对话思维
  • 无人机视觉目标检测数据集 VisDrone 介绍
  • Apache IoTDB 架构特性与 Prometheus+Grafana 监控体系部署实践

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online