上篇文章:C++:set类

目录

1.map类概念

pair类型介绍

2.map系列使用

2.1map的构造

2.2map的增删查

2.3map的数据修改

了解map::operator[]

3.multimap和map差异

4.力扣练习map的使用

4.1随机链表的复制

4.2前k个高频单词

解决思路1:

解决思路2:


1.map类概念

map的声明如下,Key就是map底层关键字的类型,T是map底层value的类型,set默认要求Key支持小于比较,如果不支持或者需要的话可以自行实现仿函数传给第二个模版参数,map底层存储数据的内存是从空间配置器申请的。一般情况下,我们都不需要传后两个模版参数。map底层是用红黑树实现,增删查改效率是O(logN),迭代器遍历是走的中序,所以是按key有序顺序遍历的。

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;

pair类型介绍

pair是 C++ 标准库提供的通用模板类,专门用来封装 “两个关联的值”;而 map 的每一个元素本质上就是一个pair<const Key, Value>对象。

map底层的红黑树节点中的数据,使用pair<Key,T>存储键值对数据,每个节点存储的都是一个pair对象,但有个关键特性:map中 pair 的 first(键)是 const 类型,无法直接修改。

默认构造:

default (1)	
constexpr pair();

copy / move (2)	
template<class U, class V> pair (const pair<U,V>& pr);
template<class U, class V> pair (pair<U,V>&& pr);
pair (const pair& pr) = default;
pair (pair&& pr) = default;

initialization (3)	
pair (const first_type& a, const second_type& b);
template<class U, class V> pair (U&& a, V&& b);

piecewise (4)	
template <class... Args1, class... Args2>
  pair (piecewise_construct_t pwc, tuple<Args1...> first_args,
                                   tuple<Args2...> second_args);

核心结构:

只有两个公有成员变量:

template <typename T1, typename T2>
struct pair {
    T1 first;   // 第一个值(map中对应key)
    T2 second;  // 第二个值(map中对应value)
    
    // 构造函数等(标准库已实现)
};

示例:

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

2.map系列使用

2.1map的构造

map的支持正向和反向迭代遍历,遍历默认按key的升序顺序,因为底层是二叉搜索树,迭代器遍历走的中序;支持迭代器就意味着支持范围for,map支持修改value数据,不支持修改key数据,修改关键字数据,破坏了底层搜索树的结构。

关注以下几个接口:

// 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();

2.2map的增删查

map增接口,插入的pair键值对数据,跟set所有不同,但是查和删的接口只用关键字key跟set是完全类似的,不过find返回iterator,不仅仅可以确认key在不在,还找到key映射的value,同时通过迭代还可以修改value

关注以下几个接口:

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;

示例:

map<string, string>dict;

// c++98
pair<string, string>kv1("sort", "排序");
dict.insert(kv1);
dict.insert(pair<string, string>("left", "左边"));
dict.insert(make_pair("left", "左边"));

// C++11
dict.insert({ "right", "右边" });
//dict.insert({kv1, pair<string, string>("left", "左边")});
dict.insert({ {"string", "字符串"}, {"map", "地图,映射"} });

// key相同就不会再插入,value不相同也不会插入
dict.insert({ "left", "左边xxx" });

map<string, string>::iterator it = dict.begin();
while (it != dict.end())
{
	// cout << (*it).first <<":"<< (*it).second << endl; 
	cout << it->first << ":" << it->second << endl;
	// cout << it.operator->()->first << ":" << it.operator->()->second << endl;
	++it;
}
cout << endl;

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

// 结构化绑定 C++17
auto [x, y] = kv1;
//for (auto [k, v] : dict)
//for (auto& [k, v] : dict)
for (const auto& [k, v] : dict)
{
	cout << k << ":" << v << endl;
}
cout << endl;

auto pos = dict.find("left");
if (pos != dict.end())
{
	dict.erase(pos);
}

for (const auto& [k, v] : dict)
{
	cout << k << ":" << v << endl;
}
cout << endl;

结果:

2.3map的数据修改

前面提到map支持修改mapped_type 数据,不支持修改key数据,修改关键字数据,破坏了底层搜索树的结构。

map第一个支持修改的方式时通过迭代器,迭代器遍历时或者find返回key所在的iterator修改,map还有一个非常重要的修改接口operator[],但是operator[]不仅仅支持修改,还支持插入数据和查找数据,所以他是一个多功能复合接口

需要注意从内部实现角度,map这里把我们传统说的value值,给的是T类型,typedef为mapped_type。而value_type是红黑树结点中存储的pair键值对值。日常使用我们还是习惯将这里的T映射值叫做value。

了解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;
}

代码示例:

string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" };
map<string, int> countmap;

for (auto& e : arr)
{
	auto it = countmap.find(e);
	if (it != countmap.end())
	{
		it->second++;
	}
	else
	{
		countmap.insert({ e,1 });
	}
}

for (auto& e : arr)
{
	countmap[e]++;
}

for (auto& [k, v] : countmap)
{
	cout << k << ":" << v << endl;
}
cout << endl;

运行结果:

修改:

map<string, string> dict;
// 插入
dict["sort"];
// 插入+修改
dict["left"] = "左边";
// 修改
dict["sort"] = "排序";
// 查找
cout << dict["sort"] << endl;

