C++进阶 | [4] map and set

摘要:set,multiset,map,multimap

前言

1. 容器 

  • 序列式容器:只存储数据,数据之间无关联关系。例如,vector、list、deque、……
  • 关联式容器:不仅存储数据,且数据之间有关联关系。例如,map、set、…

2. 键对值

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

SGI-STL中关于键值对的定义:

template <class T1, class T2>
struct pair 
{
    typedef T1 first_type;
    typedef T2 second_type;
    T1 first;
    T2 second;

    pair(): first(T1()), second(T2()){}
    pair(const T1& a, const T2& b): first(a), second(b){}
};

本文将介绍 set、multiset、map、multimap 。 


1. set

set - C++ Reference (cplusplus.com)

set 是一个 key 模型的搜索二叉树。因此,set 对于数据具有排序(搜索二叉树的特点) + 去重(不允许重复值)的作用。
注意:set 内存储的数据不可被修改(BST都不支持修改key)


1)insert

//single element (1)    
pair<iterator,bool> insert (const value_type& val);

//with hint (2)    
iterator insert (iterator position, const value_type& val);

//range (3)    
template <class InputIterator>
void insert (InputIterator first, InputIterator last);

通过文档可以看到 insert 有 3 个重载。

当使用 pair<iterator,bool> insert (const value_type& val); 插入指定值的结点,函数返回的是一个 pair<iterator,bool> 的对象。该 pair 的 first 为迭代器,指向新插入的元素(插入成功 second 值为 true)或已有的等效元素(插入失败 second 值为 false)

使用示例:

#include<set>
#include<map>
#include<vector>
#include<iostream>

void test_set()
{
	std::set<int> s1;
	std::vector<int> vt = { 4,2,6,1,8,9,5,3,2,4 };
	for (auto e : vt)
	{
		auto tmp = s1.insert(e);
		std::cout << e << " " << tmp.second << std::endl;
	}
}

int main()
{
	test_set();
	return 0;
}

2)erase & find & count

(1)
     void erase (iterator position);

ps. position 必须是存在的,否则会报错

(2)
     size_type erase (const value_type& val);

ps.val 是否 是 存在的值都可以

(3)
     void erase (iterator first, iterator last);

由于 iterator position 不存在而报错的举例:

void test_set()
{
	std::set<int> s1;
	std::vector<int> vt = { 4,2,6,1,8,9,5,3,2,4 };
	for (auto e : vt)
	{
		auto tmp = s1.insert(e);
		std::cout << e << " " << tmp.second << std::endl;
	}

	std::set<int>::iterator it2 = s1.find(7);//没找都会返回s1.end()
	s1.erase(it2);//这样就会导致删除错误
	//因此,如果通过find函数找到结点再erase,那么使用erase之间要进行判断
}

  •  find count 的区别:
    iterator find (const value_type& val) const; 👉 find 返回类型是 iterator,判断是否 find 成功需要取到这个返回值,并判断是否与 std::set::end 相等。即 find 是都成功判断起来比较麻烦。
    
    size_type count (const value_type& val) const; 👉 (这里 size_type 参看文档可知是 size_t。)count 返回类型为 size_t,成功返回1,失败返回0,比较方便判断。

3)lower_bound & upper_bound

iterator lower_bound (const value_type& val) const;

Return value

An iterator to the the first element in the container which is not considered to go before* val, or set::end if all elements are considered to go before val.

Member types iterator and const_iterator are bidirectional iterator types pointing to elements.

*在这句话中,"go before" 表示在顺序上位于指定值 val 之前的意思。换句话说,如果元素被认为在指定值 val 之前,即比 val 小或者按照容器自身的排序规则排在 val 之前,那么这些元素就被视为"go before val"


iterator upper_bound (const value_type& val) const;

Return value

An iterator to the the first element in the container which is considered to go after* val, or set::end if no elements are considered to go after val.

Member types iterator and const_iterator are bidirectional iterator types pointing to elements.

*类比上面的 "go before",这句话中的 "go after" 就很好理解了。

void test_set2()
{
	std::set<int> myset;
	std::set<int>::iterator itlow, itup;

	for (int i = 1; i < 10; i++) myset.insert(i * 10); // 10 20 30 40 50 60 70 80 90

	itlow = myset.lower_bound(30);                     //       ^
													   // itloew将指向第一个不位于30之间的元素,即>=30,即30
	itup = myset.upper_bound(60);                      //                   ^
													   // itup将指向第一个位于60之后的元素,即>60,即70

	myset.erase(itlow, itup);                     // 10 20 70 80 90

	std::cout << "Myset Contains:";
	for (std::set<int>::iterator it = myset.begin(); it != myset.end(); ++it)
		std::cout << ' ' << *it;
}

对于上述代码,itlow = myset.lower_bound(30); == itlow = myset.lower_bound(25);  即这两句话得到的itlow是相等的。>=30即30;>=25即30.
而对于 myset.erase(itlow, itup); 即删除 [ 30, 60 ] 这个闭区间的所有值。


4)equal_range

pair<iterator,iterator> equal_range (const value_type& val) const;

Return value

The function returns a pair, whose member pair::first is the lower bound of the range (the same as lower_bound), and pair::second is the upper bound (the same as upper_bound).

