0
点赞
收藏
分享

微信扫一扫

C++——map和set的使用以及map系列

small_Sun 2024-09-21 阅读 21

目录

map和set的使用

1. 序列式容器和关联式容器

2. set系列的使⽤

2.1 set和multiset参考⽂档

2.2 set类的介绍

2.3 set的构造和迭代器

2.4 set的增删查

set的增删查关注以下⼏个接⼝即可:

2.6 find和erase使⽤样例:

lower_bound(); upper_bound();

2.7 multiset和set的差异(可重复set(multiset))

那么到这里来一个set使用的例题

下一题,环形链表也超简单。

3. map系列的使⽤

3.1 map和multimap参考⽂档

3.2 map类的介绍

3.3 pair类型介绍

3.4 map的构造

3.5 map的增删查

3.6 map的数据修改

3.7 构造遍历及增删查使⽤样例

3.8 map的迭代器和[]功能样例:

利用operator[] 来统计水果出现的次数

3.9 multimap和map的差异

最后,来看一个运用列题:

总结:第一次做的时候总想着自己创建一个头节点,然后把整个链表跟它一样先把所有next串起来再进行查找,可是看完题解后,别人全用hash存起来后在进行连接是真的方便。


map和set的使用

1. 序列式容器和关联式容器

前⾯我们已经接触过STL中的部分容器如:string、vector、list、deque、list等,这 些容器统称为序列式容器,因为逻辑结构为线性序列的数据结构,两个位置存储的值之间⼀般没有紧密的关联关系,⽐如交换⼀下,他依旧是序列式容器。顺序容器中的元素是按他们在容器中的存储位置来顺序保存和访问的。

关联式容器也是⽤来存储数据的,与序列式容器不同的是,关联式容器逻辑结构通常是⾮线性结构,两个位置有紧密的关联关系,交换⼀下,他的存储结构就被破坏了。顺序容器中的元素是按关键字来保存和访问的。关联式容器有map/set系列和unordered_map/unordered_set系列。

本章节讲解的map和set底层是红⿊树,红⿊树是⼀颗平衡⼆叉搜索树。set是key搜索场景的结构,map是key/value搜索场景的结构。

2. set系列的使⽤

2.1 set和multiset参考⽂档

https://legacy.cplusplus.com/reference/set/

2.2 set类的介绍

template < class T, // set::key_type/value_type
           class Compare = less<T>, // set::key_compare/value_compare
           class Alloc = allocator<T>, // set::allocator_type
         > class set;

2.3 set的构造和迭代器

set的构造我们关注以下⼏个接⼝即可。

set的⽀持正向和反向迭代遍历,遍历默认按升序顺序,因为底层是⼆叉搜索树迭代器遍历⾛的中序;⽀持迭代器就意味着⽀持范围for,set的iterator和const_iterator都 不⽀持迭代器修改数据 修改关键字数据,破坏了底层搜索树的结构

// empty (1) ⽆参默认构造
explicit set (const key_compare& comp = key_compare(),
const allocator_type& alloc = allocator_type());

// range (2) 迭代器区间构造
template <class InputIterator>
set (InputIterator first, InputIterator last,
     const key_compare& comp = key_compare(),
     const allocator_type& = allocator_type());

// copy (3) 拷⻉构造
set (const set& x);

// initializer list (5) initializer 列表构造
set (initializer_list<value_type> il,
     const key_compare& comp = key_compare(),
     const allocator_type& alloc = allocator_type());

// 迭代器是⼀个双向迭代器
iterator -> a bidirectional iterator to const value_type

// 正向迭代器
iterator begin();
iterator end();

// 反向迭代器
reverse_iterator rbegin();
reverse_iterator rend();

2.4 set的增删查

set的增删查关注以下⼏个接⼝即可:

int main()
{
	set<int> s;
	s.insert(1);
	s.insert(2);
	s.insert(3);
	s.insert(4);
	auto it = s.begin();
	while (it != s.end())
	{
		cout << *it << " ";
		it++;
	}
	return 0;
}

