博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
[转]std::set、自定义类型与比较函数
阅读量:7211 次
发布时间:2019-06-29

本文共 3101 字,大约阅读时间需要 10 分钟。

转自: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_set; Edge edge_a(0, 1); Edge edge_b(0, 2); edge_set.insert(edge_a); edge_set.insert(edge_b); std::set
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版没问题的程序也不能掉以轻心啊)

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

转载于:https://www.cnblogs.com/wlzy/p/8696930.html

你可能感兴趣的文章
MongoDB介绍与安装
查看>>
《C语言接口与实现:创建可重用软件的技术》一1.5 习题
查看>>
《网页设计与前端开发 Dreamweaver+Flash+Photoshop+HTML+CSS+JavaScript 从入门到精通》—— 第1章 网页设计基础知识...
查看>>
Maven实战. 3.7NetBeans Maven插件简单使用
查看>>
Android开发技术周报 Issue#17
查看>>
《iOS 9 开发指南》——第6章,第6.7节iOS 9控件的属性
查看>>
this is incompatible with sql_mode=only_full_group_by
查看>>
TableView编辑状态下跳转页面的崩溃处理
查看>>
java操作阿里云的对象存储OSS
查看>>
linux 如何判断当前用户
查看>>
ERROR 1045 (28000): Access denied for user &#39;root&#39;@&#39;localhost&#39; (using password: YES)
查看>>
魔兽世界客户端数据研究(四):M2文件头分析
查看>>
jQuery中getJSON跨域原理详解
查看>>
【MySql】MySql存储,游标,循环的简单使用
查看>>
一些服务器客户端的c例子
查看>>
众推架构的进一步讨论
查看>>
OEA 2.11 支持单机版数据库 - SQLite与SQLCE对比
查看>>
【系统架构】如何解决热点数据更新问题
查看>>
Cacti设置流量阀值实现邮件报警
查看>>
[转载]了解Linux的进程与线程
查看>>