当前位置: 首页 > news >正文

【C++】map和set的使用

​🌠 作者:@阿亮joy.
🎆专栏:《吃透西嘎嘎》
🎇 座右铭:每个优秀的人都有一段沉默的时光,那段时光是付出了很多努力却得不到结果的日子,我们把它叫做扎根
在这里插入图片描述

目录

    • 👉关联式容器👈
    • 👉键值对👈
    • 👉树形结构的关联式容器👈
      • set
        • 1. set 的介绍
        • 2. set 的使用
      • multiset
        • 1. multiset 的介绍
        • 2. multiset 的使用
      • map
        • 1. map 的介绍
        • 2. map 的使用
      • multimap
        • 1. multimap 的介绍
        • 2. multimap 的使用
    • 👉前K个高频单词👈
    • 👉两个数组的交集👈
    • 👉总结👈

👉关联式容器👈

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

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

👉键值对👈

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

SCI - 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)
	{}
};

👉树形结构的关联式容器👈

根据应用场景的不同,STL 总共实现了两种不同结构的关联式容器:树型结构与哈希结构。树型结构的关联式容器主要有四种:map、set、multimap、multiset。这四种容器的共同点是:使用平衡搜索树(即红黑树)作为其底层结果,容器中的元素是一个有序的序列。下面一依次介绍每一个容器。

set

1. set 的介绍

  1. set 是按照一定次序存储元素的容器。
  2. 在 set 中,元素的 value 也标识它(value 就是 key,类型为 T),并且每个 value 必须是唯一的。 set 中的元素不能在容器中修改(元素总是 const),但是可以从容器中插入或删除它们。
  3. 在内部,set 中的元素总是按照其内部比较对象(类型比较)所指示的特定严格弱排序准则进行排序。
  4. set 容器通过 key 访问单个元素的速度通常比unordered_set 容器慢,但它们允许根据顺序对子集进行直接迭代。
  5. set 在底层是用二叉搜索树(红黑树)实现的。

注意:

  1. 与 map / multimap 不同,map / multimap 中存储的是真正的键值对 <key, value>,set 中只 value,但在底层实际存放的是由 <value, value> 构成的键值对。
  2. set 中插入元素时,只需要插入 value 即可,不需要构造键值对。
  3. set 中的元素不可以重复(因此可以使用 set 进行去重)。
  4. 使用 set 的迭代器遍历 set 中的元素,可以得到有序序列。
  5. set 中的元素默认按照小于来比较。
  6. set 中查找某个元素,时间复杂度为 l o g 2 N log_2 N log2N

2. set 的使用

  1. set 的模板参数列表

在这里插入图片描述
T:set 中存放元素的类型,实际在底层存储 <value, value> 的键值对。
Compare:set 中元素默认按照小于来比较,即中序遍历的结果是升序(注:Compare 可以自己写一个仿函数来控制比较的方式)。
Alloc:set 中元素空间的管理方式,使用 STL 提供的空间配置器管理。

  1. set 的构造和赋值运算符重载
函数声明功能介绍
set (const Compare& comp = Compare(), const Allocator = Allocator() );构造空的 set
set (InputIterator first, InputIterator last, const Compare& comp = Compare(), const Allocator& = Allocator() );用 [first, last) 区间中的元素构造
set (const set& x);set 的拷贝构造
set& operator= (const set& x);set 的运算符重载

注:set 的拷贝构造和赋值运算符重载,代价是比较大的!

  1. set 的修改操作
函数声明功能介绍
pair<iterator,bool> insert (const value_type& x )在 set 中插入元素 x,实际插入的是 <x, x> 构成的键值对,如果插入成功,返回 <该元素在set中的位置,true>;如果插入失败,说明 x 在 set 中已经存在,返回 <x在set中的位置,false>
void erase ( iterator position )删除 set 中 position 位置上的元素
size_type erase ( const key_type& x )删除 set 中值为 x 的元素,返回删除的元素的个数
void erase ( iterator first, iterator last )删除 set 中 [first, last) 区间中的元素
void swap (set<Key,Compare,Allocator>&st );交换 set 中的元素,通过交换根节点就可以实现了。
void clear ( )将 set 中的元素清空
iterator find ( const key_type& x ) const返回 set 中值为 x 的元素的位置
size_type count ( constkey_type& x ) const返回 set 中值为 x 的元素的个数,可以通过该接口判断 x 在不在

