map&&set

目录

1.关联式容器&&键值对

2.树形结构(关联式容器)

2.1set&&multiset

2.1.1简介

2.1.2使用

2.1.2.1构造和遍历方式

2.1.2.2迭代器

2.1.2.3容量

2.1.2.4修改

insert()

erase()

swap()

clear()

find()

count()

lower_bound(),upper_bound()

其他

2.2map

2.2.1简介

2.2.2使用

2.2.2.1构造&&遍历

2.2.2.2迭代器

2.2.2.3容量和访问

[]&&at

2.2.2.4修改

insert()

erase()

swap()&&clear()

find()

count()

lower_bound&&upper_bound

3.例题

3.1随机链表复制

3.2两个数组的交集

3.3同时找差集和交集

3.4前K个高频单词


map和set的底层是平衡树和红黑树,本文主要是关于map和set的使用,具体的底层模拟,可以看我关于AVL树和红黑树的文章

1.关联式容器&&键值对

简单描述下。如vector,list,deque,queue等容器,都是序列式容器,因为底层是线性序列的数据结构,存的是数据本身。

而关联式容器也存数据,但存的是<key,value>这样的键值对,在数据检索时比序列式容器效率更高。

键值对,就是同时存储两种数据的结构,这两种数据是一一对应的,形象的说,就像英汉词典一样,中文对应某个英文。

pair<first,second>,就是库里的键值对结构

template <class T1, class T2> struct pair;

pair 默认对first升序,当first相同时对second升序;

make_pair也差不多,但是写法比较简洁。

template <class T1, class T2>
  pair<V1,V2> make_pair (T1&& x, T2&& y);  // see below for definition of V1 and V2

make_pair可以直接构造一个pair,所以简洁一些。

make_pair<a,b>

这是c++98的

但是也可以依赖c++11的隐式类型转换

map<int,int>a;

a.insert({3,3});

2.树形结构(关联式容器)

 stl中实现了两类关联式容器,树形结构和哈希结构。树形结构分:map,set,multimap,multiset

这四个共同点是,底层结果都是平衡搜索树(红黑树),容器中元素是有序序列。

2.1set&&multiset

set和mulitiset的区别就是后者支持重复数据(在搜索树中会放左边或右边,不影响)

2.1.1简介

1.内部按一定次序存储元素容器。

2.元素中value也表示它,在set中value就是key,类型是模板实例化决定,每个value唯一

元素不能被修改(元素都是const),但可以插入或删除元素

3.set中元素按期内部比较对象所制定的特定严格弱排序准则进行(可以自己改成严格强,自定义类型要做好重载运算符的工作)

4.set容器通过key访问单个元素速度比unordered_set慢,但它们允许根据顺序对子集直接进行迭代

5.底层是平衡二叉树(红黑树)

6.map/multimap中是真的存了一个键值对<key,value>,而set中只存value,底层结构是<value,value>这样的键值对

7.set插入元素时,只需插入value,不需构造键值对

8.set中的元素不可以重复(可以使用set去重)

9.set迭代器遍历元素,可以得到有序序列(因为底层还是搜索树,而搜索树中序遍历的结果就是有序的,所以迭代器遍历也是走中序遍历)

10.set查找某个元素,时间复杂度:log2 n

2.1.2使用

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;

第一个是set容器内部元素的类型。
第二个是内部容器的比较规则。可参考我队列的文章中优先队列的描述
第三个是内存池。
2.1.2.1构造和遍历方式
explicit set (const key_compare& comp = key_compare(),
              const allocator_type& alloc = allocator_type());
上面看起来很抽象,但实际使用就是
set<类型>对象名-----set<int>a;
第一个参数是比较规则(仿函数)

