转自:http://www.189works.com/article-42025-1.html

怎样在set中放入自定义类型?这个问题通过谷歌就可以得到不少答案:1、定义一个函数对象并在定义set的时候将其作为第二个模板参数。2、为自定义类型定义<运算符。如:

class Edge
{
public:
Edge(int u, int v): u(u), v(v){}
bool operator < (const Edge& edge) const
{
return this->u < edge.u;
} //为了方便起见设为public int u; int v;
};
class EdgeComp
{
public:
bool operator()(const Edge& left, const Edge& right)
{
return left.u < right.u;
}
};
std::set<Edge> edge_set;
Edge edge_a(0, 1);
Edge edge_b(0, 2);
edge_set.insert(edge_a);
edge_set.insert(edge_b);
std::set<Edge, EdgeComp> edge_set2;
edge_set2.insert(edge_a);
edge_set2.insert(edge_b);

  

其实两种方法的道理是一样的,就是set需要一个比较函数对象comp。set的第二个模板参数默认为less函数对象,它会调用自定义类型的<运算符。那么,为什么需要这个比较对象呢。让我们看看标准中对关联容器的规定:two keys k1 and k2 are considered to be equivalent if for the comparison object comp, comp(k1, k2) == false && comp(k2, k1) == false.

原来set需要这个comp函数对象来判断元素的唯一性,通过两次参数顺序不同的调用来确定。但是是不是自定义的比较函数仅仅满足这个条件就够了呢?我们先来看看下面这个来自stackoverflow的问题;对于Edge类,为了将(1,2)、(2,1)视为同样的对象(可以想象Edge表示一条直线上的线段,那么这两个对象其实表达的是同一条线段),提问者定义了如下<运算符:

bool operator< (const Edge& e) const
{
bool result = true;
if( (u == e.u && v == e.v)||(v == e.u && u == e.v) )
{
result = false;
}
return result;
}

  

对(1,2)、(2,1)两个Edge对象,该运算符当然满足k1 < k2==false && k2 < k1==false。这完全符合Effective STL的第21条(的标题):总是让比较函数在等值情况下返回false。按理说应该可以正确的保证set中元素的唯一性。但是结果向set插入(1,2) -> (1,2) -> (2,1) -> (3,2) -> (2,3) -> (5,2)-> (1,2)后, set中却保存了如下元素:(1,2), (3,2), (5,2), (1,2)。

这是为什么呢?原来标准中对对关联容器还有另一个规定:

Effective STL的第21条内部也提到了(看来读书不能只做标题党啊),“从技术上来说,用于对关联容器排序的比较函数必须为它们所比较的对象定义一个‘严格的弱序化’(strick weak ordering)”。什么是严格的弱序化?让我们看看wiki上的定义:Each associative container is parameterized on Key and an ordering relation Compare that induces a strict weak ordering on elements of Key.

其实Effective STL的第21条内部也提到了(看来读书不能只做标题党啊),“从技术上来说,用于对关联容器排序的比较函数必须为它们所比较的对象定义一个‘严格的弱序化’(strick weak ordering)”。什么是严格的弱序化?让我们看看wiki上的定义:严格弱序化拥有如下属性:对于集合S中所有的x,y,z,对于所有的x,不存在x < x (非自反性 - 21条标题说的就是这个);对于所有x不等于y,如果x < y那么不存在y < x (不对称性);对于所有的x,y和z,如果x < y并且y < z,那么x < z(传递性);如果x < y,那么对于所有的z,要么x < z要么z < y(或者两者都成立).

显然,上例中的<运算符违反了不对称性,只要x,y不相等,x < y和 y < x都会返回true。下面让我们再来分析一下为什么这个违规会造成多余节点的插入。由于标准对关联容器的一些约束,如在关联容器中查找一个元素需要在对数时间内完成,基本上所有关联容器的底层都会用二叉搜索树类的数据结构来实现。这里就用平衡二叉搜索树作为例子,虽然实际的实现一般是更特殊的红黑树,但是不影响分析。另外值得注意的是,违反了标准属于未定义行为,所以下面的分析只是一种可能性。我们知道,一个二叉搜索树的节点放置规则是:任何节点的键值一定大于其左子树中任一节点的键值,并小于其右子数中任一节点的键值。因此我们可以模拟出插入过程,注意所有不相等的数对都回返回true,即永远只会与左子树中的节点比较:

插入(1,2):(1,2)

插入(1,2):与(1,2)比较判定为已存在,不插入

插入(2,1):与(1,2)比较判定为已存在,不插入

插入(3,2):

(1,2)

/

(3,2)

插入(2,3):与(1,2)比较返回true,然后与(3,2)比较,判定为已存在,不插入

插入(5,2):与(1,2)比较返回true,然后与(3,2)比较返回true,插入。之后对树进行了重新平衡

(3,2)

/     \

(5,2)   (1,2)

插入(1,2):与(3,2)比较返回true,与(5,2)比较返回true,插入

(3,2)

/     \

(5,2)   (1,2)

/

(1,2)

至此,(1,2)再次被插入了树中。由上面的分析可以看出,如果某C++的实现选择普通的二叉搜索树作为底层数据结构,数据将会退化为一条链表,此时反而是可以防止(1,2)再次被插入的。但是基于性能考虑,相信没有C++实现会这样做。(这里扯个个题外话,VC的set底层实现在debug版本时会对不对称性进行检查,如果违反会抛出一个异常。因此对于debug版有问题,release版没问题的程序也不能掉以轻心啊)

