cpp浅析STL-set

前言

CPP

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

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

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

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

Set定义

#include<set>
using namespace std;

set<int> s1;//空对象
set<int> s2{3, 4, 2, 1};//列表清单,默认less递增 ,输出为{1,2,3,4}
set<int, greater<int> > s3{6, 5, 7, 8};//列表清单 ,输出为{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插入

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

/*
 * insert能插入多个,慢但是实用
 * */
void add1() {
    set<int> demo{1, 2};
    //在第一个元素后面插入3
    demo.insert(demo.begin()++, 3);//{1,2,3},结果遵循递增规则

    //直接插入元素,也是按照规则排列
    demo.insert(-1);//{-1,1,2,3}
    //C++11之后,可以用emplace_hint或者emplace替代

    //插入其他容器的部分序列
    vector<int> test{7, 8, 9};
    demo.insert(++test.begin(), --test.end()); //{-1,1,2,3,8}

    //插入初始化列表
    demo.insert({10, 11}); //{-1,1,2,3,8,10,11}

    set<int> s = demo;
    set<int>::iterator iter;
    for (iter = s.begin(); iter != s.end(); ++iter) {
        cout << *iter << " ";
    }
}

emplace插入

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

/*
 * emplace,只能插入一个
 * 而且如果元素已经存在,是不会再次插入的
 * */
void add2() {
    set<int> demo{1, 2};

    demo.emplace(4);//{1,2,4}

    demo.emplace(4);//还是{1,2,4}

    set<int> s = demo;
    set<int>::iterator iter;
    for (iter = s.begin(); iter != s.end(); ++iter) {
        cout << *iter << " ";
    }
}

遍历元素

/*
 * 直接用迭代器,注意const_iterator还是iterator
 * */
void search() {
    set<int> demo{1, 2};

    // 如果参数为const vector<int> 需要用const_iterator
    // vector<int>::const_iterator iter=v.begin();

    set<int> s = demo;
    set<int>::iterator iter;
    for (iter = s.begin(); iter != s.end(); ++iter) {
        cout << *iter << " ";
    }
}

删除元素

/*
 * 删除有两种方式,
 * clear是直接清空
 * erase是删除指定迭代器范围内的数字
 * 也可以用来删除指定的单个元素
 * */
void del() {
    set<int> demo{1, 2, 3, 4, 5};
    //清空
    demo.clear();//{}
    if (demo.empty()) {//判断Vector为空则返回true
        demo.insert({6, 7, 8, 9, 10, 11});//{ 6, 7, 8, 9, 10, 11 }

        //删除第一个元素
        demo.erase(demo.begin());//{7, 8, 9, 10, 11 }
        // 删除指定元素
        demo.erase(11);
        //删除前三个
        demo.erase(demo.begin(), next(demo.begin(), 3)); //{ 10, 11 }
        // 只保留最后一个
        demo.erase(demo.begin(), ++demo.end()); //{11}
    }

    set<int> s = demo;
    set<int>::iterator iter;
    for (iter = s.begin(); iter != s.end(); ++iter) {
        cout << *iter << " ";
    }
}

比较函数

元素为基本类型

使用函数对象

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

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

// 基础类型,变动规则为递增或者递减
void BasicCompare() {
    set<int, less<int> > s2{6, 5, 7, 8};//小数靠前。递增,输出为{5,6,7,8}
    set<int, greater<int> > s3{6, 5, 7, 8};//大数靠前。递减,输出为{8,7,6,5}

    // 遍历查看内容
    set<int> s = s2;
    set<int>::iterator iter;
    for (iter = s.begin(); iter != s.end(); ++iter) {
        cout << *iter << " ";
    }
}

类外重载括号

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

// 基础类型,但是有特殊的比较规则
// 此处以递增递减为例
// 当然也可以指定为其他自定义类型
struct Special {
    bool operator()(const int &a, const int &b) {
        // return a > b;//等价greater<int>
        return a < b;//等价less<int>
    }
};

// 基础类型需要类外重载
void SpecialCompare() {
    set<int, Special> s2;

    s2.emplace(3);
    s2.emplace(2);
    s2.emplace(1);
    s2.emplace(4);

    // 遍历查看内容
    set<int, Special> s = s2;
    set<int>::iterator iter;
    for (iter = s.begin(); iter != s.end(); ++iter) {
        cout << *iter << " ";
    }
}
/*1 2 3 4 */

元素为自定义类型

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

类内重载小于号

// 自定义结构体
struct Info {
    string name;
    float score;

    Info() {
        name = "a";
        score = 60;
    }