int main()
{
	set<int> s;
	s.insert(1);
	s.insert(2);
	s.insert(3);
	s.insert(3);
	s.insert(4);
	auto it = s.begin();
	while (it != s.end())
	{
		cout << *it << " ";
		it++;
	}
	return 0;
}

auto it = s.find(3);
cout << *it << endl;

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

这里注意不能再while里面删除后再之间访问,会造成迭代器失效,要么就删除一个,然后再将这个迭代器赋值给it 

s.erase(3);
auto it = s.begin();
while (it != s.end())
{
	cout << *it << " ";
	it++;
}

左闭右开区间,那么就删除[2,5)之间的值,但不会删除5 

auto first = s.find(2);
auto end = s.find(5);
s.erase(first, end);
auto it = s.begin();
while (it != s.end())
{
	cout << *it << " ";
	it++;
}

//10 20 30 40 50 60 

//>=25 那么就是从30开始
auto first = s.lower_bound(25); 

//>55的迭代器,那么就是60
auto end = s.upper_bound(55);
s.erase(first, end);
auto it = s.begin();
while (it != s.end())
{
	cout << *it << " ";
	it++;
}
//10 20 60

2.5. insert和迭代器遍历使⽤样例:

#include<iostream>
#include<set>
using namespace std;
int main()
{
	// 去重+升序排序
	set<int> s;

	// 去重+降序排序(给⼀个⼤于的仿函数)
	//set<int, greater<int>> s;
	s.insert(5);
	s.insert(2);
	s.insert(7);
	s.insert(5);
	//set<int>::iterator it = s.begin();
	auto it = s.begin();
	while (it != s.end())
	{

		// error C3892: “it”: 不能给常量赋值
		// *it = 1;
		cout << *it << " ";
		++it;
	}
	cout << endl;

	// 插⼊⼀段initializer_list列表值,已经存在的值插⼊失败
	s.insert({ 2,8,3,9 });
	for (auto e : s)
	{
		cout << e << " ";
	}
	cout << endl;
	set<string> strset = { "sort", "insert", "add" };

	// 遍历string⽐较ascll码⼤⼩顺序遍历的
	for (auto& e : strset)
	{
		cout << e << " ";
	}
	cout << endl;
}

2.6 find和erase使⽤样例:

#include<iostream>
#include<set>
using namespace std;
int main()
{
	set<int> s = { 4,2,7,2,8,5,9 };
	for (auto e : s)
	{
		cout << e << " ";
	}
	cout << endl;

	// 删除最⼩值
	s.erase(s.begin());
	for (auto e : s)
	{
		cout << e << " ";
	}
		cout << endl;

	// 直接删除x
	int x;
	cin >> x;
	int num = s.erase(x);

	if (num == 0)
	{
		cout << x << "不存在!" << endl;
	}

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

	// 直接查找在利⽤迭代器删除x
	cin >> x;
	auto pos = s.find(x);

	if (pos != s.end())
	{
		s.erase(pos);
	}
	else
	{
		cout << x << "不存在!" << endl;
	}

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

	cout << endl;
	// 算法库的查找 O(N)
	auto pos1 = find(s.begin(), s.end(), x);

	// set⾃⾝实现的查找 O(logN)
	auto pos2 = s.find(x);

	// 利⽤count间接实现快速查找
	cin >> x;
	if (s.count(x))
	{
		cout << x << "在!" << endl;
	}
	else
	{
	cout << x << "不存在!" << endl;
	}
	return 0;
}

lower_bound(); upper_bound();

#include<iostream>
#include<set>
using namespace std;
int main()
{
	std::set<int> myset;
	for (int i = 1; i < 10; i++)
		myset.insert(i * 10); // 10 20 30 40 50 60 70 80 90
	for (auto e : myset)
	{
		cout << e << " ";
	}
	cout << endl;

	// 实现查找到的[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;
}

2.7 multiset和set的差异(可重复set(multiset))

multiset和set的使⽤基本完全类似,主要区别点在于multiset⽀持值冗余,那么

insert/find/count/erase都围绕着⽀持值冗余有所差异,具体参看下⾯的样例代码理解。