template <class InputIterator>
  set (InputIterator first, InputIterator last,
       const key_compare& comp = key_compare(),

这是用一段迭代区间构造的set
set<int>b(a.begin(), a.end());

set (const set& x);
拷贝构造

set<int>c(b);


遍历方式:
	while (tmp != a.end())
	{
		cout << *tmp << " ";
		tmp++;
	}
	cout << endl;
	for (auto& s : a)
	{
		cout << s << " ";
	}
2.1.2.2迭代器

提供了begin(),end(),rbegin(),rend(),cbegin(),cend(),crbegin(),crend()

2.1.2.3容量

empty()是用来看容器是否为空,为空则返回true,否则false

size是容器中元素个数

max_size()是set最多能存多大

2.1.2.4修改
insert()
pair<iterator,bool> insert (const value_type& val);

返回的是一对pair键值对,
	set<int>a;
	a.insert(1);
实际上,set中底层还是存键值对,这个键值对是<value,value>
而如果插入执行后,返回的也是一对键值对,成功是<该元素在set中的位置(迭代器),true>
失败,说明set中已经有相同值,返回<已有的相同元素在set中的位置(迭代器),false>

iterator insert (iterator position, const value_type& val);
指定位置插入数据(但还是会被排序,没多大用

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

是插入一对迭代区间内的值。
erase()
 void erase (iterator position);
删除指定位置的数据(迭代器)
但这个指定位置必须是有效位置
size_type erase (const value_type& val);
删除set中跟val相同的元素,返回删除的元素个数
在,就删,不在,就没反应
这个返回值主要是为了multiset准备

void erase (iterator first, iterator last);
删除set中一段区间的值

swap()
void swap ( 
set<Key,Compare,Allocator>& 
st );

交换set中的值

	set<int>a;
	a.insert(1);

	set<int>b;
	b.insert(2);

	b.swap(a);
clear()

元素清空

find()
iterator find (const value_type& val) const;

在set中找值为value的元素,找到之后,返回该元素在set中的位置(迭代器)
没找到,直接返回set的end()

a.find(3);


对于Mulitiset是返回中序遍历中的第一个
count()
size_type count ( const 
key_type& x ) const

返回值set中为x的的元素有多少个

主要是为了mulitiset准备
lower_bound(),upper_bound()
iterator lower_bound (const value_type& val) const;
返回大于等于val的一个迭代器位置

iterator upper_bound (const value_type& val) const;
返回大于val的一个迭代器位置

之所以不是>=,是为了配合左闭右开的接口使用


可以用来配合着用来找到一段区间。不管是删除还是遍历等都可以。
 

其他

key_comp,value_comp,返回比较的仿函数

2.2map

跟multimap的区别,后者允许相同的key存在。value不影响

mulitimap不存在[]重载,因为key有相同

2.2.1简介

1.按特定次序(按key比较)存储 由key和value组合而成的元素

2.map中key用排序和唯一表示元素,value存于key关联的内容。key和value类型可以不同,

key与value放在pair里。整个pair别名为value_type;

typedef pair<const key,T>value_type;

3.map中通过键值访问单个元素通常比unordered_map慢,但map允许根据顺序对元素进行迭代遍历(通过迭代可以得到有序序列,因为也是搜索树,所以中序遍历还是有序)

4.map支持下标访问符,[]中放key,就可以找到对应value

5.底层:平衡搜索树(红黑树)

2.2.2使用

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;
第一个是key的类型,T是value的类型
compare是比较的类型即仿函数
默认是小于
自定义类型需要自己传递比较规则,或者重载自定义类型的运算符
2.2.2.1构造&&遍历

explicit map (const key_compare& comp = key_compare(),
              const allocator_type& alloc = allocator_type());

跟set一样,空的map
	
template <class InputIterator>
  map (InputIterator first, InputIterator last,
       const key_compare& comp = key_compare(),
       const allocator_type& alloc = allocator_type());
跟set一样,一段区间构造

map (const map& x);
拷贝构造

遍历没什么,就是常见的迭代器

	map<int,int>a;
	a.insert(pair<int,int>(3,3));
	pair<int, int>c(5, 4);
	a.insert(c);
	a.insert({ 1,2 });
	for (auto& s : a)
	{
		cout << s.first << " ";
		cout << s.second << endl;
	}
2.2.2.2迭代器

没什么,就是begin(),end(),rbegin,rend,cbegin,cend,crbegin,crend

2.2.2.3容量和访问

empty,map中元素数量是否为空,是返回ture,不是false

size()元素个数

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

返回key对应的value的引用
如果key不存在,则构造一个<key,默认value>的键值对插入进去
但at不同,at是直接抛异常,其他没区别
具体插入方式
(*((this->insert(make_pair(k,mapped_type()))).first)).second


结合下面的insert例子

(this->insert(make_pair(k,mapped_type()))).first
这句话,其实就是插入一个新的元素,k是提供的,value直接调用默认value的默认构造函数
依靠insert的机制,如果元素(key是k)是已经存在的,则insert(make_pair(k,mapped_type()))返回的是
一个键值对,其中first是map中已经存在的元素(key是k的)的迭代器
如果不存在,则执行插入操作,并且返回一个键值对,其中first是相应新插入元素的迭代器

不管这两种情况,反正都是会返回一个pair,这个pair的first都是这个元素(key是k)的迭代器

(this->insert(make_pair(k,mapped_type())))就是这个pair

((this->insert(make_pair(k,mapped_type()))).first)就是这个迭代器

(*((this->insert(make_pair(k,mapped_type()))).first)).second
*解引用后,因为map的元素也是个键值对,所以解引用后,再访问这个元素的second


[]可以支持插入,查找,查找+修改,插入+修改

2.2.2.4修改
insert()
pair<iterator,bool> insert (const value_type& val);

使用上和set区别不大。
注意,插入是一个键值对
	map<int,int>a;
	a.insert(pair<int,int>(3,3));
	pair<int, int>c(5, 4);
	a.insert(c);
	a.insert({ 1,2 });


注意,第一个参数不能重复。就算重复了,也不会插入也不会更新value

iterator insert (iterator position, const value_type& val);

指定插入

template <class InputIterator>
  void insert (InputIterator first, InputIterator last);
一段区间插入


	map<int,int>a;
	a.insert(pair<int,int>(3,3));
	pair<int, int>c(5, 4);
	a.insert(c);
	pair<map<int,int>::iterator,bool>re= a.insert({ 3,2 });
	if (re.second == false)
	{
		cout << "已经存在了";
	}
	re.first->second++;
	//如果已经存在,则是3 4
	//如果是新插入,则是3 3
erase()
 void erase (iterator position);

删除指定位置(迭代器)上的数据

size_type erase ( const 
key_type& x )

删除key键值为x的值
返回删除元素个数

void erase ( iterator first, 
iterator last )
删除一段区间值
swap()&&clear()

swap是交换

void swap (map& x);

clear是清空元素

find()
iterator find ( const key_type& x 
)
找到key为x的元素的迭代器。找不到返回end()

const_iterator find ( const 
key_type& x ) const

找到key为x的元素的const迭代器。找不到返回cend()
count()
size_type count ( const 
key_type& x ) const

返回key为x在map中的元素个数,因为map中key唯一
所以要么0要么1
lower_bound&&upper_bound
iterator lower_bound (const key_type& k);
const_iterator lower_bound (const key_type& k) const;

返回大于等于key的迭代器

iterator upper_bound (const key_type& k);
const_iterator upper_bound (const key_type& k) const;

返回大于key的迭代器


3.例题

3.1随机链表复制

 138. 随机链表的复制 - 力扣(LeetCode)

这题可以先看我c语言版的链表中的写法,再看我这里的新的写法。

class Solution {
public:
    Node* copyRandomList(Node* head) {
        map<Node*,Node*>hash;
        Node*cur=head,*copyhead=nullptr;
        Node*copytail=nullptr;
        while(cur)
        {
            if(copytail==nullptr)
            {
                copyhead=copytail=new Node(cur->val);
            }
            else
            {
                copytail->next=new Node(cur->val);
                copytail=copytail->next;
            }
            hash[cur]=copytail;
            cur=cur->next;
        }
        cur=head;
        Node*x=copyhead;
        while(cur)
        {
            if(cur->random==nullptr)
            {
                x->random=nullptr;
            }
            else{
                x->random=hash[cur->random];
            }
            cur=cur->next;
            x=x->next;
        }
        return copyhead;
    }
};

3.2两个数组的交集

349. 两个数组的交集 - 力扣(LeetCode)

class Solution {
public:
    
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        set<int> s(nums1.begin(),nums1.end());
        //set<int>s2(nums2.begin(),nums2.end());
        //可以再弄个set,然后两个set比对,但是这题数据量小,可以直接用下面的数组标记
        int mp[1001]={0};
        vector<int>ret;
        for(auto a:nums2)
        {
            if(s.count(a)==1&&mp[a]==0)
            {
                ret.push_back(a);
                mp[a]=1;
            }
        }
        return ret;
    }
};
//事实上还可以不用set,只依靠数组标记。
class Solution {
public:
    
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        int m1[1001]={0};
        int m2[1001]={0};
        vector<int>ret;
        for(auto a:nums1)
        {
            if(m1[a]==0)
            m1[a]=1;
        }
        for(auto a:nums2)
        {
            if(m2[a]==0)
            m2[a]=1;
        }
        for(int i=0,j=0;i<=1000;i++,j++)
        {
            if(m1[i]==1&&m2[j]==1)ret.push_back(i);
        }
        return ret;
    }
};

