C++ 标准模板库(STL)_有序关联容器—— set +multiset(迭代器失效问题)(侯捷老师)

Set + multiset

​#include <set>

为了补充前面说明的序列式容器缺少的部分,同时区分顺序(序列式)容器和关联容器,所以这里进行容器性质的说明。

1、顺序(序列式)容器和关联容器

在这里插入图片描述

1.1、顺序容器和关联容器定义

  • 1、顺序性容器
    一种各元素之间有顺序关系的线性表,是一种线性结构的可序群集。顺序性容器中的每个元素均有固定的位置,除非用删除或插入的操作改变这个位置。这个位置和元素本身无关,而和操作的时间和地点有关,顺序性容器不会根据元素的特点排序而是直接保存了元素操作时的逻辑顺序。比如我们一次性对一个顺序性容器追加三个元素,这三个元素在容器中的相对位置和追加时的逻辑次序是一致的。
  • 2、关联式容器
    和顺序性容器不一样,关联式容器是非线性的树结构,更准确的说是二叉树结构。各元素之间没有严格的物理上的顺序关系,也就是说元素在容器中并没有保存元素置入容器时的逻辑顺序。 但是关联式容器提供了另一种根据元素特点排序的功能,这样迭代器就能根据元素的特点“顺序地”获取元素。
    关联式容器另一个显著的特点是它是以键值的方式来保存数据,就是说它能把关键字和值关联起来保存,而顺序性容器只能保存一种(可以认为它只保存关键字,也可以认为它只保存值)。

1.2、顺序容器和关联容器分类

  • (1)顺序容器
    array(数组)、
    vector(向量)、
    deque(双端队列)、
    list(列表)、
    forward_list(单链表)

  • (2)有序关联容器
    set(集合)、
    multiset(多重集合)、
    map(映射)、
    multimap(多重映射)

  • (3)无序关联容器
    unorderedset (无序集合)、
    unorderedmultiset(无序多重集合)、
    unorderedmap(无序映射)、
    unordermultimap(无序多重映射)

  • (4)容器适配器
    stack(栈)、
    queue(队列)、
    priority_queue(优先队列)

2、set + multiset定义

在这里插入图片描述

2.1、 set 定义

  • 用来存储同一数据类型的数据类型,并且能从一个数据集合中取出数据,在set中每个元素的值都唯一,而且系统能根据元素的值自动进行排序。应该注意的是set中数元素的值不能直接被改变

  • 其内部是通过节点的方式来组织,所以在插入的时候比vector 快,但在查找和末尾添加上被vector 慢。

  • set内部采用的是平衡检索二叉树:红黑树(R-BTree)

2.1.1、 set 特点

(1)存储同一数据类型
(2)每个元素的值都唯一
(3)元素的值自动进行排序,插入、删除、查找 O(log2n)(面试常问)
(4)元素的值不能直接被改变(RB_tree 中的const iterator)
(5)方便地得到指定大小范围的元素在容器中所处的区间。

2.1.2、 插入效率和迭代器失效问题说明

(1)为何map和set的插入删除效率比用其他序列容器高?
set容器内所有元素都是以节点的方式来存储,其节点结构和链表差不多,指向父节点和子节点。
在这里插入图片描述
因此插入的时候只需要稍做变换,把节点的指针指向新的节点就可以了。删除的时候类似,稍做变换后把指向删除节点的指针指向其他节点,不需要做内存拷贝和内存移动。
(2)为何每次insert之后,以前保存的iterator不会失效?
迭代器iterator在set中就相当于指向节点的指针,只要内存不变,那这个iterator就不会失效。相对于vector来说,每一次的删除和插入之后,指针都有可能失效,调用push_back在尾部插入的时候情况也是如此。因为为了保证内部数据的连续存放,iterator指向的那块内存在插入和删除的过程中可能已经被其他内存覆盖或者内存已经被释放掉了。即使是push_back的时候,容器内部空间可能不够,需要一块新的更大的内存,只能把以前的内存释放掉,申请更大的内存,复制已有的数据元素都新的内存中,最后把需要插入的元素放到最后,那么以前的内存指针自然就不可用了。

2.2、 multiset 定义

是多重集合,其实现方式和set 是相似的,只是它不要求集合中的元素是唯一的,也就是说集合中的同一个元素可以出现多次。
在这里插入图片描述

3、RB-tree结点的定义