#include<iostream>
#include<set>
using namespace std;
int main()
{
	// 相⽐set不同的是,multiset是排序,但是不去重
	multiset<int> s = { 4,2,7,2,4,8,4,5,4,9 };
	auto it = s.begin();
	while (it != s.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;

	// 相⽐set不同的是,x可能会存在多个,find查找中序的第⼀个
	int x;
	cin >> x;
	auto pos = s.find(x);
	while (pos != s.end() && *pos == x)
	{
		cout << *pos << " ";
		++pos;
	}
	cout << endl;

	// 相⽐set不同的是,count会返回x的实际个数
	cout << s.count(x) << endl;

	// 相⽐set不同的是,erase给值时会删除所有的x
	s.erase(x);
	for (auto e : s)
	{
		cout << e << " ";
	}
	cout << endl;
	return 0;
}

那么到这里来一个set使用的例题

怎么样这题是不是超简单~

只需要进行去重,然后看一个数组的元素是否再另一个数组里出现。那么就要用set去重很方便。

class Solution {
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        set<int> s;
        for(auto e : nums1)
        {
            s.insert(e);
        }
        set<int> st;
        for(auto e : nums2)
        {
            st.insert(e);
        }

        vector<int> v;
        
        //只是在遍历s中的每一个元素 与st中的每一个元素进行比较
        for(auto e:s)
        {
            if(st.find(e)!=st.end())
            {
                v.push_back(e);
            }
        }
        return v;
    }
};

下一题,环形链表也超简单。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        unordered_map<ListNode*,int> hash;
        ListNode* root=head;
        while(root)
        {
            hash[root]++;
            if(hash[root]==2) return root;
            root=root->next;
        }
        return root;
    }
};

3. map系列的使⽤

3.1 map和multimap参考⽂档

https://legacy.cplusplus.com/reference/map/

3.2 map类的介绍

template < class Key, // map::key_type
	       class T, // map::mapped_type
		   class Compare = less<Key>, // map::key_compare
		   class Alloc = allocator<pair<const Key, T> > //map::allocator_type
	     > class map;

3.3 pair类型介绍

map底层的红⿊树节点中的数据,使⽤pair<Key, T>存储键值对数据。

typedef pair<const Key, T> value_type;

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)
	{}
	template<class U, class V>
	pair(const pair<U, V>& pr) : first(pr.first), second(pr.second)
	{}
};

template <class T1, class T2>
inline pair<T1, T2> make_pair(T1 x, T2 y)
{
	return (pair<T1, T2>(x, y));
}

3.4 map的构造

map的构造我们关注以下⼏个接⼝即可。

// empty (1) ⽆参默认构造
explicit map(const key_compare& comp = key_compare(),
	const allocator_type& alloc = allocator_type());

// range (2) 迭代器区间构造
template <class InputIterator>
map(InputIterator first, InputIterator last,
	const key_compare& comp = key_compare(),
	const allocator_type & = allocator_type());

// copy (3) 拷⻉构造
map(const map& x);

// initializer list (5) initializer 列表构造
map(initializer_list<value_type> il,
	const key_compare& comp = key_compare(),
	const allocator_type& alloc = allocator_type());

// 迭代器是⼀个双向迭代器
iterator->a bidirectional iterator to const value_type

// 正向迭代器
iterator begin();
iterator end();

// 反向迭代器
reverse_iterator rbegin();
reverse_iterator rend();

3.5 map的增删查

map的增删查关注以下⼏个接⼝即可:

Member types
key_type->The first template parameter(Key)
mapped_type->The second template parameter(T)
value_type->pair<const key_type, mapped_type>

// 单个数据插⼊,如果已经key存在则插⼊失败,key存在相等value不相等也会插⼊失败
pair<iterator, bool> insert(const value_type& val);

// 列表插⼊,已经在容器中存在的值不会插⼊
void insert(initializer_list<value_type> il);

// 迭代器区间插⼊,已经在容器中存在的值不会插⼊
template <class InputIterator>
void insert(InputIterator first, InputIterator last);

// 查找k,返回k所在的迭代器,没有找到返回end()
iterator find(const key_type& k);