3.3同时找差集和交集

这边没找到题。只说思想

1.两个数组放入set(依靠set自动排序和去重的特性)
2.两个指针指向两个set的第一个元素

3.小的就是差集,相同就是交集(因为排序和去重,小的必然是单独的)

4.小的指针++,相同一起++

利用这个思想,我们可以单独拿来找交集。上面一题就可以这样写

class Solution {
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        set<int> s(nums1.begin(),nums1.end());
        set<int>s2(nums2.begin(),nums2.end());
        vector<int>ret;
        auto l1=s.begin(),l2=s2.begin();
        while(l1!=s.end()&&l2!=s2.end())
        {
            if(*l1<*l2)
            {
                l1++;
            }
            else if(*l1>*l2)
            {
                l2++;
            }
            else
            {
                ret.push_back(*l1);
                l1++,l2++;
            }
        }
        return ret;
    }
};

3.4前K个高频单词

692. 前K个高频单词 - 力扣(LeetCode)

这个版本思路是利用map记录单词频率。

再理由sort排序。

但是我们要注意,sort是不稳定的排序,这题说单词频率相同按字典序排序。

但事实上,map默认就会对first升序,也就是字典序排序了。

但是,当我们让其再用sort排序。这个字典序也被破坏了,相对位置改变了。

