set详讲(C++)

目录

1. 前言

2. 预备知识

 2.1 关联式容器

2.2 键值对

3. set详解

3.1 set是什么

3.2 set模板参数列表

3.3 set构造

3.4 set的使用

3.4.1 insert

3.4.2 find

 3.4.3 erase

3.4.4 swap

3.4.5 empty

3.4.6 size

3.4.7 count

3.4.8  lower_bound

3.8.9 upper_bound 

2.8.10 set降序使用

4. set 的特点 


1. 前言

set  是 STL 中的容器之一,不同于普通容器,它的查找速度极快,常用来存储各种经常被检索的数据,因为容器的底层是平衡二叉搜索树中的红黑。除此之外,还可以借助其特殊的性质,解决部分难题

2. 预备知识

 2.1 关联式容器

在初阶阶段,我们已经接触过STL中的部分容器,比如:vector、list、deque、forward_list(C++11)等,这些容器统称为序列式容器,因为其底层为线性序列的数据结构,里面存储的是元素本身。那什么是关联式容器?它与序列式容器有什么区别?

线性存储:是指元素按照一定的顺序排列,然后元素是连续存储的,或者通过指针构成一条链表

list:

关联式容器也是用来存储数据的,与序列式容器不同的是,其里面存储的是<key,value>结构的键值对,在数据检索时比序列式容器效率更高。

2.2 键值对

用来表示具有一一对应关系的一种结构,该结构中一般只包含两个成员变量key和value,key代表键值,value表示与key对应的信息(实值)。比如:现在要建立一个英汉互译的字典,那该字典中必然有英文单词与其对应的中文含义,而且,英文单词与其中文含义是一一对应的关系,即通过该应该单词,在词典中就可以找到与其对应的中文含义。

3. set详解

3.1 set是什么

【set】 是按照一定顺序存储的容器

  • 在 set 中,元素的 value 也标识它(value就是key,类型为T),并且每个value必须是唯一的。set中的元素不能在容器中修改(元素总是 const 类型),但是可以从容器中插入或者删除它们。

  • 在内部,set 中的元素总是按照其内部比较对象(类型为Compare)所指示的特定严格弱排序准则进行排序。 

  • set 容器通过 key 访问单个元素的速度比 unordered_set 容器慢,但 set 容器允许根据顺序对子集进行直接迭代 

  • set 在底层是用二叉搜索树(红黑树)实现的。

 注意点:

  • set 中 插入元素时,只需要插入 value 即可,不需要构造键值对

  • set 中的元素不可以重复(因此可以使用 set 去重)

  • 使用 set  的迭代器遍历 set 中的元素,可以得到有序序列

  • set 中的元素默认按照 升序 来排序

  • set 中的查找某个元素,时间复杂度为 :log N

  • 与 map/multimap不同 ,map/multimap 中存储的时真正的键值对 <key,value>,set 中只放 value,但是底层实际存放的是 <value,const value> ,这个大家先了解后面学到用set复用底层红黑树就能懂了

  • set 中的元素不允许修改,因为 set 在底层是用二叉搜索树来实现的,若是二叉搜索树当中的某个元素值进行了修改,那么这棵树将不再是二叉查找树。

3.2 set模板参数列表

如下图所示:

  • T : set中存放元素的类型,实际在底层存储 <value, value> 的键值对。
  • Compare :set中元素默认按照小于来比较。
  • Alloc :set中元素空间的管理方式,使用STL提供的空间配置器管理。

3.3 set构造

这里主要有三种方式:

(1)无参构造空容器

set<int> s1; // 构造一个int类型的空容器

(2)使用迭代器区间进行初始化构造

vector<int> v1 = { 1,2,3,4,5 };
set<int> s3(v1.begin(), v1.end()); // 构造vector对象某段区间的复制品

(3)拷贝构造某类型容器

set<int> s2(s1); // 拷贝构造int类型s1容器的复制品

3.4 set的使用

set参考文档

使用set要包头文件include <set>

set的成员函数主要分为:迭代器,容量操作,修改操作。 

  • set的成员函数主要分为:迭代器,容量操作,修改操作。 

 我这里只举几个常用的,其它可以看文档学习。

3.4.1 insert

在 set 中插入元素 val ,实际插入的是 <val,val> 构成的键值对

  • 如果插入成功,返回 <val在set中的位置, true> 
  • 如果插入失败,说明 val 在set中已经存在,返回 <val在set中的位置, false> 

void testset()
{
	set<int> s;
 
	// 插入元素
	s.insert(4);
	s.insert(5);
	s.insert(2);
	s.insert(2);
	s.insert(1);
	s.insert(3);
	s.insert(3);
 
	// 遍历
	for (auto e : s)
	{
		cout << e << " ";
	}
}

可以看到当插入元素重复元素时,set 会自动过滤掉,并对已有的元素进行排序

3.4.2 find

 代码示例:

