C++STL之set和map的接口使用介绍

set

set的使用

set的插入

cpp 复制代码
int main()
{
	//去重+升序排序
	set<int>s;
	s.insert(5);
	s.insert(2);
	s.insert(7);
	s.insert(5);
}

set在插入的时候会默认升序,并且不会插入的值不会重复

set的遍历

cpp 复制代码
int main()
{
	set<int>s = { 4,2,7,2,8,5,9 };

	for (auto e : s)
	{
		cout << e << " ";
	}
	cout << endl;
}

set的遍历就是使用迭代器去遍历

set的find接口

cpp 复制代码
#include<set>
void test_set()
{
    set<int> s;
    s.insert(4);
    s.insert(2);
    s.insert(3);
    s.insert(6);
    s.insert(1);
    s.insert(8); 
    set<int>::iterator ret = s.find(4);//  O(logN)
    //这两个有效率的区别
    auto ret = find(s.begin(),s.end(),4);//这个是暴力查找,在一段迭代器区间里进行查找 O(N)
}

find我们可以用算法里面的find接口,也可以用set提供的find接口

cpp 复制代码
set<int>::iterator ret = s.find(4);//O(logN)
    //这两个有效率的区别
auto ret = find(s.begin(),s.end(),4);//这个是暴力查找,在一段迭代器区间里进行查找 O(N)

但是这两个的区别是效率的问题,set的底层是二叉搜索树的性质,时间复杂度为O(logN),而算法当中的find是在一段迭代器区间暴力查询,在一段迭代器区间里进行查找,时间复杂度O(N)

set的erase接口

删除接口可以传迭代器,也可以传val,也可以传迭代器区间

cpp 复制代码
int main()
{
	set<int>s = { 4,2,7,2,8,5,9 };

	for (auto e : s)
	{
		cout << e << " ";
	}
	cout << endl;

	//1、直接删除x
	int x;
	cin >> x;
	int num = s.erase(x);
	if (num == 0)
	{
		cout << x << "不存在" << endl;
	}
	else
		cout << "删除成功!" << endl;

    //2、使用迭代器删除
	cin >> x;
	auto pos = s.find(x);
	if (pos != s.end()) {
		// 删除成功
		cout << "找到 " << *pos << ",正在删除..." << endl;

		s.erase(pos);  // 删除
		cout << x << "删除成功!" << endl;
	}
	else {
		cout << x << "不存在!" << endl;
	}

	return 0;
}
cpp 复制代码
int main()
{
	std::set<int> myset;
	for (int i = 1; i < 10; i++)
		myset.insert(i * 10);
	//3、实现查找到的[itlow, itup)包含[30, 60]区间

	// 返回>= 30
	auto itlow = myset.lower_bound(30);
	// 返回> 60
	auto itup = myset.upper_bound(60);
	// 删除这段区间的值

	myset.erase(itlow, itup);
	for (auto e : myset)
	{
		cout << e << " ";
	}
	cout << endl;
	return 0;
}

map介绍

map是关联容器,它按照特定的次序(按照key来比较)存储由键值key和值value组合而成的元素

1、在map中,键值key通常用于排序和唯一地标识元素,而值value中存储与此键值key关联的内容。键值key和值value的类型可能不同,并且在map的内部,key与value通过成员类型value_type绑定在一起,为其取名为pair:typedef pair value_type;

2、在内部,map中的元素总是按照键值key进行排序的

3、map中通过键值访问单个元素的速度通常比unordered_map容器慢,但map允许根据顺序对元素进行直接迭代(即对map中的元素进行迭代时,可以得到一个有序的序列)。

4、map支持下标访问符,即在[]中放入key,就可以找到与key对应的value。

5、map通常被实现为二叉搜索树(更准确的说:平衡二叉搜索树(红黑树))。

键值对

用来表示具有一一对应关系的一种结构,该结构中一般只包含两个成员变量key和value,key代表键值,value表示与key对应的信息。例如:建立一个英汉互典的字典,那该字典中必然有英文字母单词与对应的中文含义

cpp 复制代码
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)
    {}
};

map

map的使用

map的模板参数

key: 键值对中key的类型

T: 键值对中value的类型

Compare: 比较器的类型,map中的元素是按照key来比较的,缺省情况下按照小于来比较,一般情况下(内置类型元素)该参数不需要传递,如果无法比较时(自定义类型),需要用户自己显式传递比较规则(一般情况下按照函数指针或者仿函数来传递)