注:set 中的元素不允许修改,因为修改元素可能会破坏二叉搜索树的结构。

在这里插入图片描述

  1. set 的使用举例
void SetTest1()
{
	// 排序+去重
	set<int> s = { 1, 2,1,6,3, 8,5,9 };	// C++11的列表初始化
	set<int>::iterator it = s.begin();
	while (it != s.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
}

在这里插入图片描述

void SetTest2()
{
	// greater的该文件是functional
	set<int, greater<int>> s = { 1, 2,1,6,3, 8,5,9 };	// C++11的列表初始化
	set<int>::iterator it = s.begin();
	while (it != s.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
}

在这里插入图片描述

void SetTest3()
{
	int arr[] = { 1,2,1,6,3,8,5,9 };
	// 迭代器区间初始化
	set<int, greater<int>> s(arr, arr + sizeof(arr) / sizeof(arr[0]));	
	
	for (auto e : s)
	{
		cout << e << " ";
	}
	cout << endl;

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

	auto it = s.find(2);
	if (it != s.end())
	{
		s.erase(it);
		for (auto e : s)
		{
			cout << e << " ";
		}
		cout << endl;
	}
}

在这里插入图片描述
注:find 函数如果没有找到指定的 key 值,会返回 end();如果找到了,会返回该 key 值的迭代器位置。所以如果要通过迭代器位置来删除元素,需要判断该迭代器位置是否为 end()。而通过 key 值来删除的话,容器中没有指定 key 值的话,就返回 0;如果有的话,就返回 1。(注:这里的 0 和 1 是 key 值在容器 set 中的个数)

void SetTest4()
{
	set<int> myset;
	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);	// 返回大于等于val的第一个元素的迭代器位置     
	itup = myset.upper_bound(60);   // 返回大于val的第一个元素的迭代器位置

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

	std::cout << "myset contains:";
	for (auto it = myset.begin(); it != myset.end(); ++it)
		std::cout << ' ' << *it;
	cout << endl;
}

在这里插入图片描述

void SetTest5()
{
	std::set<int> myset;

	for (int i = 1; i <= 5; i++) myset.insert(i * 10);   // myset: 10 20 30 40 50

	std::pair<std::set<int>::const_iterator, std::set<int>::const_iterator> ret;
	// equal_range的返回值是键值对pair<iterator,iterator>
	ret = myset.equal_range(30);  // x <= val < y

	std::cout << "the lower bound points to: " << *ret.first << '\n';
	std::cout << "the upper bound points to: " << *ret.second << '\n';
}

在这里插入图片描述

multiset

1. multiset 的介绍

  1. multiset 是按照特定顺序存储元素的容器,其中元素是可以重复的。
  2. 在 multiset 中,元素的 value 也会识别它(因为multiset 中本身存储的就是 <value, value> 组成的键值对,因此 value 本身就是 key,key 就是 value,类型为 T)。multiset 元素的值不能在容器中进行修改(因为元素总是 const 的),但可以从容器中插入或删除。
  3. 在内部,multiset 中的元素总是按照其内部比较规则(类型比较)所指示的特定严格弱排序准则进行排序。
  4. multiset 容器通过 key 访问单个元素的速度通常比unordered_multiset 容器慢,但当使用迭 代器遍历时会得到一个有序序列。
  5. multiset 的底层结构为二叉搜索树(红黑树)。

注意:

  1. multiset 中在底层中存储的是 <value, value> 的键值对。
  2. mtltiset的插入接口中只需要插入即可。
  3. 与 set 的区别是 multiset 中的元素可以重复,set 是中value 是唯一的。
  4. 使用迭代器对 multiset 中的元素进行遍历,可以得到有序的序列。
  5. multiset 中的元素不能修改。
  6. 在 multiset 中找某个元素,时间复杂度为 O ( l o g 2 N ) O(log_2 N) O(log2N)
  7. multiset 的作用是可以对元素进行排序。

2. multiset 的使用

void MultisetTest1()
{
	int arr[] = { 1, 2,1,8,5,7,6,2,9 };
	multiset<int> s(arr, arr + sizeof(arr) / sizeof(arr[0]));

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

	cout << s.count(1) << endl;

	auto pos = s.find(2);
	while (pos != s.end())
	{
		cout << *pos << " ";
		++pos;
	}
	cout << endl;

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

在这里插入图片描述

注:find 返回的是中序遍历中第一个 key 值的迭代器位置,通过 key 值来调用 erase 函数是会删除所有的 key 值的。

map

1. map 的介绍

  1. map 是关联容器,它按照特定的次序(按照 key 来比较)存储由键值 key 和值 value 组合而成的元素。
  2. 在 map 中,键值 key 通常用于排序和唯一地标识元素,而值 value 中存储与此键值 key 关联的内容。键值 key 和值 value 的类型可能不同。在 map 的内部,key 与value 通过成员类型 value_type 绑定在一起,为其取别名称为 pair:typedef pair<const key, T> value_type;
  3. 在内部,map 中的元素总是按照键值 key 进行比较排序的。
  4. map 中通过键值访问单个元素的速度通常比unordered_map 容器慢,但 map 允许根据顺序对元素进行直接迭代(即对 map 中的元素进行迭代时,可以得到一个有序的序列)。
  5. map 支持下标访问符,即在[]中放入key,就可以找到与 key 对应的 value。
  6. map 通常被实现为二叉搜索树(更准确地说,是平衡二叉搜索树(红黑树))。

2. map 的使用

  1. map 的模板参数说明

在这里插入图片描述
key:键值对中 key 的类型

T:键值对中 value 的类型

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

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

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

map 的修改操作和 set 的修改操作基本是相同的,就是 map 比 set 多了一个 value 值。

在这里插入图片描述
注:make_pair 是一个函数模板,其通常被定义成内联函数,其头文件是 utility,通过会被间接包含。

void MapTest1()
{
	map<string, string> dict;
	// 有名对象
	pair<string, string> kv1("sort", "排序");
	dict.insert(kv1);
	// 匿名对象
	dict.insert(pair<string, string>("test", "测试"));
	dict.insert(pair<string, string>("sort", "排序"));
	dict.insert(pair<string, string>("string", "字符串"));

	typedef pair<string, string> DictKV;
	dict.insert(DictKV("left", "左边"));
	dict.insert(make_pair("left", "剩下"));	// make_pair是个函数模板

	//map<string, string>::iterator it = dict.begin();
	auto it = dict.begin();
	while (it != dict.end())
	{
		//cout << (*it).first << ":" << (*it).second << endl;
		// operator->返回的是结构的指针,再加上一个->就是结构中的数据
		// 为了可读性,编译器省略了一个->
		cout << it->first << ":" << it->second << endl;
		++it;
	}
	cout << endl;

	for (auto& kv : dict)
	{
		cout << kv.first << ":" << kv.second << endl;
	}
	cout << endl;
}

注:map 的 operator* 返回的是结构体 pair。

在这里插入图片描述
注:map 和 set 的迭代器都是双向迭代器!!!

统计出现次数

void MapTest2()
{
	string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" };
	map<string, int> countMap;
	for (auto& str : arr)
	{
		map<string, int>::iterator it = countMap.find(str);
		if (it != countMap.end())
			++it->second;
		else
			countMap.insert(make_pair(str, 1));
	}

	for (auto& kv : countMap)
	{
		cout << kv.first << ":" << kv.second << endl;
	}
	cout << endl;
}

在这里插入图片描述

在这里插入图片描述

void MapTest3()
{
	string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" };
	map<string, int> countMap;
	for (auto& str : arr)
	{
		// 1. str不在countMap中,插入pair(str, int()),再对value的引用进行++
		// 2. str在countMap中,对value的引用进行++
		++countMap[str];
	}

	for (auto& kv : countMap)
	{
		cout << kv.first << ":" << kv.second << endl;
	}
	cout << endl;
}

multimap

1. multimap 的介绍

  1. multimap 是关联式容器,它按照特定的顺序,存储由 key 和 value 映射成的键值对 <key, value>,其中多个键值对之间的 key 是可以重复的。
  2. 在 multimap 中,通常按照 key 排序和唯一地标识元素,而映射的 value 存储与 key 关联的内容。key 和 value 的类型可能不同,通过 multimap 内部的成员类型 value_type 组合在一起,value_type 是组合 key 和 value 的键值对:typedef pair<const Key, T> value_type;
  3. 在内部,multimap 中的元素总是通过其内部比较对象,按照指定的特定严格弱排序标准对 key 进行排序的。
  4. multimap 通过 key 访问单个元素的速度通常比unordered_multimap 容器慢,但是使用迭代器直接遍历 multimap 中的元素可以得到关于 key 有序的序列。
  5. multimap 在底层用二叉搜索树(红黑树)来实现。

注意:multimap 和 map 的唯一不同就是:map中的key 是唯一的,而 multimap 中 key 是可以重复的。

2. multimap 的使用

multimap 中没有重载[],原因是 multimap 允许键值冗余。当有多个相同的键值时,不知道返回哪一个。

void MultimapTest()
{
	multimap<string, string> mdict;
	mdict.insert(make_pair("sort", "排序"));
	mdict.insert(make_pair("left", "左边"));
	mdict.insert(make_pair("left", "剩下"));
	mdict.insert(make_pair("string", "字符串"));

	for (auto& kv : mdict)
	{
		cout << kv.first << ":" << kv.second << endl;
	}
	cout << endl;
}

在这里插入图片描述

👉前K个高频单词👈

给定一个单词列表 words 和一个整数 k ,返回前 k 个出现次数最多的单词。

返回的答案应该按单词出现频率由高到低排序。如果不同的单词有相同出现频率, 按字典顺序排序。

在这里插入图片描述

思路一:可以通过优先级队列来做,认为出现次数多的单词优先级高。如果出现次数相同,则在字典顺序靠前的优先级高。根据优先级比较的规则,我们自己实现一个仿函数即可。

class Solution 
{
    struct Less
    {
        bool operator()(const pair<string, int>& kv1, const pair<string, int>& kv2) const
        {
            if(kv1.second < kv2.second)
                return true;
            else if(kv1.second == kv2.second && kv1.first > kv2.first)
                return true;
            else
                return false;
        }
    };
public:
    vector<string> topKFrequent(vector<string>& words, int k) 
    {
        // 统计出现的次数
        map<string, int> countMap;
        for(auto& str : words)
        {
            ++countMap[str];
        }
        // TOPK问题
        priority_queue<pair<string, int>, vector<pair<string, int>>, Less> mh(countMap.begin(), countMap.end());

        vector<string> v;
        while(k--)
        {
            v.push_back(mh.top().first);
            mh.pop();
        }
        return v;
    }
};

在这里插入图片描述

思路二:因为countMap中已经按照字典顺序排序了,所以我们可以采用稳定的排序对countMap中的元素按照出现次数来排序就行了。sort 底层是一个快排,快排是不稳定排序,那么我们可以给 sort 传一个比较方式,就可以保证稳定性了。注:sort 要求传入的迭代器是随机迭代器,而 map 的迭代器是双向迭代器。那我们可以先将 map 的元素存储 vector 中。

class Solution 
{
    struct Greater
    {
        bool operator()(const pair<string, int>& kv1, const pair<string, int>& kv2) const
        {
            if(kv1.second > kv2.second)
                return true;
            else if(kv1.second == kv2.second && kv1.first < kv2.first)
                return true;
            else
                return false;
        }
    };
public:
    vector<string> topKFrequent(vector<string>& words, int k) 
    {
        // 统计出现的次数
        map<string, int> countMap;
        for(auto& str : words)
        {
            ++countMap[str];
        }

        vector<pair<string, int>> sortV(countMap.begin(), countMap.end());  
        sort(sortV.begin(), sortV.end(), Greater());

        vector<string> v;
        for(size_t i = 0; i < k; ++i)
        {
            v.push_back(sortV[i].first);
        }
        return v;
    }
};

在这里插入图片描述
上面的解法是通过比较方式来控制稳定性的。那么我们也可以使用库里提供的稳定排序stable_sort来控制稳定性。

class Solution 
{
    struct Greater
    {
        bool operator()(const pair<string, int>& kv1, const pair<string, int>& kv2) const
        {
            if(kv1.second > kv2.second)
                return true;
            else
                return false;
        }
    };
public:
    vector<string> topKFrequent(vector<string>& words, int k) 
    {
        // 统计出现的次数
        map<string, int> countMap;
        for(auto& str : words)
        {
            ++countMap[str];
        }

        vector<pair<string, int>> sortV(countMap.begin(), countMap.end());  
        stable_sort(sortV.begin(), sortV.end(), Greater());

        vector<string> v;
        for(size_t i = 0; i < k; ++i)
        {
            v.push_back(sortV[i].first);
        }
        return v;
    }
};

在这里插入图片描述

思路三:因为countMap已经按照字典顺序排序了,那可以将其中的元素依次插入到以整型为 key 值的multimap中即可。

class Solution 
{
public:
    vector<string> topKFrequent(vector<string>& words, int k) 
    {
        // 统计出现的次数
        map<string, int> countMap;
        for(auto& str : words)
        {
            ++countMap[str];
        }
		// 传greater<int>也是为了保证稳定性
        multimap<int, string, greater<int>> sortMap;
        for(auto& str : countMap)
        {
            sortMap.insert(make_pair(str.second, str.first));
        }

        vector<string> v;
        auto it = sortMap.begin();
        for(size_t i = 0; i < k; ++i)
        {
            v.push_back(it->second);
            ++it;
        }
        return v;
    }
};

在这里插入图片描述

👉两个数组的交集👈

给定两个数组 nums1 和 nums2 ,返回 它们的交集 。输出结果中的每个元素一定是唯一的。我们可以不考虑输出结果的顺序 。

在这里插入图片描述

思路:因为set容器是对元素进行排序加上去重的,所以我们可以用数组中的元素来构造set,然后再来求交集。求交集的思路:谁小谁往后走,相等就添加到vector中并同时往后走。

class Solution 
{
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) 
    {
        set<int> s1(nums1.begin(), nums1.end());
        set<int> s2(nums2.begin(), nums2.end());

        auto it1 = s1.begin();
        auto it2 = s2.begin();

        vector<int> v;

        while(it1 != s1.end() && it2 != s2.end())
        {
            if(*it1 < *it2)
                ++it1;
            else if(*it1 > *it2)
                ++it2;
            else
            {
                v.push_back(*it1);
                ++it1;
                ++it2;                
            }
        }

        return v;
    }
};

在这里插入图片描述
补充:求差集的思路:先用两个数组的元素构造出两个et,然后遍历set。谁小就将谁添加到vector中并且往后走,相等时就同时加加。求并集只需要将两个数组的元素插入到set中就能得到两个数组的并集了。

class Solution 
{
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) 
    {
        set<int> s1(nums1.begin(), nums1.end());
        set<int> s2(nums2.begin(), nums2.end());

        auto it1 = s1.begin();
        auto it2 = s2.begin();

        vector<int> v;

        while(it1 != s1.end() && it2 != s2.end())
        {
            if(*it1 < *it2)
            {
                v.push_back(*it1);
                ++it1;
            }
            else if(*it1 > *it2)
            {
                v.push_back(*it2);
                ++it2;
            }
            else
            {
                ++it1;
                ++it2;                
            }
        }

        // 后面的元素都是相差的元素
        while(it1 != s1.end())
        {
            v.push_back(*it1);
            ++it1;
        }
        while(it2 != s2.end())
        {
            v.push_back(*it2);
            ++it2;
        }

        return v;
    }
};