在这里插入图片描述
在这里插入图片描述
红黑树需要满足的五条性质:

  • 性质一:节点是红色或者是黑色;
  • 性质二:根节点是黑色;
  • 性质三:每个叶节点(NIL或空节点)是黑色;
  • 性质四:每个红色节点的两个子节点都是黑色的(也就是说不存在两个连续的红色节点);
  • 性质五:从任一节点到其每个叶节点的所有路径都包含相同数目的黑色节点,成为黑高。
  • 隐含性质:1)任意一颗以黑色节点为根的子树也必定是一颗红黑树2)左(右)子树的高度最多是右(左)子树的两倍,即H(left)>H(right),
    H(left)<=2* H(right)+1。
    树的平衡旋转

3.1、rb_tree结构定义部分源码

在这里插入图片描述
RB-tree

//KeyOfValue是获取key值得仿函数,Compare是用来比较节点大小的仿函数。
template <class Key, class Value, class KeyOfValue, class Compare,
          class Alloc = alloc>
class rb_tree {
protected:
  typedef void* void_pointer;
  typedef __rb_tree_node_base* base_ptr;
  typedef __rb_tree_node<Value> rb_tree_node;
  typedef simple_alloc<rb_tree_node, Alloc> rb_tree_node_allocator; //专属空间配置器
  typedef __rb_tree_color_type color_type;
public:
  //iterator定义在后面
  typedef Key key_type;
  typedef Value value_type;
  typedef value_type* pointer;
  typedef const value_type* const_pointer;
  typedef value_type& reference;
  typedef const value_type& const_reference;
  typedef rb_tree_node* link_type;
  typedef size_t size_type;
  typedef ptrdiff_t difference_type;
protected://sizeof = 12字节
  size_type node_count; //追踪记录树的大小(节点数量)
  link_type header;     //这是实现上的一个技巧
  Compare key_compare;  //节点间键值大小比较准则,应该是个function object
  ...
3.1.1、__rb_tree_node_base定义部分源码

红黑树的基础迭代器为struct __rb_tree_base_iterator,主要成员就是一个__rb_tree_node_base节点,指向树中某个节点,作为迭代器与树的连接关系,还有两个方法,用于将当前迭代器指向前一个节点decrement()和下一个节点increment().

typedef bool __rb_tree_color_type;
const __rb_tree_color_type __rb_tree_red = false;    //红色为0
const __rb_tree_color_type __rb_tree_black = true;   //黑色为1

struct __rb_tree_node_base
{
  typedef __rb_tree_color_type color_type;
  typedef __rb_tree_node_base* base_ptr;

  color_type color;  //节点颜色,非红即黑
  base_ptr parent;   //RB树的许多操作必须知道父节点
  base_ptr left;     //指向左节点
  base_ptr right;    //指向右节点

  static base_ptr minimum(base_ptr x)
  {
    while (x->left != 0) x = x->left;  //一直向左走,就会找到最小值
    return x;                          //这是二叉搜索树的特性
  }

  static base_ptr maximum(base_ptr x)
  {
    while (x->right != 0) x = x->right;  //一直向右走,就会找到最大值
    return x;                            //这是二叉搜索树的特性
  }
};

//真正的节点定义,基类中不含模板参数
template <class Value>
struct __rb_tree_node : public __rb_tree_node_base
{
  typedef __rb_tree_node<Value>* link_type;
  Value value_field;  //节点值
};

在这里插入图片描述

4、Set结构部分源码

在这里插入图片描述

template <class Key, class Compare = less<Key>, class Alloc = alloc>
class set {
public:
    typedef Key key_type;
    typedef Key value_type;  // 使用的value类型跟key一样
    typedef Compare key_compare;
    typedef Compare value_compare;
private:
    typedef rb_tree<key_type, value_type,
                  identity<value_type>, key_compare, Alloc> rep_type;
    rep_type t;
public:
    typedef typename rep_type::const_iterator iterator;//注意这里是const_iterator
    iterator begin() const { return t.begin(); }
    iterator end() const { return t.end(); }
    pair<iterator,bool> insert(const value_type& x) {
        pair<typename rep_type::iterator, bool> p = t.insert_unique(x);
        return pair<iterator, bool>(p.first, p.second);
    }
     ...
};

5、multiset结构部分源码


template <class Key, class Compare, class Alloc = alloc>
class multiset {
public:
  // typedefs:
 