所以一个方案就是我们对仿函数里继续改造,添加字典序排序,比如下面的版本v1

,如果不想改,也可以用stable_sort,稳定排序,采用的是归并排序,归并排序是稳定的排序,也就是v2。v3版本是用优先队列存储,采用v1的方式解决排序问题(也可以v2)

v4不写了,就说思路,主要是排序的问题,字典序可以让第一次排完的数据继续放入set,因为默认升序排序,然后再放入最终答案数组里
v1

class Solution {
public:
    struct kp{
        public:
        bool operator()(const pair<string,int>&a,const pair<string,int>&b)const
        {
            if(a.second==b.second)
            {
                return a.first<b.first;
            }
            else
            {
                return a.second>b.second;
            }
        }
    };
    vector<string> topKFrequent(vector<string>& words, int k) {
        map<string,int>h1;
        for(auto a:words)
        {
            h1[a]++;
        }
        vector<pair<string,int>>v(h1.begin(),h1.end());
        sort(v.begin(),v.end(),kp());
        vector<string>ret;
        auto it=v.begin();
        while(k--)
        {
            ret.push_back(it->first);
            it++;
        }
        return ret;
    }
};

v2

class Solution {
public:
    struct kp{
        public:
        bool operator()(const pair<string,int>&a,const pair<string,int>&b)const
        {
            return a.second>b.second;
        }
    };
    vector<string> topKFrequent(vector<string>& words, int k) {
        map<string,int>h1;
        for(auto a:words)
        {
            h1[a]++;
        }
        vector<pair<string,int>>v(h1.begin(),h1.end());
        stable_sort(v.begin(),v.end(),kp());
        vector<string>ret;
        auto it=v.begin();
        while(k--)
        {
            ret.push_back(it->first);
            it++;
        }
        return ret;
    }
};

v3

这个版本就是用优先队列存储

class Solution {
public:
    struct kp {
    public:
        bool operator()(const pair<string, int>& a, const pair<string, int>& b)const
        {
            if(a.second==b.second)
            {
                return a.first>b.first;
            }
            else
            {
                return a.second < b.second;
            }
            
        }
    };
    vector<string> topKFrequent(vector<string>& words, int k) {
        map<string, int>h1;
        for (auto a : words)
        {
            h1[a]++;
        }
        priority_queue<pair<string, int>, vector<pair<string, int>>,kp >v(h1.begin(), h1.end());
        vector<string>ret;
        auto it = v.top();
        while(k--)
        {
            ret.push_back(it.first);
            v.pop();
            it=v.top();
        }
        return ret;
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值