// 查找k,返回k的个数
size_type count(const key_type& k) const;

// 删除⼀个迭代器位置的值
iterator erase(const_iterator position);

// 删除k,k存在返回0,存在返回1
size_type erase(const key_type& k);

// 删除⼀段迭代器区间的值
iterator erase(const_iterator first, const_iterator last);

// 返回⼤于等k位置的迭代器
iterator lower_bound(const key_type& k);

// 返回⼤于k位置的迭代器
const_iterator lower_bound(const key_type& k) const;

3.6 map的数据修改

operator[] 很重要,在各类算法题里都常常用到。

Member types
key_type->The first template parameter(Key)
mapped_type->The second template parameter(T)
value_type->pair<const key_type, mapped_type>
// 查找k,返回k所在的迭代器,没有找到返回end(),如果找到了通过iterator可以修改key对应的
mapped_type值
iterator find(const key_type& k);
// ⽂档中对insert返回值的说明
// The single element versions (1) return a pair, with its member pair::first
//set to an iterator pointing to either the newly inserted element or to the
//element with an equivalent key in the map.The pair::second element in the pair
//is set to true if a new element was inserted or false if an equivalent key
//already existed.
// insert插⼊⼀个pair<key, T>对象
// 1、如果key已经在map中,插⼊失败,则返回⼀个pair<iterator,bool>对象,返回pair对象
//first是key所在结点的迭代器,second是false
// 2、如果key不在在map中,插⼊成功,则返回⼀个pair<iterator,bool>对象,返回pair对象
//first是新插⼊key所在结点的迭代器,second是true
// 也就是说⽆论插⼊成功还是失败,返回pair<iterator,bool>对象的first都会指向key所在的迭
//代器
// 那么也就意味着insert插⼊失败时充当了查找的功能,正是因为这⼀点,insert可以⽤来实现operator[]
// 需要注意的是这⾥有两个pair,不要混淆了,⼀个是map底层红⿊树节点中存的pair<key, T>,另
//⼀个是insert返回值pair<iterator, bool>
pair<iterator, bool> insert(const value_type & val);
mapped_type& operator[] (const key_type& k);
// operator的内部实现
mapped_type& operator[] (const key_type& k)
{
	// 1、如果k不在map中,insert会插⼊k和mapped_type默认值,同时[]返回结点中存储
//	mapped_type值的引⽤,那么我们可以通过引⽤修改返映射值。所以[]具备了插⼊ + 修改功能
		// 2、如果k在map中,insert会插⼊失败,但是insert返回pair对象的first是指向key结点的
		//迭代器,返回值同时[]返回结点中存储mapped_type值的引⽤,所以[]具备了查找 + 修改的功能
		pair<iterator, bool> ret = insert({ k, mapped_type() });
	iterator it = ret.first;
	return it->second;
}

3.7 构造遍历及增删查使⽤样例

#include<iostream>
#include<map>
using namespace std;
int main()
{
	// initializer_list构造及迭代遍历
	map<string, string> dict = { {"left", "左边"}, {"right", "右边"},
	{"insert", "插⼊"},{ "string", "字符串" } };
	//map<string, string>::iterator it = dict.begin();
	auto it = dict.begin();
	while (it != dict.end())
	{
		//cout << (*it).first <<":"<<(*it).second << endl;
		// map的迭代基本都使⽤operator->,这⾥省略了⼀个->
		// 第⼀个->是迭代器运算符重载,返回pair*,第⼆个箭头是结构指针解引⽤取pair数据
			//cout << it.operator->()->first << ":" << it.operator->()-> second << endl;
		cout << it->first << ":" << it->second << endl;
		++it;
	}
	cout << endl;

	// insert插⼊pair对象的4种⽅式,对⽐之下,最后⼀种最⽅便
	pair<string, string> kv1("first", "第⼀个");
	dict.insert(kv1);
	dict.insert(pair<string, string>("second", "第⼆个"));
	dict.insert(make_pair("sort", "排序"));
	dict.insert({ "auto", "⾃动的" });

	// "left"已经存在,插⼊失败
	dict.insert({ "left", "左边,剩余" });

	// 范围for遍历
	for (const auto& e : dict)
	{
		cout << e.first << ":" << e.second << endl;
	}
	cout << endl;

	string str;
	while (cin >> str)
	{
		auto ret = dict.find(str);
		if (ret != dict.end())
		{
			cout << "->" << ret->second << endl;
		}
		else
		{
			cout << "⽆此单词,请重新输⼊" << endl;
		}
	}
	// erase等接⼝跟set完全类似,这⾥就不演⽰讲解了
	return 0;
}