    Info(string sname, float fscore) {
        name = sname;
        score = fscore;
    }

    // 类内重载方法
    bool operator<(const Info &a) const {
        /*名字递减;若名字相同,则分数递减*/
        if (a.name < name)
            return true;
        else if (a.name == name) {
            return a.score < score;
        } else
            return false;
    }
};

void InfoCompare() {
    pair<set<Info>::iterator, bool> result;//获取插入的结果
    set<Info> s;
    // 插入默认对象和指定对象
    s.insert(Info());
    s.insert(Info("a", 53));
    s.insert(Info("keen", 68));
    result = s.insert(Info("keen", 60));

    // 遍历查看内容
    for (auto iter = s.begin(); iter != s.end(); ++iter) {
        cout << iter->name << " " << iter->score << endl;
    }
    cout << "插入元素的信息:" << result.first->name << " " << result.first->score << endl;
    cout << "插入是否成功" << boolalpha << result.second << endl;

    // 再次插入相同元素
    result = s.insert(Info("keen", 60));
    cout << "插入元素的信息:" << result.first->name << " " << result.first->score << endl;
    cout << "插入是否成功" << boolalpha << result.second << endl;
}
    /*
    keen 68
    keen 60
    a 60
    a 53
    插入元素的信息:keen 60
    插入是否成功true
    插入元素的信息:keen 60
    插入是否成功false
     */

类外重载括号

#include <iostream>
#include <set>

using namespace std;

/*Student结构体*/
struct Student {
    string name;
    int age;
    string sex;

    Student() {}

    Student(const string &name, int age, const string &sex) : name(name), age(age), sex(sex) {}
};

/*
 * 为Student指定排序准则
 * 先比较名字;若名字相同,则比较年龄。小的返回true
 * 如果想要部分参数匹配,可以用正则表达式来规定
 * */
class StudentCompare {
public:
    bool operator()(const Student &a, const Student &b) const {
        if (a.name < b.name)
            return true;
        else if (a.name == b.name) {
            if (a.age < b.age)
                return true;
            else
                return false;
        } else
            return false;
    }
};

int main() {
    set<Student, StudentCompare> stuSet;

    stuSet.insert(Student("张三", 13, "male"));
    stuSet.insert(Student("李四", 23, "female"));

    /*构造一个用来查找的临时对象,可以看到,即使stuTemp与stu1实际上并不是同一个对象,
     *但当在set中查找时,仍能够查找到集合中的元素成功。
     *这是因为已定义的StudentCompare的缘故。
     */
    Student stuTemp;
    stuTemp.name = "张三";
    stuTemp.age = 13;

    set<Student, StudentCompare>::iterator iter;
    iter = stuSet.find(stuTemp);
    if (iter != stuSet.end()) {
        cout << (*iter).name << " " << (*iter).age << " " << (*iter).sex << endl;
    } else {
        cout << "Cannot fine the student!" << endl;
    }
    return 0;
}

查找函数

count统计元素个数

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

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

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

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

源码如下。

size_type __tree<_Tp, _Compare, _Allocator>::__count_unique(const _Key& __k) const
{
    __node_pointer __rt = __root();
    while (__rt != nullptr)
    {
        if (value_comp()(__k, __rt->__value_))
        {
            __rt = static_cast<__node_pointer>(__rt->__left_);
        }
        else if (value_comp()(__rt->__value_, __k))
            __rt = static_cast<__node_pointer>(__rt->__right_);
        else
            return 1;
    }
    return 0;
}

find获取元素迭代器

/*
 * 用find函数寻找元素,
 * */
void find2(set<int> s ){

    if (s.find(4)!= s.end() ) {
        cout << "元素4存在"<<endl;
    }else{
        cout << "元素4不存在";
    }
    if (s.find(8)!= s.end() ) {
        cout << "元素8存在"<<endl;
    }else{
        cout << "元素8不存在";
    }
}

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

源码如下。

iterator __lower_bound(const _Key& __v,__node_pointer __root,__iter_pointer __result)
{
    while (__root != nullptr)
    {
        if (!value_comp()(__root->__value_, __v))
        {
            __result = static_cast<__iter_pointer>(__root);
            __root = static_cast<__node_pointer>(__root->__left_);
        }
        else
            __root = static_cast<__node_pointer>(__root->__right_);
    }
    return iterator(__result);
}

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

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

感谢

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

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

感谢现在努力的自己。

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

离线下载链接

C++中set用法详解

C++ STL set::find的用法

C++ STL set容器完全攻略

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值