  typedef Key key_type;
  typedef Key value_type;
  typedef Compare key_compare;
  typedef Compare value_compare;
private:
  typedef rb_tree<key_type, value_type, 
                  identity<value_type>, key_compare, Alloc> rep_type;
  rep_type t;  // red-black tree representing multiset
public:
  typedef typename rep_type::const_pointer pointer;
  typedef typename rep_type::const_pointer const_pointer;
  typedef typename rep_type::const_reference reference;
  typedef typename rep_type::const_reference const_reference;
  typedef typename rep_type::const_iterator iterator;
  typedef typename rep_type::const_iterator const_iterator;
  typedef typename rep_type::const_reverse_iterator reverse_iterator;
  typedef typename rep_type::const_reverse_iterator const_reverse_iterator;
  typedef typename rep_type::size_type size_type;
  typedef typename rep_type::difference_type difference_type;
  ...
};

6、set常见操作和应用

6.1 定义和赋值

set<T> s;//默认的比较函数是 less<int>,因此容器中的元素会升序排列
set<T> s(n);
set<T> s(begin, end);

//将key_value插入到set中 ,返回值是pair<set<int>::iterator,bool>,
//bool标志着插入是否成功,而iterator代表插入的位置,
//若key_value已经在set中,则iterator表示的key_value在set中的位置。
insert(key_value);     
// 将定位器begin到end之间的元素插入到set中,返回值是void.
inset(begin, end);   

set<int> numbers {8, 7, 6, 5, 4, 3, 2, 1};

在这里插入图片描述

set<string, greater<string>> words {"one", "two", "three", "four", "five", "six", "seven" , "eight"};

在这里插入图片描述

#include <iostream>
#include <set>
#include<vector>
 
using namespace std;
 
int main()
{
	vector<int> a = {1,2,3,4,5};//键是不能重复的
	set<int> s;
	set<int>::iterator iter;
	s.insert(a,a + 5);
	for(iter = s.begin() ; iter != s.end() ; ++iter)
	{
		cout<<*iter<<" ";
	}
	cout<<endl;
	pair<set<int>::iterator,bool> pr;
	pr = s.insert(5);
	if(pr.second)
	{
		cout<<*pr.first<<endl;
	}
	cout << " 插入数值之后的set中有:" << endl;
	for(iter = s.begin() ; iter != s.end() ; ++iter)
	{
		cout<<*iter<<" ";
	}
	cout<<endl;
 
	return 0;
}

6.2 查找和读取

find()      //返回给定值的定位器,如果没找到则返回end()。
count()      // 用来查找set中某个某个键值出现的次数。这个函数在set并不是很实用,因为一个键值在set只可能出现0或1次,这样就变成了判断某一键值是否在set出现过了。

lower_bound(key_value) //返回第一个大于等于key_value的定位器

upper_bound(key_value)//返回最后一个大于key_value的定位器

实例:

#include <stdio.h>
#include<iostream>
#include <vector>
#include <set>

using namespace std;

int main(){
        vector<int> v;
        for (int i = 0; i < 10; i++){
                v.push_back(i);
        }

        set<int> s;
        s.insert(v.begin(), v.end());
        set<int>::iterator it;
        for (it = s.begin(); it != s.end(); it++){
                printf("%d\t", *it);
        }
        printf("\n");

        printf("%d\n", s.count(9));
        printf("%d\n", *(s.find(9)));

		cout << "第一个大于等于 2 的值是:   ";
		cout<<*s.lower_bound(2)<<endl;
		cout << "第一个大于 3 的值是:   ";
		cout<<*s.upper_bound(3)<<endl;
        return 0;
}

6.3 删除

erase(iterator)         //删除定位器iterator指向的值
erase(begin,end) //删除定位器begin和end之间的值,左闭右开[ begin, end);
erase(key_value)    //删除键值key_value的值
#include <iostream>
#include <set>
using namespace std;
 
int main()
{
	set<int> s;
	set<int>::iterator it;
	set<int>::const_iterator iter;
	set<int>::iterator first;
	set<int>::iterator second;
	for(int i = 1 ; i <= 10 ; ++i)
	{
		s.insert(i);
		cout << i << "  " ;
	}
	cout << endl;
	it = s.begin();
	cout << "第一次删除的是:" << *it << endl;
	s.erase(s.begin());
	cout << "第一种删除之后 :"  << endl; 
	for(iter = s.begin() ; iter != s.end() ; ++iter)
	{
		cout<<*iter<<" ";
	}
	cout << endl;
	first = s.begin();
	second = s.begin();
	second++;
	second++;
	cout << "l == " << *first  << "  r == " << *second << endl;
	cout << "第二种删除之后 :"  << endl; 
	s.erase(first,second);
	for(iter = s.begin() ; iter != s.end() ; ++iter)
	{
		cout<<*iter<<" ";
	}
	cout << endl;
 
	s.erase(8);
	cout << "第三次删除的是: 8" << endl;
	cout<<"	第三种删除后 set 中元素是 :";
	for(iter = s.begin() ; iter != s.end() ; ++iter)
	{
		cout<<*iter<<" ";
	}
	cout << endl;
	return 0;
}

6.4 begin() 、end() 、empty() 、size()

begin()        //返回一个迭代器,返回的值为set容器的第一个元素
end()      //返回一个迭代器,返回的值为set容器的最后一个元素

clear()         //删除set容器中的所有的元素

empty()    //判断set容器是否为空
size()      //返回当前set容器中的元素个数

rbegin()    //返回一个逆迭代器,返回的值和end()相同
rend()     //返回一个逆迭代器,返回的值和rbegin()相同