现在我们可以给文章一开始提到的方法添加一个补充约束: 总是让比较函数使被比较对象满足严格的弱序。

[转]std::set、自定义类型与比较函数的更多相关文章

  1. std::map自定义类型key

    故事背景:最近的需求需要把一个结构体struct作为map的key,时间time作为value,定义:std::map<struct, time> _mapTest; 技术调研:众所周知, ...

  2. c/c++ 标准库 set 自定义关键字类型与比较函数

    标准库 set 自定义关键字类型与比较函数 问题:哪些类型可以作为标准库set的关键字类型呢??? 答案: 1,任意类型,但是需要额外提供能够比较这种类型的比较函数. 2,这种类型实现了 < 操 ...

  3. 在set中放入自定义类型

    这件事情的起因是在学习背包问题时突然想到了一种算法,分析了一下应该是n^2logn复杂度的,当然比dp慢.但是既然想到了就实现了下: #include<bits/stdc++.h> usi ...

  4. 关于set或map的key使用自定义类型的问题

    我们都知道set或map的key使用自定义类型时必须重载<关系运算符 但是,还有一个条件,所调用重载的小于操作符,使用的对象必须是const 而对象调用的方法也必须是const的 1 #incl ...

  5. map以自定义类型当Key

    关于map的定义: template < class Key, class T, class Compare = less<Key>, class Allocator = alloc ...

  6. QSet使用及Qt自定义类型使用QHash等算法

    版权声明:若无来源注明,Techie亮博客文章均为原创. 转载请以链接形式标明本文标题和地址: 本文标题:QSet使用及Qt自定义类型使用QHash等算法     本文地址:http://techie ...

  7. C++类型转换 -- 由其他类型转换到自定义类型

    由其他类型转换到自定义类型 由其他类型(如int,double)向自定义类的转换是由构造函数来实现,只有当类的定义和实现中提供了合适的构造函数,转换才能通过. /******************* ...

  8. Qt自定义类型使用QHash等算法(Qt已经自定义了34种类型,包括int, QString, QDate等基本数据类型)

    自定义类型 #include <QCoreApplication> #include <QSet> #include <QDebug> class testCust ...

  9. 《精通C#》自定义类型转化-扩展方法-匿名类型-指针类型(11.3-11.6)

    1.类型转化在C#中有很多,常用的是int类型转string等,这些都有微软给我们定义好的,我们需要的时候直接调用就是了,这是值类型中的转化,有时候我们还会需要类类型(包括结构struct)的转化,还 ...

随机推荐

  1. ACM数论之旅3---最大公约数gcd和最小公倍数lcm(苦海无边,回头是岸( ̄∀ ̄))

    gcd(a, b),就是求a和b的最大公约数 lcm(a, b),就是求a和b的最小公倍数 然后有个公式 a*b = gcd * lcm     ( gcd就是gcd(a, b), ( •̀∀•́ ) ...

  2. Caffe使用step by step:使用自己数据对已经训练好的模型进行finetuning

    在经过前面Caffe框架的搭建以及caffe基本框架的了解之后,接下来就要回到正题:使用caffe来进行模型的训练. 但如果对caffe并不是特别熟悉的话,从头开始训练一个模型会花费很多时间和精力,需 ...

  3. JavaScript的setTimeout()和setInterval()

    1. setTimeout()方法 作用:在制定的毫秒数后调用函数或计算表达式 语法: setTimeout(code,millisec) 实例: function timedMsg() { var ...

  4. 03.基于IDEA+Spring+Maven搭建测试项目--常用dependency

    <!--常用的依赖配置--> <!--未展示完整的pom.xml文件内容--> <properties> <java.version>1.8</j ...

  5. Powerful array CodeForces - 86D(莫队)

    给你n个数,m次询问,Ks为区间内s的数目,求区间[L,R]之间所有Ks*Ks*s的和.1<=n,m<=200000.1<=s<=10^6 #include <iostr ...

  6. cmake 常用变量和常用环境变量查表手册

    cmake 常用变量和常用环境变量查表手册 一,cmake 变量引用的方式: 前面我们已经提到了,使用${}进行变量的引用.在 IF 等语句中,是直接使用变量名而不通过${}取值 二,cmake 自定 ...

  7. Swift使用SDWebImage处理远程图片资源

    第一步:配置SDWebImage 打开github,https://github.com/rs/SDWebImage,将SDWebImage下载到本地 用Xcode创建一个swift的singleVi ...

  8. 学习Spring Boot:(五)使用 devtools热部署

    前言 spring-boot-devtools 是一个为开发者服务的一个模块,其中最重要的功能就是自动应用代码更改到最新的App上面去.原理是在发现代码有更改之后,重新启动应用,但是比速度比手动停止后 ...

  9. 【BZOJ2961】共点圆(CDQ分治)

    [BZOJ2961]共点圆(CDQ分治) 题面 BZOJ 题解 设询问点\((x,y)\),圆心是\((X,Y)\) 那么如果点在园内的话就需要满足 \((X-x)^2+(Y-y)^2\le X^2+ ...

  10. hadoop进阶----hadoop经验(一)-----生产环境hadoop部署在超大内存服务器的虚拟机集群上vs几个内存较小的物理机

    生产环境 hadoop部署在超大内存服务器的虚拟机集群上 好 还是  几个内存较小的物理机上好? 虚拟机集群优点 虚拟化会带来一些其他方面的功能. 资源隔离.有些集群是专用的,比如给你三台设备只跑一个 ...