Alloc:通过空间配置器来申请底层空间,不需要用户传递,除非用户不想使用标准库提供的空间配置器

注意:在使用map时,需要包含头文件。

map的插入

cpp 复制代码
#include <map>
int main()
{
	map<string, string> dict;
	//C++98
	pair<string, string>kvl("left", "排序");
	dict.insert(kvl);
	dict.insert(pair<string, string>("left", "左边"));
	dict.insert(make_pair("pair", "左边"));

	//C++11
	dict.insert({ "right", "左边" });
	dict.insert({{ "string", "字符串" }, { "map","地图、映射" }});
	return 0;
}

因为C++98的写法太麻烦,并且效率低

但是下面的C++11的写法,效率高不麻烦

所以建议去使用C++11的写法

那么我们应该怎么去读取呢?

cpp 复制代码
map<int,int>::iterator it = m.begin();
    while(it != m.end())
    {
        cout<<*it<<" ";//这里编译不过,这里如果这样的话需要返回两个值key和value,而C++不支持返回两个值,要返回两个值就需要构成一个结构来返回
        ++it;
    }

*cout<<it<<endl;这里编译不过,这里如果这样的话需要返回两个值key和value,而C++不支持返回两个值,要返回两个值就需要构成一个结构来返回

cpp 复制代码
int main()
{
	map<string, string> dict;
	//C++98
	pair<string, string>kvl("left", "排序");
	dict.insert(kvl);
	dict.insert(pair<string, string>("left", "左边"));
	dict.insert(make_pair("pair", "左边"));

	//C++11
	dict.insert({ "right", "左边" });
	dict.insert({{ "string", "字符串" }, { "map","地图、映射" }});
	auto e = dict.begin();
	while (e != dict.end())
	{
		//cout<<(*it).first << ":" << (*it).second << endl;
		//operator* 返回的是节点中值的引用
		cout << e->first << ":" << e->second << endl;//这里为了可读性省略了一个箭头
		//operator-> 返回的是节点中值的指针,也就是pair<k,v>*
		++e;
	}
	return 0;
}

也支持我们的for循环遍历

cpp 复制代码
for (auto e : dict)
{
	cout << e.first << ":" << e.second << endl;
}

统计字符串的个数

cpp 复制代码
void test_map3()
{
    string str[] = {"sort","sort","tree","insert","sort","tree","sort","test"};
    map<string,int> countMap;//统计字符串个数
    for(auto& e:str)
    {
        auto ret = countMap.fin1d(e);//返回一个迭代器
        if(ret == countMap.end())
        {
            //如果不存在,则插入
        	countMap.insert(make_pair(e,1));
            //countMap.insert(pair<>(e,1));
        }
        else
        {
            //如果存在,则次数++
            //(*ret).second++;
            ret->second++;
        }
    }
    
    for(auto& kv:countMap)
    {
        cout<<kv.first<<" "<<kv.second<<endl;
    }
}

统计次数的方式二:

正常来说如果insert插入成功了返回true,已经存在此时插入失败了返回false,但是我们可以看到insert的返回值是一个pair:

返回值是pair,其成员pair::first被设为一个迭代器,指向新插入的元素否则指向map中具有相等key的元素,如果插入了新元素,则将该对中的第二个元素pair::second设置为true;否则如果已经存在相等的key,则将其设置为false。

cpp 复制代码
void test_map4()
{
    string str[] = { "sort","sort","tree","insert","sort","tree","sort","test" };
    map<string, int> countMap;
    for (const auto& e : str)
    {
        //先插入,如果str在map中,insert会返回str所在的节点的迭代器,++次数即可
        pair<map<string, int>::iterator, bool> ret = countMap.insert(make_pair(e, 1));
        //auto ret = countMap.insert(make_pair(e, 1));
        if (ret.second == false)
        {
            //说明插入失败了,之前已经插入过了
            ret.first->second++;//ret.first是指向相同key的那个元素的迭代器,ret是insert返回的pair
        }
    }
    map<string, int>::iterator it = countMap.begin();
    while (it != countMap.end())
    {
        cout << it->first << " " << it->second << endl;
        ++it;
    }
}

operator[]的使用

统计次数方式3

operator[]的实现;

cpp 复制代码
mapped_type& operator[](const key_type& k)
{
	return (*((this->insert(make_pair(k,mapped_type()))).first)).second;//插入时的匿名对象默认值为0
}

operator[]的实现进行简化,也可以这样实现:

