期末考试周,王老师的信息学集训队要每天点名。队里有 10 万名学员,每人有一个 6 位学号。
第一天,助教 A 说:「把所有报到的人写进一个数组里吧。」于是写下了 int a[100005];。王老师问:「小 P 来了吗?」助教只能从头到尾扫一遍,最坏要看 10 万次。
第二天,有人重复签了两次到,数组里就出现了两个相同的学号。王老师问:「今天实到多少人?」助教又得先排序、再去重,折腾半天。
第三天,王老师改问:「今天实到次数 ≥ 3 次的有几个人?」、「把学号从小到大打印出来」。助教终于崩溃了。
转机:王老师笑着说,C++ 标准库早就替我们准备好了一本「自动排好序、自动去重、随手翻到任意一页」的点名册——它叫 set;还有一本「按学号查成绩」的字典——它叫 map。
数组擅长「按下标访问」,set / map 擅长「按值查找」——这是整个提高组最基础的一次思维转变。
💡 为什么数组不够用
数组的问题:查找某个值是否存在要 O(n);插入删除要搬动后面的元素,也是 O(n)。当操作次数达到 10⁵ 量级时,O(n²) 直接超时。
set / map 的答案:底层是平衡二叉搜索树(GCC 里实现为红黑树),树高恒为 O(log n)。查找、插入、删除全部 O(log n),并且中序遍历天然有序。
代价:常数比数组大(每个结点要存三个指针 + 颜色位),随机访问不支持(没有 s[5] 这种写法)。所以选型时先问自己:我需要的是「按下标取」还是「按值查」?