如上,即 pair::first 为 按顺序第一个 >= val 的结点的 iterator,pair::second 为 按顺序第一个 > val 的结点的 iterator.
(ps.这个函数对 set 容器来说用处不大)


2. multiset

multiset - C++ Reference (cplusplus.com)

相比与 setmultiset 允许重复值。因此,multiset 对于数据仅具有排序的作用。multiset 与 set 很相似,以下简化表达,且只讲解一些存在差异的地方。

①对于重复值,插入到右树还是左树都可以。因为插入之后导致搜索二叉树不平衡会旋转,图例如下;

②如果有重复值,find(val) 会返回中序遍历序列的第一个值为 val 的 iterator;

equal_range(val) → 返回值 pair::first >= valpair::second > val →  [ first, second ) 在这个区间内,则找到了所有重复的 val 值结点;

count(val) 会统计值为 val 的结点个数;

erase(val) 删除所有值为 val 的结点,并返回删除结点的个数(return size_t)


3. map

map - C++ Reference (cplusplus.com)

map 是一个 KV 模型的搜索二叉树。KV 即 key and valuevaluekey 的映射值。key 不可被修改,value 可被修改。根据下表,KV的 K 即key_type,V即 mapped_type。KV 整体即 value_type。
(前面自己实现的 KV 模型的BST的 key 与 value 是分开的,而 map 的处理是将两者融合进 pair对象中。)

member typedefinition
key_typeThe first template parameter (Key)
mapped_typeThe second template parameter (T)
value_typepair<const key_type,mapped_type>

1)insert

single element (1)
pair<iterator,bool> insert (const value_type& val);
with hint (2)
iterator insert (iterator position, const value_type& val);
range (3)
template <class InputIterator>
  void insert (InputIterator first, InputIterator last);

value_type → pair<const key_type,mapped_type> → pair<const Key,T>

如下代码,Key → string,T → string. 

void test_map()
{
	std::map<std::string, std::string> dict;
	dict.insert(std::pair<std::string, std::string>("sort", "排序"));//方式一:匿名对象

	std::pair<std::string, std::string> pr("test", "测试");//方式二
	dict.insert(pr);

	pr = { "cruel","残忍的" };
	dict.insert(pr);

	auto it = dict.begin();
	while (it != dict.end())
	{
		std::cout << it->first << ":" << it->second << " ";
		++it;
	}
	std::cout << std::endl;
}
  • std::make_pair

对于 map 的 insert ,以下介绍一种更常用的写法。

std::make_pair:(function)<utility>

template <class T1, class T2>
  pair<T1,T2> make_pair (T1 x, T2 y);
//插入更常用的写法↓  方式三:make_pair()
dict.insert(std::make_pair("autumn","秋天"));

2)用 map 统计次(个)数

思路:对于一组给定的数据,实例化出相应的 map 对象,在 map<Type, int> 中依次查找给定的数据,该数据未在其(map实例化的对象)中则插入数据,若有则对其数量(int)++.

示例代码如下:

void test_Count()
{
	std::vector<std::string> vstr = { "apple","banana","grap","apple","cherry","cherry","grap","cherry","lemon","grap" };
	std::map<std::string, int> Count;
	for (auto str : vstr)
	{
		if (Count.find(str) == Count.end())//没找到
			Count.insert(std::make_pair(str, 1));
		else//找到了
			++Count.find(str)->second;
	}

	auto it = Count.begin();
	while (it != Count.end())
	{
		std::cout << it->first << ":" << it->second << std::endl;
		++it;
	}
}
  • ⭐ std::map::operator[]

mapped_type& operator[] (const key_type& k);

Access element

If k matches the key of an element in the container, the function returns a reference to its mapped value.

If k does not match the key of any element in the container, the function inserts a new element with that key and returns a reference to its mapped value. Notice that this always increases the container size by one, even if no mapped value is assigned to the element (the element is constructed using its default constructor).

A similar member function, map::at, has the same behavior when an element with the key exists, but throws an exception when it does not.

A call to this function is equivalent to:
(*((this->insert(make_pair(k,mapped_type()))).first)).second

注意该函数的返回值:

Member type mapped_type is the type of the mapped values in the container, defined in map as an alias of its second template parameter (T) 

分析:

  1. 如上,通过阅读文档可知,[key] → 该操作有两种情况:①key已经存在,则返回key的映射值(mapped value)的引用;②key不存在,则插入key,并返回这个key的映射值的引用。
  2. A call to this function is equivalent to: (*((this->insert(make_pair( k,mapped_type() ))).first)).second
    逐步拆解这句代码👇

 

通过使用这个函数,下面进一步优化上面统计次(个)数的代码:

void test_Count2()
{
	std::vector<std::string> vstr = { "apple","banana","grap","apple","cherry","cherry","grap","cherry","lemon","grap" };
	std::map<std::string, int> Count;
	for (auto str : vstr)
	{
		++Count[str];//⭐
	}

	auto it = Count.begin();
	while (it != Count.end())
	{
		std::cout << it->first << ":" << it->second << std::endl;
		++it;
	}
}

4. multimap

multimap - C++ Reference (cplusplus.com)

  • 相比于 mapmultimap 允许重复值。
  • 不支持 operator[]

END

  • 10
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

畋坪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值