• cpp浅析STL-set


    前言

    CPP

    集合(Set)t是一种元素唯一的、包含已排序对象的数据容器。

    C++ STL中标准关联容器set, multiset, map, multimap内部采用的就是一种非常高效的平衡检索二叉树:红黑树,也称为RB树(Red-Black Tree)。RB树的统计性能要好于一般平衡二叉树,所以被STL选择作为了关联容器的内部结构。

    对于mapset这种关联容器来说,不需要做内存拷贝和内存移动。以节点的方式来存储,其节点结构和链表差不多。

    博主当前的CPP版本为c++14,所以相关源码内容就是以这个版本的为准。

    Set定义

    1. #include<set>
    2. using namespace std;
    3. set<int> s1;//空对象
    4. set<int> s2{3421};//列表清单,默认less递增 ,输出为{1,2,3,4}
    5. set<int, greater<int> > s3{6578};//列表清单 ,输出为{8.7.6.5}

    Set常规操作

    支持正向和反向迭代器,但是不支持随机迭代器访问元素。

    C++中文在线手册:https://zh.cppreference.com/

    begin()返回指向第一个元素的迭代器
    clear()清除所有元素
    count()返回某个值元素的个数,0或1,可以用来判断是否存在某数
    empty()如果集合为空,返回true
    end()返回指向最后一个元素的迭代器
    equal_range()返回集合中与给定值相等的上下限的两个迭代器
    erase()删除集合中的元素
    set集合中的删除操作是不进行任何的错误检查的,比如定位器的是否合法等等,所以用的时候自己一定要注意。
    find()返回一个指向被查找到元素的迭代器;如果没有找到,返回指向集合最后一个元素的迭代器。
    get_allocator()返回集合的分配器
    insert()在集合中插入元素
    可以在集合中插入其他数组中指定个数的值
    lower_bound()返回指向大于(或等于)某值的第一个元素的迭代器
    key_comp()返回一个用于元素间值比较的函数
    max_size()返回集合能容纳的元素的最大限值
    rbegin()返回指向集合中最后一个元素的反向迭代器
    rend()返回指向集合中第一个元素的反向迭代器
    size()集合中元素的数目
    swap()交换两个集合变量
    upper_bound()返回大于某个值元素的迭代器
    value_comp()返回一个用于比较元素间的值的函数

    增加元素

    insert插入

    允许多个元素的插入,使用迭代器指定位置。

    1. /*
    2.  * insert能插入多个,慢但是实用
    3.  * */
    4. void add1() {
    5.     set<int> demo{12};
    6.     //在第一个元素后面插入3
    7.     demo.insert(demo.begin()++3);//{1,2,3},结果遵循递增规则
    8.     //直接插入元素,也是按照规则排列
    9.     demo.insert(-1);//{-1,1,2,3}
    10.     //C++11之后,可以用emplace_hint或者emplace替代
    11.     //插入其他容器的部分序列
    12.     vector<int> test{789};
    13.     demo.insert(++test.begin(), --test.end()); //{-1,1,2,3,8}
    14.     //插入初始化列表
    15.     demo.insert({1011}); //{-1,1,2,3,8,10,11}
    16.     set<int> s = demo;
    17.     set<int>::iterator iter;
    18.     for (iter = s.begin(); iter != s.end(); ++iter) {
    19.         cout << *iter << " ";
    20.     }
    21. }

    emplace插入

    注意是每次只能插入一个,而且是只有构造函数,效率高!

    1. /*
    2.  * emplace,只能插入一个
    3.  * 而且如果元素已经存在,是不会再次插入的
    4.  * */
    5. void add2() {
    6.     set<int> demo{12};
    7.     demo.emplace(4);//{1,2,4}
    8.     demo.emplace(4);//还是{1,2,4}
    9.     set<int> s = demo;
    10.     set<int>::iterator iter;
    11.     for (iter = s.begin(); iter != s.end(); ++iter) {
    12.         cout << *iter << " ";
    13.     }
    14. }

    遍历元素

    1. /*
    2.  * 直接用迭代器,注意const_iterator还是iterator
    3.  * */
    4. void search() {
    5.     set<int> demo{12};
    6.     // 如果参数为const vector<int> 需要用const_iterator
    7.     // vector<int>::const_iterator iter=v.begin();
    8.     set<int> s = demo;
    9.     set<int>::iterator iter;
    10.     for (iter = s.begin(); iter != s.end(); ++iter) {
    11.         cout << *iter << " ";
    12.     }
    13. }

    删除元素

    1. /*
    2.  * 删除有两种方式,
    3.  * clear是直接清空
    4.  * erase是删除指定迭代器范围内的数字
    5.  * 也可以用来删除指定的单个元素
    6.  * */
    7. void del() {
    8.     set<int> demo{12345};
    9.     //清空
    10.     demo.clear();//{}
    11.     if (demo.empty()) {//判断Vector为空则返回true
    12.         demo.insert({67891011});//67891011 }
    13.         //删除第一个元素
    14.         demo.erase(demo.begin());//{7891011 }
    15.         // 删除指定元素
    16.         demo.erase(11);
    17.         //删除前三个
    18.         demo.erase(demo.begin(), next(demo.begin(), 3)); //1011 }
    19.         // 只保留最后一个
    20.         demo.erase(demo.begin(), ++demo.end()); //{11}
    21.     }
    22.     set<int> s = demo;
    23.     set<int>::iterator iter;
    24.     for (iter = s.begin(); iter != s.end(); ++iter) {
    25.         cout << *iter << " ";
    26.     }
    27. }

    比较函数

    元素为基本类型

    使用函数对象

    当元素是基本类型,一般是变动规则为递增或者递减。

    推荐使用自带的less或者greater函数比较。

    1. // 基础类型,变动规则为递增或者递减
    2. void BasicCompare() {
    3.     set<int, less<int> > s2{6578};//小数靠前。递增,输出为{5,6,7,8}
    4.     set<int, greater<int> > s3{6578};//大数靠前。递减,输出为{8,7,6,5}
    5.     // 遍历查看内容
    6.     set<int> s = s2;
    7.     set<int>::iterator iter;
    8.     for (iter = s.begin(); iter != s.end(); ++iter) {
    9.         cout << *iter << " ";
    10.     }
    11. }

    类外重载括号

    当元素是基本类型,变动规则后不能用自带的less或者greater函数比较,就只能新建一个结构体来描述规则。这种方式是适用性最广的。

    1. // 基础类型,但是有特殊的比较规则
    2. // 此处以递增递减为例
    3. // 当然也可以指定为其他自定义类型
    4. struct Special {
    5.     bool operator()(const int &a, const int &b) {
    6.         // return a > b;//等价greater<int>
    7.         return a < b;//等价less<int>
    8.     }
    9. };
    10. // 基础类型需要类外重载
    11. void SpecialCompare() {
    12.     set<int, Special> s2;
    13.     s2.emplace(3);
    14.     s2.emplace(2);
    15.     s2.emplace(1);
    16.     s2.emplace(4);
    17.     // 遍历查看内容
    18.     set<int, Special> s = s2;
    19.     set<int>::iterator iter;
    20.     for (iter = s.begin(); iter != s.end(); ++iter) {
    21.         cout << *iter << " ";
    22.     }
    23. }
    24. /*1 2 3 4 */

    元素为自定义类型

    当元素是自定义结构体,可以在类外重载括号();也可以在类内重载小于号<,例子如下。

    类内重载小于号

    1. // 自定义结构体
    2. struct Info {
    3.     string name;
    4.     float score;
    5.     Info() {
    6.         name = "a";
    7.         score = 60;
    8.     }
    9.     Info(string sname, float fscore) {
    10.         name = sname;
    11.         score = fscore;
    12.     }
    13.     // 类内重载方法
    14.     bool operator<(const Info &a) const {
    15.         /*名字递减;若名字相同,则分数递减*/
    16.         if (a.name < name)
    17.             return true;
    18.         else if (a.name == name) {
    19.             return a.score < score;
    20.         } else
    21.             return false;
    22.     }
    23. };
    24. void InfoCompare() {
    25.     pair<set<Info>::iterator, bool> result;//获取插入的结果
    26.     set<Info> s;
    27.     // 插入默认对象和指定对象
    28.     s.insert(Info());
    29.     s.insert(Info("a"53));
    30.     s.insert(Info("keen"68));
    31.     result = s.insert(Info("keen"60));
    32.     // 遍历查看内容
    33.     for (auto iter = s.begin(); iter != s.end(); ++iter) {
    34.         cout << iter->name << " " << iter->score << endl;
    35.     }
    36.     cout << "插入元素的信息:" << result.first->name << " " << result.first->score << endl;
    37.     cout << "插入是否成功" << boolalpha << result.second << endl;
    38.     // 再次插入相同元素
    39.     result = s.insert(Info("keen"60));
    40.     cout << "插入元素的信息:" << result.first->name << " " << result.first->score << endl;
    41.     cout << "插入是否成功" << boolalpha << result.second << endl;
    42. }
    43.     /*
    44.     keen 68
    45.     keen 60
    46.     a 60
    47.     a 53
    48.     插入元素的信息:keen 60
    49.     插入是否成功true
    50.     插入元素的信息:keen 60
    51.     插入是否成功false
    52.      */

    类外重载括号

    1. #include <iostream>
    2. #include <set>
    3. using namespace std;
    4. /*Student结构体*/
    5. struct Student {
    6.     string name;
    7.     int age;
    8.     string sex;
    9.     Student() {}
    10.     Student(const string &name, int age, const string &sex) : name(name), age(age), sex(sex) {}
    11. };
    12. /*
    13.  * 为Student指定排序准则
    14.  * 先比较名字;若名字相同,则比较年龄。小的返回true
    15.  * 如果想要部分参数匹配,可以用正则表达式来规定
    16.  * */
    17. class StudentCompare {
    18. public:
    19.     bool operator()(const Student &a, const Student &b) const {
    20.         if (a.name < b.name)
    21.             return true;
    22.         else if (a.name == b.name) {
    23.             if (a.age < b.age)
    24.                 return true;
    25.             else
    26.                 return false;
    27.         } else
    28.             return false;
    29.     }
    30. };
    31. int main() {
    32.     set<Student, StudentCompare> stuSet;
    33.     stuSet.insert(Student("张三"13"male"));
    34.     stuSet.insert(Student("李四"23"female"));
    35.     /*构造一个用来查找的临时对象,可以看到,即使stuTemp与stu1实际上并不是同一个对象,
    36.      *但当在set中查找时,仍能够查找到集合中的元素成功。
    37.      *这是因为已定义的StudentCompare的缘故。
    38.      */
    39.     Student stuTemp;
    40.     stuTemp.name = "张三";
    41.     stuTemp.age = 13;
    42.     set<Student, StudentCompare>::iterator iter;
    43.     iter = stuSet.find(stuTemp);
    44.     if (iter != stuSet.end()) {
    45.         cout << (*iter).name << " " << (*iter).age << " " << (*iter).sex << endl;
    46.     } else {
    47.         cout << "Cannot fine the student!" << endl;
    48.     }
    49.     return 0;
    50. }

    查找函数

    count统计元素个数

    count函数是用来统计一个元素在当前容器内的个数。由于Set的特性,所以只能返回1或者0。

    1. /*
    2.  * 用count函数寻找元素,
    3.  * */
    4. void find1(set<int> s ){
    5.     if (s.count(4== 1) {
    6.         cout << "元素4存在"<<endl;
    7.     }
    8.     if (s.count(8== 0) {
    9.         cout << "元素8不存在";
    10.     }
    11. }

    追查源码,我发现他是用的红黑树的__count_unique方法,从根节点开始比对,如果要查找的元素是比当前节点的数值要小就去左子树找,如果要查找的元素是比当前节点的数值要大就去右子树找,当匹配到节点值相等时,就返回1;当到最后的叶子结点仍然没有找到就返回0。

    底层是使用的红黑树,所以查找起来还是很方便的。

    源码如下。

    1. size_type __tree<_Tp, _Compare, _Allocator>::__count_unique(const _Key& __k) const
    2. {
    3.     __node_pointer __rt = __root();
    4.     while (__rt != nullptr)
    5.     {
    6.         if (value_comp()(__k, __rt->__value_))
    7.         {
    8.             __rt = static_cast<__node_pointer>(__rt->__left_);
    9.         }
    10.         else if (value_comp()(__rt->__value_, __k))
    11.             __rt = static_cast<__node_pointer>(__rt->__right_);
    12.         else
    13.             return 1;
    14.     }
    15.     return 0;
    16. }

    find获取元素迭代器

    1. /*
    2.  * 用find函数寻找元素,
    3.  * */
    4. void find2(set<int> s ){
    5.     if (s.find(4)!= s.end() ) {
    6.         cout << "元素4存在"<<endl;
    7.     }else{
    8.         cout << "元素4不存在";
    9.     }
    10.     if (s.find(8)!= s.end() ) {
    11.         cout << "元素8存在"<<endl;
    12.     }else{
    13.         cout << "元素8不存在";
    14.     }
    15. }

    追查源码,我发现他是用的红黑树的find方法,从根节点开始比对,如果要查找的元素是比当前节点的数值要大就去右子树找;如果查找的元素比当前节点的数值要小于等于匹配到节点值时,就赋值一次结果节点并去左子树找。一直到最后所查找的节点为空再结束。最终返回结果节点。

    源码如下。

    1. iterator __lower_bound(const _Key& __v,__node_pointer __root,__iter_pointer __result)
    2. {
    3.     while (__root != nullptr)
    4.     {
    5.         if (!value_comp()(__root->__value_, __v))
    6.         {
    7.             __result = static_cast<__iter_pointer>(__root);
    8.             __root = static_cast<__node_pointer>(__root->__left_);
    9.         }
    10.         else
    11.             __root = static_cast<__node_pointer>(__root->__right_);
    12.     }
    13.     return iterator(__result);
    14. }

    暂时看来,两个方法的底层实现逻辑是相似的,都是用平衡二叉树的方式去寻找节点。

    区别是count返回1或0来标明元素是否存在;find函数是返回指针可以方便下一步修改或者取用。

    感谢

    谨以此文,送给未来笨兮兮又回来看的博主自己

    哦,我看到了,过去那个很努力但是很皮的我哦

    感谢现在努力的自己。

    感谢现在的好奇,为了能成为更好的自己。

    离线下载链接

    C++中set用法详解

    C++ STL set::find的用法

    C++ STL set容器完全攻略

  • 相关阅读:
    django 操作
    怎么制作一个网站?怎样搭建一个高质量的网站?
    模拟一个js底层数据类型隐式转换
    Laravel 博客开发|SEO 友好的 URL
    使用dom4j解析XML
    #力扣:1. 两数之和@FDDLC
    (附源码)springboorCRM客户关系管理系统 毕业设计 316631
    iOS17.2正式版什么时候发布? 13大新功能细节抢先看
    CANdelaStudio中的状态跳转图无法查看
    原型和原型链
  • 原文地址:https://blog.csdn.net/qq_41461536/article/details/126571357