👉总结👈

本篇博客主要讲解了键值对、关联式容器 set、multiset、map 和 multiset 以及两道 OJ 题前 K 个高频单词和两个数组的交集等等。那么以上就是本篇博客的全部内容了,如果大家觉得有收获的话,可以点个三连支持一下!谢谢大家!💖💝❣️

相关文章:

  • 做网站销售提成怎么算/网站注册流程
  • 网站开发的技术支持/最快新闻资讯在哪看
  • css不规则网站导航怎么做/人员优化是什么意思
  • iis wordpress httpd.ini 无后缀/seo点击优化
  • 公司做网站的流程/百度推广一天费用200
  • 网站建设未来发展的趋势/电商平台排行榜前十名
  • Pytorch DataLoader中的num_workers (选择最合适的num_workers值)
  • 第328场周赛2537. 统计好子数组的数目
  • JDK1.8使用的垃圾回收器和执行GC的时长以及GC的频率
  • 【Django项目开发】django的信号机制(八)
  • JUC面试(一)——JUCJMMvolatile 1.0
  • Xinlinx zynq7020国产替代 FMQL20S400 全国产化 ARM 核心板+扩展板
  • Go语言开发小技巧易错点100例(五)
  • 算法leetcode|31. 下一个排列(rust重拳出击)
  • SpringCloud-Netflix学习笔记03——什么是Eureka
  • 测试篇(二): 如何合理的创建bug、bug的级别、bug的生命周期、跟开发产生争执怎么办
  • springboot 项目自定义log日志文件提示系统找不到指定的文件
  • 【数据结构与算法】顺序表的原理及实现