cpp 复制代码
mapped_type& operator[](const key_type& k)
{
    pair<iterator,bool> ret = insert(make_pair(k,mapped_type()));//插入时的匿名对象默认值为0
	return ret.first->second;
}

这里也分两种情况:

k在map中,insert插入失败,因为k已经有了,insert返回的pair会带出k在map中存储节点的迭代器,通过这个迭代器,我们可以拿到k对应的value值,进行返回。

k不在map中,insert进行插入,插入的值是pair<k,value()>,insert返回新插入值节点的迭代器,通过这个迭代器,我们可以拿到k对应的value值,进行返回。

总结map的operator[]特征:

k不存在时,插入默认构造函数生成缺省值的value的pair<k,v()>

k存在时,返回k对应的value值

cpp 复制代码
void test_map5()
{
    string str[] = { "sort","sort","tree","insert","sort","tree","sort","test" };
    map<string,int> countMap;
    //[]的用法
    for(const auto& e:str)
    {
        countMap[e]++;//countMap[]返回的是value
    }
    for(const auto& kv:countMap)
    {
        cout<<kv.first<<" "<<kv.second<<endl;
    }
}

1、如果k不在map中,先插入<k,V()>,返回新插入节点中V对象的引用

2、如果k已经在map中,返回k所在节点中对应V对象的引用

cpp 复制代码
void test_map6()
{
    map<string,string> dict;
    dict.insert(make_pair("sort","排序"));
    dict["left"] = "左边";//插入+修改
    dict["insert"];//插入
    dict["insert"] = "插入";//已经存在所以只是修改
    dict["left"] = "左边、剩余";//已经存在所以只是修改
}

第一个是插入+修改,因为刚开始没有则插入,[]返回的是value,所以赋值相当于将他修改了,第二个只是插入,第三个只是修改,因为insert已经存在了就没有再插入只是修改,第四个也已经存在,所以只是修改

map和multimap的对比

关于map和multimap的区别:

  • 允许键值冗余与否,map不允许键值冗余,multimap允许键值冗余
  • multimap不支持[]

在multiset中查找一个值的实现:比如4,找到4以后,不能停止,要查找中序的第一个4,即找到4以后,要继续看4的左孩子是不是4,不是,就返回当前这个4,如果是,继续取左边的4,继续刚才的判断不断往后走

cpp 复制代码
auto pos = s.find(4);//返回中序的第一个4
while(pos!=s.end() && *pos == 4)
{
    //找出所有的4
    cout<<*pos<<" ";
    ++pos;
}

如果想看4有几个,有对应的接口

cpp 复制代码
cout<<s.count(4)<<endl;
cout<<s.count(8 )<<endl;

下面我们看一下map和multmap的区别:

cpp 复制代码
void test_map8()
{
    map<string,string> dict;
    dict.insert(make_pair("left","左边"));
    
    multimap<string,string> mdict;
    mdict.insert(make_pair("left","左边"));
    mdict.insert(make_pair("left","剩余"));
    mdict.insert(make_pair("left","左边"));
}

multimap在value相同时也会插入

相关推荐
java修仙传2 小时前
力扣hot100:跳跃游戏||
算法·leetcode·游戏
闻缺陷则喜何志丹2 小时前
【模拟】P9670 [ICPC 2022 Jinan R] Frozen Scoreboard|普及+
c++·算法·模拟·洛谷
永远都不秃头的程序员(互关)2 小时前
【K-Means深度探索(十一)】K-Means VS 其他聚类算法:如何选择最合适的工具?
算法·kmeans·聚类
l1t2 小时前
利用DeepSeek辅助翻译clickhouse SQL为DuckDB 格式求解Advent of Code 2025第10题 电子工厂 第二部分
数据库·人工智能·sql·clickhouse·duckdb
洛生&2 小时前
Nested Ranges Count
算法
老鼠只爱大米2 小时前
LeetCode经典算法面试题 #142:环形链表 II(哈希表、快慢指针等多种方法详细解析)
算法·leetcode·链表·快慢指针·floyd算法·环形链表
DarkAthena2 小时前
【GaussDB】分析函数性能优化案例-row_number改写
数据库·sql·oracle·性能优化·gaussdb
王老师青少年编程2 小时前
2024年6月GESP真题及题解(C++八级): 最远点对
c++·题解·真题·gesp·csp·八级·最远点对
techdashen2 小时前
借助gh-ost,对MySQL大表进行表结构的变更
数据库·mysql