3.8 map的迭代器和[]功能样例:

利用迭代器进行记录水果次数

#include<iostream>
#include<map>
#include<string>
using namespace std;
int main()
{
	// 利⽤find和iterator修改功能,统计⽔果出现的次数
	string arr[] = { "苹果", "西⽠", "苹果", "西⽠", "苹果", "苹果", "西⽠",
	"苹果", "⾹蕉", "苹果", "⾹蕉" };
	map<string, int> countMap;
	for (const auto& str : arr)
	{
		// 先查找⽔果在不在map中
		// 1、不在,说明⽔果第⼀次出现,则插⼊{⽔果, 1}
		// 2、在,则查找到的节点中⽔果对应的次数++
		auto ret = countMap.find(str);
		if (ret == countMap.end())
		{
			countMap.insert({ str, 1 });
		}
		else
		{
			ret->second++;
		}
	}
	for (const auto& e : countMap)
	{
		cout << e.first << ":" << e.second << endl;
	}
	cout << endl;
	return 0;
}

利用operator[] 来统计水果出现的次数

#include<iostream>
#include<map>
#include<string>
using namespace std;
int main()
{
	// 利⽤[]插⼊+修改功能,巧妙实现统计⽔果出现的次数
	string arr[] = { "苹果", "西⽠", "苹果", "西⽠", "苹果", "苹果", "西⽠",
	"苹果", "⾹蕉", "苹果", "⾹蕉" };
	map<string, int> countMap;
	for (const auto& str : arr)
	{
		// []先查找⽔果在不在map中
		// 1、不在,说明⽔果第⼀次出现,则插⼊{⽔果, 0},同时返回次数的引⽤,++⼀下就变成1次了
			// 2、在,则返回⽔果对应的次数++
			countMap[str]++;
	}
	for (const auto& e : countMap)
	{
		cout << e.first << ":" << e.second << endl;
	}
	cout << endl;
	return 0;
}
#include<iostream>
#include<map>
#include<string>
using namespace std;
int main()
{
	map<string, string> dict;
	dict.insert(make_pair("sort", "排序"));
	// key不存在->插⼊ {"insert", string()}
	dict["insert"];
	// 插⼊+修改
	dict["left"] = "左边";
	// 修改
	dict["left"] = "左边、剩余";
	// key存在->查找
	cout << dict["left"] << endl;
	return 0;
}

3.9 multimap和map的差异

最后,来看一个运用列题:

/*
// Definition for a Node.
class Node {
public:
    int val;
    Node* next;
    Node* random;
    
    Node(int _val) {
        val = _val;
        next = NULL;
        random = NULL;
    }
};
*/

class Solution {
public:
    Node* copyRandomList(Node* head) {
        if(head==nullptr) return nullptr;

        unordered_map<Node*,Node*> hash;
        Node* cur=head;
        while(cur)
        {
            hash[cur]=new Node(cur->val);
            cur=cur->next;
        }

        cur=head;
        while(cur)
        {
            hash[cur]->next=hash[cur->next];
            hash[cur]->random=hash[cur->random];
            cur=cur->next;
        }

        return hash[head];
    }
};

总结:第一次做的时候总想着自己创建一个头节点,然后把整个链表跟它一样先把所有next串起来再进行查找,可是看完题解后,别人全用hash存起来后在进行连接是真的方便。

总结一下吧~该节只是对set/map简单的结束和运用,对于这两个数据结构来刷算法是完全够用了,对我的作用很大,希望对你也是!!!

举报

相关推荐

0 条评论