set通过operator <去重、排序

发布时间 2023-10-10 23:14:03作者: cerwang

如何定义类的operator<以保证set去重、有序

STL 自定义比较器的要求是必须为严格弱序,因为STL内部就是这样做的。

  1. x<x 为假 (反自反)
  2. x<y 为真则y<x 为假 (反对称)
  3. x<y 且y<z 则x<z (传递性)
  4. x<y 为假且y<x 为假,y<z 为假且z<y 为假,则x<z 为假且z<x 为假 (不可比的传递性)

如果两个关键字都不严格弱序于另一个,则这两个关键字相等。
如果!(a<b)&&!(b<a),就认为a和b相等,这样就可以保证去重。
在使用set存储自定义类时,重载的operator<兼具了排序和去重的功能,然而使用不当很容易带来错误。

先说结论:
operator<中去重的关键字和排序的关键字必须是相同的(可以是一个或者多个),否则会出现错误。因为红黑树的插入并不会遍历容器中所有的元素,而是根据operator<的返回值来判断插入的位置,关键字相同的元素也许在插入时并不会比较,而是位于不同分支上,导致并未去重。

一个反例

#include <iostream>
#include <set>
using namespace std;
struct song
{
    int m_id;
    int m_hot;
    song(int id,int hot)
    {

        this->m_id = id;
        this->m_hot = hot;
    }
    bool operator<(const struct song & right)const   //重载<运算符
    {
        if(this->m_id == right.m_id)     //根据id去重
            return false;
        else
        {
            if(this->m_hot != right.m_hot)
            {
                return this->m_hot > right.m_hot;      //降序
            }
            else
            {
                return this->m_id > right.m_id;     
            }
        }
        if(this->m_id==right.m_id)
            return this->m_hot<right.m_hot;
        else
            return this->m_id<right.m_id;
    }
};
int main()
{
    std::set<song> mySet;
    song s1(10,100);
    song s2(20,200);
    song s3(20,50);
    song s4(30,200);
    mySet.insert(s1);
    mySet.insert(s2);
    mySet.insert(s3);    
    mySet.insert(s4);  
    for(auto it:mySet)
    {
        std::cout<<"id:"<<it.m_id<<",hot:"<<it.m_hot<<std::endl;
    }
    std::cout<<"end"<<std::endl;
}

/*
id:30,hot:200
id:20,hot:200
id:10,hot:100
id:20,hot:50
end
*/

出错的原因就是
s1(10,100) < s2(20,200),s4(20,50) < s1(10,100)
但s2(20,200) < s4(20,50)不成立,即不满足传递性,导致二者位于二叉树不同分支,插入时并未遍历到等价的元素,因此看起来与期望的去重不符。