C++unique list去重相邻元素

2026-04-10 07:10:30 1891阅读 0评论

C++ std::unique:被误解最深的算法之一

深夜调试代码时,盯着屏幕上的 vector,心里难免会犯嘀咕。明明调用了一遍 std::unique,为什么 [1, 2, 1, 2] 还是没变,只有 [1, 1, 2, 2] 才变成了 [1, 2]?这种“无效去重”的尴尬,几乎每个 C++ 开发者都经历过。很多人以为 std::unique 是万能去重键,只要按下去,重复项就会消失无踪,但事实往往比想象骨感。

这个问题的核心在于范围std::unique 这个函数的设计初衷,并不是全局去重,而是消除相邻重复元素。它的底层逻辑非常直接:它从序列头部开始扫描,将当前元素与后一个元素比较,如果两者相等,就删掉后一个;如果不等,则保留。整个过程就像是在排队整理队伍,只关心前后两个人是否长得一样,完全不在乎远处的另一个人是不是跟前面那位一模一样。

因此,如果你面对的数据是无序的,比如乱糟糟的一堆数字,直接扔给 std::unique 就像拿着漏勺捞汤里的米粒——看着在过滤,其实大部分还是漏过去了。想要真正达到“全局去重”的效果,必须在调用 std::unique 之前进行排序。

排序 + unique + erase 才是处理 vector 去重的标准三连击。

当数据经过 std::sort 变得有序,所有的相同元素都会聚拢在一起变成相邻状态,这时候再运行 std::unique,就能一次性清理掉连续重复的副本了。注意一个关键细节:std::unique 返回的是一个迭代器,指向的是去重后有效数据的尾部。原容器里超出这部分的新尾部依然是未定义的垃圾值,必须配合容器自身的 erase 成员函数才能真正释放内存空间。

std::sort(vec.begin(), vec.end());
auto last = std::unique(vec.begin(), vec.end());
vec.erase(last, vec.end());

这串代码虽然经典,但有一个致命副作用:打乱了原始顺序。在很多实际业务场景中,用户并不希望数据顺序发生跳变。如果既要去除重复项,又要保持元素首次出现的先后次序,这套方案就得停一停了。

这时候得换个思路,用空间换时间。引入 std::unordered_set 来记录已经出现过的元素,边遍历边判断。遇到没见过的塞进集合,遇到的重复的跳过。这种方式虽然牺牲了 O(1) 的查找开销(相对于直接比对),但在不破坏顺序的前提下,完美解决了无序去重的问题。当然,如果是需要严格维持原有位置关系的复杂对象,还得小心自定义哈希或比较器的陷阱。

写到这里,不妨复盘一下:std::unique 本身没错,错的是我们对它能力的过度幻想。算法工具没有好坏之分,关键在于数据状态是否匹配预期。

下次在处理容器时,先别急着敲代码。看一眼数据是不是有序的?再看一眼对顺序有没有要求?如果是简单的数值去重,老老实实走一遍排序加擦除的流程;如果是对用户反馈列表去重,记得维护一个集合标记。多问自己一句“数据现状如何”,能少走很多不必要的弯路。毕竟,懂工具的原理,远比盲目复制粘贴模板更重要。

文章版权声明:除非注明,否则均为Dark零点博客原创文章,转载或复制请以超链接形式并注明出处。

发表评论

快捷回复: 表情:
验证码
评论列表 (暂无评论,1891人围观)

还没有评论,来说两句吧...

目录[+]