	set<int> numbers {8, 7, 6, 5, 4, 3, 2, 1};
	cout<<"set 的 size 值为 :"<<s.size()<<endl;
	cout<<"set 中的第一个元素是 :"<<*s.begin()<<endl;
	cout<<"set 中的最后一个元素是:"<<*s.end()<<endl;
	s.clear();
	if(!s.empty())
	{
		cout<<s.size() <<endl;
	}

6.5 自定义比较函数

//元素不是结构体:
//自定义比较函数myComp,重载“()”操作符
struct myComp
{
	bool operator()(const your_type &a,const your_type &b)
	{
		return a.data > b.data;
	}
}
set<int,myComp>s;
set<int,myComp>::iterator it;
 
 
//如果元素是结构体,可以直接将比较函数写在结构体内。      
struct Info
{
	string name;
	float score;
	//重载“<”操作符,自定义排序规则
	bool operator < (const Info &a) const
	{
		//按score从大到小排列
		return a.score<score;
	}
}
 
set<Info> s;
set<Info>::iterator it;

7、multiset常见操作和应用(同set)

multiset<int>s;
插入一个数x:
s.insert(x)

几种删除:
s.erase(v):删除值为v的所有元素。
s.erase(it):删除迭代器it处的元素。
也就是说可以用s.erase(s.find(v))来删除值为v的一个元素。

查找相关:
s.lower_bound(v):返回第一个大于等于v的迭代器指针。
s.upper_bound(v):返回第一个大于v的迭代器指针。
s.find(v):返回一个等于v的迭代器指针。如果不存在值等于v的元素,则返回s.end()
s.equal_range(v):返回值等于v的第一个迭代器和最后一个迭代器,左闭右开,如果不存在则返回两个s.end()
s.count(v):返回值等于v的元素个数,数据类型为unsigned longlong int,如果不存在返回0,时间复杂度未知,如果重复的个数过多可能会慢。

7.1、举例

#include <set>  
#include <iostream>  
using namespace std;  
  
int main()  
{  
    ///1. 初始化  
    multiset<int> num;  
    multiset<int>::iterator iter,beg,end;  
    cout << num.max_size() << endl;///multiset容纳上限  
    cout << endl;  
  
    ///2. 添加元素  
    for (int i = 0; i < 10; i++)  
        num.insert(i);  
    cout << num.size() << endl;  
    cout << endl;  
  
    ///3. 遍历  
    for (iter = num.begin(); iter != num.end(); iter++)  
        cout << *iter << " " ;  
    cout << endl;  
    cout << endl;  
  
    ///4. 查询  
  
    iter = num.find(1);  
    if (iter != num.end())  
        cout << *iter << endl;  
    else  
        cout << -1 << endl;  
  
    iter = num.find(99);  
    if (iter != num.end())  
        cout << *iter << endl;  
    else  
        cout << -1 << endl;  
    cout << endl;  
  
    beg=num.lower_bound(2);  
    end=num.upper_bound(7);  
    for (; beg != end; beg++)  
        cout << *beg << " " ;  
    cout << endl;  
  
    ///5. 删除  
    iter = num.find(1);  
    num.erase(iter);  
    cout << num.size() << endl;  
    for (iter = num.begin(); iter != num.end(); iter++)  
        cout << *iter << " " ;  
    cout << endl;  
    cout << endl;  
  
    ///6. 判空与清空  
    if (!num.empty())  
        num.clear();  
}  

参考

1、https://www.cnblogs.com/ymd12103410/p/9498576.html
2、https://blog.csdn.net/changjiale110/article/details/79108447
3、https://blog.csdn.net/zhang_guyuan/article/details/77101163
4、https://www.cnblogs.com/evenleee/p/11689794.html
5、https://blog.csdn.net/google19890102/article/details/51720988
6、https://blog.csdn.net/Strawberry_595/article/details/81188509
7、https://www.jianshu.com/p/ecd7f66e11d3
8、《STL的源码剖析》

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值