// 纯粹的查找+修改
// at
dict.at("left") = "xxxxx";
// key不存在,会抛异常
// dict.at("insert") = "xxxxx";

3.multimap和map差异

multimap和map的使用基本完全类似,主要区别点在于multimap支持关键值key冗余,那么insert/find/count/erase都围绕着支持关键值key冗余有所差异,这里跟set和multiset完全一样,比如find时,有多个key,返回中序第一个。其次就是multimap不支持[],因为支持key冗余,[]就只能支持插入了,不能支持修改。

代码示例:

multimap<string, string> dict;
dict.insert({ "right", "右边" });
dict.insert({ "left", "左边" });
dict.insert({ "right", "右边xx" });
dict.insert({ "right", "右边" });


for (const auto& [k, v] : dict)
{
	cout << k << ":" << v << endl;
}
cout << endl;

4.力扣练习map的使用

4.1随机链表的复制

https://leetcode.cn/problems/copy-list-with-random-pointer/

数据结构初阶阶段,为了控制随机指针,我们将拷贝结点链接在原节点的后面解决,后面拷贝节点还得解下来链接,非常麻烦。这里我们直接让{原结点,拷贝结点}建立映射关系放到map中,控制随机指针会非常简单方便,这里体现了map在解决一些问题时的价值,完全是降维打击。

class Solution {
public:
    Node* copyRandomList(Node* head) {
      map<Node*, Node*> nodeMap;
      // 深拷贝链表
      Node* copyhead = nullptr, *copytail = nullptr;
      Node* cur = head;
      while(cur)
      {
        Node* copy = new Node(cur->val);
        // 尾插
        if(copytail == nullptr)
        {
            copyhead = copytail = copy;
        }
        else
        {
            copytail->next = copy;
            copytail = copy;
        }

        nodeMap.insert({cur, copy});
        cur = cur->next;
      }

      cur = head;
      Node* copy = copyhead;
      while(cur)
      {
        if(cur->random == nullptr)
        {
            copy->random = nullptr;
        }
        else
        {
            copy->random = nodeMap[cur->random];
        }
        cur = cur->next;
        copy = copy->next;
      }
      return copyhead;
    }
};

4.2前k个高频单词

https://leetcode.cn/problems/top-k-frequent-words/

本题自我们利用map统计出次数以后,返回的答案应该按单词出现频率由高到低排序,有一个特殊要求,如果不同的单词有相同出现频率,按字典顺序排序。

解决思路1:

用排序找前k个单词,因为map中已经对key单词排序过,也就意味着遍历map时,次数相同的单词,字典序小的在前面,字典序大的在后面。那么我们将数据放到vector中用一个稳定的排序就可以实现上面特殊要求,但是sort底层是快排,是不稳定的,所以我们要用stable_sort,他是稳定的。

class Solution {
public:
    struct kv_pair
    {
        bool operator()(const pair<string, int>& kv1, const pair<string, int>& kv2)
        {
            return kv1.second > kv2.second;
        }
    };

    vector<string> topKFrequent(vector<string>& words, int k) 
    {
        map<string, int> countMap;
        for(auto& str : words)
        {
            countMap[str]++;
        }
        vector<pair<string, int>> v(countMap.begin(), countMap.end());
        // sort(v.begin(), v.end(), kv_pair());
        stable_sort(v.begin(), v.end(), kv_pair());
        for(auto& [k,v] : v)
        {
            cout << k << ":" << v << endl;
        }
        cout << endl;

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

解决思路2:

将map统计出的次数的数据放到vector中排序,或者放到priority_queue中来选出前k个。利用仿函数强行控制次数相等的,字典序小的在前面。

1.

class Solution {
public:
    struct kv_pair
    {
        bool operator()(const pair<string, int>& kv1, const pair<string, int>& kv2)
        {
            return kv1.second > kv2.second || (kv1.second == kv2.second && kv1.first < kv2.first);
        }
    };

    vector<string> topKFrequent(vector<string>& words, int k) 
    {
        map<string, int> countMap;
        for(auto& str : words)
        {
            countMap[str]++;
        }
        vector<pair<string, int>> v(countMap.begin(), countMap.end());
        sort(v.begin(), v.end(), kv_pair());
       // stable_sort(v.begin(), v.end(), kv_pair());
        for(auto& [k,v] : v)
        {
            cout << k << ":" << v << endl;
        }
        cout << endl;

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

2.

class Solution {
public:
    struct kv_pair
    {
        bool operator()(const pair<string, int>& kv1, const pair<string, int>& kv2)
        {
            return kv1.second < kv2.second || (kv1.second == kv2.second && kv1.first > kv2.first);
        }
    };

    vector<string> topKFrequent(vector<string>& words, int k) 
    {
        map<string, int> countMap;
        for(auto& str : words)
        {
            countMap[str]++;
        }
        
        priority_queue<pair<string, int>, vector<pair<string, int>>, kv_pair > pq(countMap.begin(), countMap.end());

        vector<string> ret;
        for(size_t i = 0; i < k; ++i)
        {
            ret.push_back(pq.top().first);
            pq.pop();
        }
        return ret;
    }
};

本章完。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