void testset()
{
    set<int> s;
    // 插入元素
    s.insert(4);
    s.insert(5);
    s.insert(2);
    s.insert(2);
    s.insert(1);
    s.insert(3);
    s.insert(3);
    int x;
    cin >> x;
    set<int>::iterator pos = s.find(x);
    if (pos != s.end())
    {
        cout << "找到了" << endl;
    }
}

 3.4.3 erase

删除 set 中的元素,这里有3种删除方式 

(1)从set容器中删除迭代器指向位置的元素(搭配 find 使用)

void testset()
{
    set<int> s;

    // 插入元素
    s.insert(4);
    s.insert(5);
    s.insert(2);
    s.insert(1);
    s.insert(6);
    s.insert(3);
    s.insert(8);
    s.insert(7);
    // 遍历
    for (auto e : s)
    {
        cout << e << " ";
    }
    cout << endl;
    auto pos = s.find(5);
    if (pos != s.end())
    {
        s.erase(pos); // 删除元素5
        cout << "删除成功" << endl;
    }
    else
    {
        cout << "删除失败" << endl;
    }

    // 遍历
    for (auto e : s)
    {
        cout << e << " ";
    }
}

注意一点,迭代器删除一定要判断是否是end(),否则会有问题

(2)从set 容器中删除单个元素(直接传入要删除的元素)

void testset()
{
	set<int> s;
 
	// 插入元素
	s.insert(4);
	s.insert(5);
	s.insert(2);
	s.insert(1);
	s.insert(6);
	s.insert(3);
	s.insert(8);
	s.insert(7);
 
	s.erase(5);
 
	// 遍历
	for (auto e : s)
	{
		cout << e << " ";
	}
}

那么它和 第1种 的区别是什么呢?

  • erase(x) :如果x存在就删除;如果不存在,不做任何改变 
  • erase(pos) :如果x存在就删除;如果不存在,此时pos位置指向set::end的迭代器,那么程序运行就会报错。 

(3) 从 set 容器中删除一组元素(传的是迭代器区间【first,last)) 

void testset()
{
    set<int> s;

    // 插入元素
    s.insert(4);
    s.insert(5);
    s.insert(2);
    s.insert(1);
    s.insert(6);
    s.insert(3);
    s.insert(8);
    s.insert(7);
    // 遍历
    for (auto e : s)
    {
        cout << e << " ";
    }
    cout << endl;
    auto pos = s.find(5);
    if (pos != s.end())
    {
        s.erase(pos, s.end()); // 从5开始所有的元素全部删除
        cout << "删除成功" << endl;
    }
    else
    {
        cout << "删除失败" << endl;
    }

    // 遍历
    for (auto e : s)
    {
        cout << e << " ";
    }
}

程序运行结果:

  • 可以看到从 5 开始所有的元素都已经被删除了 

3.4.4 swap

交换 set 容器中的元素 

 

void testset()
{
    set<int> s1;
    s1.insert(4);
    s1.insert(5);
    s1.insert(2);
    s1.insert(1);

    set<int> s2;
    s2.insert(6);
    s2.insert(3);
    s2.insert(8);
    s2.insert(7);

    cout << "s1:";
    // 遍历
    for (auto e1 : s1)
    {
        cout << e1 << " ";
    }

    cout << "s2:";
    // 遍历
    for (auto e2 : s2)
    {
        cout << e2 << " ";
    }
    s1.swap(s2);
    printf("\n交换后\n");
    cout << "s1:";
    // 遍历
    for (auto e1 : s1)
    {
        cout << e1 << " ";
    }

    cout << "s2:";
    // 遍历
    for (auto e2 : s2)
    {
        cout << e2 << " ";
    }
}

程序运行结果:

3.4.5 empty

判断 set 容器是否为空,空返回 ture,非空返回 false 

void testset()
{
	set<int> s1;
	s1.insert(4);
	s1.insert(5);
	s1.insert(2);
	s1.insert(1);
 
	set<int> s2;
 
	cout << s1.empty() << endl; // s1容器不为空,输出0(表示假)
 
	cout << s2.empty() << endl; // s2容器为空,输出1(表示真)
}

3.4.6 size

返回 set 中的有效元素个数

void testset()
{
	set<int> s;
 
	// 插入元素
	s.insert(4);
	s.insert(5);
	s.insert(2);
	s.insert(1);
	s.insert(6);
	s.insert(3);
	s.insert(8);
	s.insert(7);
 
	// 打印元素个数
	cout << s.size() << endl;
}

3.4.7 count

返回 set 中值为 x 的元素的个数

  • 这个接口对于 set 容器其实没有太大用处,因为 set 当中每个value 都是唯一的 

3.4.8  lower_bound

返回大于等于val位置的迭代器

void testset()
{
	set<int> s;
 
	// 插入元素
	s.insert(4);
	s.insert(5);
	s.insert(2);
	s.insert(1);
	s.insert(3);
	s.insert(8);
	s.insert(7);
 
	// 如果3存在就返回3位置的迭代器
	auto lowIt = s.lower_bound(3);
	cout << *lowIt << endl;
 
	// 如果6不存在就返回比6大的位置的迭代器
	lowIt = s.lower_bound(6);
	cout << *lowIt << endl;
}

 代码示例二:

3.8.9 upper_bound 

不管val存在还是不存在,都返回比val大的那个值位置的迭代器

 

void testset()
{
    set<int> s;

    // 插入元素
    s.insert(4);
    s.insert(5);
    s.insert(2);
    s.insert(1);
    s.insert(3);
    s.insert(8);
    s.insert(7);

    // 如果3存在就返回大于3位置的迭代器
    auto lowIt = s.upper_bound(3);
    cout << *lowIt << endl;

    // 如果6不存在就返回比6大的位置的迭代器
    lowIt = s.upper_bound(6);
    cout << *lowIt << endl;

    //如果在set中找打不到比val还大的值就会返回end()
    //lowIt = s.upper_bound(8);
    // 此时去访问迭代器指向的位置就会报错
    //cout << *lowIt << endl;
}

  • 其实,lower_bound 和 upper_bound 可以搭配使用,删除元素中间的一段区间 
void testset()
{
    set<int> s;

    // 插入元素
    s.insert(4);
    s.insert(5);
    s.insert(2);
    s.insert(6);
    s.insert(1);
    s.insert(3);
    s.insert(8);
    s.insert(7);
    cout << "删除前" << endl;
    // 遍历
    for (auto e : s)
    {
        cout << e << " ";
    }
    // 删除 [3, 7] 这段区间中的所有数
    auto leftIt = s.lower_bound(3); // 返回3位置的迭代器
    auto rightIt = s.upper_bound(7); // 返回8位置的迭代器
    //删除是左闭右开
    s.erase(leftIt, rightIt);
    cout << endl << "删除后" << endl;
    // 遍历
    for (auto e : s)
    {
        cout << e << " ";
    }
}

  • 注意:erase 删除的迭代器区间是左闭右开,也就是说 right 是 8 位置的迭代器,但是 erase 只会删除到 7。

2.8.10 set降序使用

可以通过改变 set 模板参数2的方式,改变其中的顺序为 降序

#include <iostream>
#include <vector>
#include <set>
using namespace std;
 
int main()
{
	vector<int> arr = { 7,3,6,9,3,1,6,2 };
	set<int, greater<int>> s1(arr.begin(), arr.end());
	for (auto e : s1)
		cout << e << " ";
 
	return 0;
}

4. set 的特点 

### 查询返回 `Empty set` 的可能原因 MySQL 查询返回 `Empty set` 通常表示查询语句执行成功,但没有匹配到任何记录。以下是可能导致该结果的常见原因: #### 查询条件过于严格 如果 `WHERE` 子句中的条件设置过于具体或逻辑错误,可能导致无法匹配任何记录。例如,使用了错误的比较操作符或值范围不匹配,都会导致查询结果为空 [^1]。 示例: ```sql SELECT * FROM users WHERE age > 150; ``` #### 表中确实没有数据 当查询的表中没有任何记录时,自然会返回空结果集。可以使用 `IF(empty(...))` 函数判断表是否为空,如下所示 [^2]: ```sql SELECT IF(empty('your_table_name'), 'Table is empty', 'Table is not empty') AS result; ``` #### 字符集不匹配 在某些情况下,如果客户端和服务器端的字符集设置不一致,可能导致查询无法正确匹配数据,即使数据确实存在。例如,在 DOS 界面中查询时出现 `Empty set`,但在 Navicat 中可以正常显示,可能是由于字符集不一致所致。此时可以通过设置字符集为 `gbk` 来解决该问题 [^1]: ```sql SET NAMES gbk; ``` #### 数据存储格式或编码问题 字段中存储的数据可能包含隐藏字符、空格或其他编码问题,导致查询条件无法正确匹配。例如,字符串字段中包含不可见字符或前后空格,可能影响 `LIKE` 或 `=` 条件的匹配 [^3]。 #### 数据类型不匹配 查询条件中使用的值与列的数据类型不匹配,可能导致无法正确匹配记录。例如,对 `INT` 类型的列使用字符串值进行查询,或对 `DATE` 类型的列使用格式错误的日期字符串,都会导致查询结果为空。 #### 索引失效或查询优化器问题 虽然查询语法正确,但如果索引未被正确使用,或者查询优化器选择了低效的执行计划,也可能导致查询结果为空。 #### 外键约束或连接条件错误 在涉及多表连接的查询中,如果 `JOIN` 条件设置错误或外键约束导致无法匹配记录,也可能返回空结果集。 ### 总结 MySQL 查询返回 `Empty set` 的原因包括查询条件过于严格、表中确实没有数据、字符集不匹配、数据存储格式问题、数据类型不匹配、索引失效以及连接条件错误等。排查时应结合具体查询语句、表结构及数据内容进行分析。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值