std::set::equal_range的作用是什么?为何在无重复有序集合中存在?
你提的这个问题非常合理——毕竟std::set的元素唯一且始终有序,equal_range返回的范围确实要么是空,要么只包含一个元素,看起来好像单独用find或者lower_bound就能搞定,显得有点多余。但实际上,它的存在是为了接口一致性和泛型编程的灵活性,还有几个实用的场景:
1. 适配泛型代码,统一处理允许重复和不允许重复的容器
假设你写了一段模板代码,需要处理任意有序关联容器(比如std::set和std::multiset),统计某个元素的出现次数。如果用equal_range,你只需要写一套逻辑:
template <typename OrderedContainer, typename T> size_t count_occurrences(const OrderedContainer& container, const T& value) { auto [lower, upper] = container.equal_range(value); return std::distance(lower, upper); }
这段代码对std::multiset来说会返回真实的重复次数,对std::set来说返回0或1,但代码完全不需要修改。如果不用equal_range,你就得针对set和multiset分别写不同的逻辑(比如set用count,multiset用equal_range加distance),代码冗余度会高很多。
2. 一次调用同时获取「存在性」和「插入位置」
equal_range返回的std::pair里,first是lower_bound(第一个不小于value的元素),second是upper_bound(第一个大于value的元素)。利用这个特性,你可以:
- 如果
lower != upper:说明元素存在,lower就是指向该元素的迭代器; - 如果
lower == upper:说明元素不存在,lower的位置就是你插入该元素时保持容器有序的最佳位置。
比如下面的代码,插入元素前检查是否存在,不存在就插入,用equal_range只需要一次O(logn)的操作:
std::set<int> my_set = {1,3,5}; int value = 4; auto [lower, upper] = my_set.equal_range(value); if (lower == upper) { // 元素不存在,插入到lower的位置 my_set.insert(lower, value); } else { // 元素存在,做相应处理 std::cout << "元素" << value << "已存在" << std::endl; }
如果分开用find和lower_bound,你需要两次O(logn)的操作,效率上虽然差别不大,但代码更简洁,逻辑更连贯。
3. 语义更清晰的代码表达
当你在代码里写equal_range时,读者一眼就能明白你的意图是「找到所有等于某个值的元素范围」——哪怕在std::set里这个范围最多只有一个元素。相比之下,find只能表达「找到某个元素」的意图,lower_bound/upper_bound表达的是「找到边界」的意图。用equal_range能让代码的语义更贴合你的业务逻辑,尤其是在团队协作的场景下,可读性会更好。
总的来说,std::set::equal_range的存在不是多余的,它是STL容器接口一致性设计的体现,让泛型代码更简洁,同时也提供了一些实用的小场景,让代码更高效、更易读。
内容的提问来源于stack exchange,提问作者rubix_addict

