文章目录
1.关联式容器
vector、list、deque等容器被称之为序列式容器,因为它们的底层为线性序列的数据结构,里面存储的是元素的本身
关联式容器也是用来存储数据的,与序列式容器不同,里面存储的是<key,value>结构的键值对,在数据检索时比序列式容器的效率更高
2.键值对
用来表示具有一一对应关系的一种结构,该结构中一般只包含两个成员变量key和value,key代表键值,value表示与key对应的信息
比如将key设置为中文,value设置为英文,就可以通过中文去查找对应的单词
3.树形结构的关联式容器
根据应用场景的不同,STL总共实现了两种不同结构的管理式容器:树形结构与哈希结构。树形结构的关联式容器主要有四种:map、set、
multimap、multiset(multi表示多样的,不去重)。 这四种容器的共同特点是使用平衡搜索树(红黑树)作为底层结构,容器中的元素是一个有序序列
4.set的使用介绍
- set是按照一定次序存储元素的容器
- 在set中,元素的value也标识它(value就是key,类型为T),并且每个value必须是唯一的。set中的元素 不能在容器中修改(元素总是const),但是可以从容器中插入或删除它们。
- 在内部,set中的元素总是按照其内部比较对象(类型比较)所指示的特定严格弱排序准则进行排序。
- set容器通过key访问单个元素的速度通常比unordered_set容器慢,但它们允许根据顺序对子集进行直接迭代。
- set在底层是用二叉搜索树(红黑树)实现的
- 常用于排序和去重
4.1set的模板参数
4.2使用介绍
4.2.1排序和去重
4.2.2查找
5.map的使用介绍
1.map是关联式容器,按照特定的次序(按照key值比较)存储由键值key和值value组合而成的元素
2.在map中,键值key通常用于排序和唯一的标识元素,值value中存储与键值key关联的内容,键值key和值value的类型可能不同,并且在map的内部,key和value可以通过成员类型value_type绑定在一起,取名为pair(结构体)
3.在内部,map中的元素总是按照键值key进行比较排序
4.map中通过键值访问单个元素的速度通常比unordered_map慢,但map允许根据顺序对元素进行直接迭代(搜索树的中序遍历)
5.map支持下标访问,即通过在[]中放入key,就可以找到key对应的value
6.map通常被实现为二叉搜索树(平衡二叉搜索树(红黑树))
7.常用于排序+去重、判断一个元素在不在、kv查找模型、统计次数
5.1模板参数
5.2元素插入
C++11中的插入是emplace、emplace_hint(引入了右值引用)
5.3元素的交换
在C++98之中,调用如下图所示,类中的swap只会交换两个树的根节点。如果调用的是库函数里面的swap则需要发生3次深拷贝消耗是巨大的
在C++11之中,由于右值引用的设计,两者的效率是一样的,但是在不明确运行环境的情况之下,还是建议使用中的swap
5.4排序、去重、统计次数
5.5查找
5.6删除
5.7[]原理
5.8map实际应用
1.给你一个整数数组 nums
和一个整数 k
,请你返回其中出现频率前 k
高的元素。你可以按 任意顺序 返回答案。
//利用map统计数组之中不同元素的个数
//然后构建一个小根堆 -> 此时写一个仿函数,利用value值构建小根堆,最后得到的就是频率前k高的元素
class Solution {
public:
//建小堆仿函数->greater
struct greater
{
bool operator()(map<int,int>::iterator &x,map<int,int>::iterator &y)//根据value值进行建堆
{
return x->second>y->second;
}
};
vector<int> topKFrequent(vector<int>& nums, int k) {
//建立map,统计次数,利用堆获取前K个最大的val
map<int,int>count_map;//统计数组
for(auto& e:nums)
{
count_map[e]++;//统计每个元素出现的次数
}
vector<int>ret;
typedef map<int,int>::iterator count_it;
auto it=count_map.begin();
priority_queue<count_it,vector<count_it>,greater> heap;
while(it!=count_map.end())//根据value建K个元素的小堆
{
if(heap.size()<k)//小于K个的时候
heap.push(it);
else
{
auto val=heap.top();
if(it->second>val->second)//大于入堆
{
heap.pop();
heap.push(it);
}
}
it++;
}
while(!heap.empty())
{
ret.push_back(heap.top()->first);//K值进入返回数组
heap.pop();
}
return ret;
}
};
2.请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null。题目链接
class Solution {
public:
Node* copyRandomList(Node* head) {
if(!head)
return nullptr;
map<Node*,Node*> list;
Node *newhead=head;
//构造map
while(newhead)//构造新旧链表的map
{
Node *cur=new Node(newhead->val);
list.insert(make_pair(newhead,cur));//将节点插入map之中
newhead=newhead->next;
}
//从map之中将拷贝的链表取出来
newhead=head;
Node *ret_node=new Node(0);
Node *ret=ret_node;
while(newhead)
{
ret_node->next=list[newhead];//将新链表链接起来
ret_node=ret_node->next;
//填入对应的random
if(newhead->random==NULL)
ret_node->random=NULL;
else
ret_node->random=list[newhead->random];
//更新位置
newhead=newhead->next;
}
return ret->next;
}
};
3.统计最欢迎的水果
解法1:
利用map将所有的水果出现的次数统计起来,然后将map的迭代器放入vector之中,最后构造仿函数 ->second进行比较,利用sort进行排序即可;
class Fruit
{
private:
vector<string> _str;
int _k;
public:
Fruit(vector<string> &str,int k)
:_str(str)
,_k(k)
{}
void TopK()
{
map<string,int> count_map;
typedef map<string,int>::iterator count_it;//重定义迭代器
for(auto&e:_str)//统计水果的次数
{
count_map[e]++;
}
vector<count_it> arr;
count_it it=count_map.begin();
while(it!=count_map.end())//将map中迭代器放入数组之中
{
arr.push_back(it);
it++;
}
typedef struct Com//仿函数
{
bool operator()(count_it &x,count_it y)
{
return x->second > y->second;
}
}compare;
sort(arr.begin(),arr.end(),compare());//排升序
while(_k--)//输出内容
{
cout<<arr[_k]->first<<" "<<arr[_k]->second<<endl;
}
}
};
解法2:
利用大小堆来进行排序
class Fruit
{
private:
vector<string> _str;
int _k;
public:
Fruit(vector<string> &str,int k)
:_str(str)
,_k(k)
{}
void TopK()
{
map<string,int> count_map;
typedef map<string,int>::iterator count_it;
for(auto&e:_str)//统计水果的次数
{
count_map[e]++;
}
typedef struct Com
{
bool operator()(count_it &x,count_it &y)
{
return x->second > y->second;
}
}compare;
priority_queue<count_it,vector<count_it>,compare> heap;//建立小堆
count_it it=count_map.begin();
while(it!=count_map.end())
{
if(heap.size() < _k)
heap.push(it);
else
{
if(it->second > heap.top()->second)//大于则入堆
{
heap.pop();
heap.push(it);
}
}
it++;
}
while(!heap.empty())//打印
{
cout<<heap.top()->first<<" "<<heap.top()->second<<endl;
heap.pop();
}
}
};
int main()
{
vector<string>arr={"苹果","苹果","西瓜","西瓜","香蕉","西瓜"};
Fruit(arr,2).TopK();
return 0;
}
6.multiset、multimap
multiset、multimap的用法和接口与set和map几乎没有区别、他们的共同点是排序、不同点是multiset和multimap不会进行去重
multimap中的元素默认将key按照小来比较
multimap没有[] -> 因为不知道是取哪一个
find的时候,找的是中序的第一个
插入的元素key相同时,插入的顺序就是输出的顺序
6.1实际应用
最受欢迎的水果:
利用map统计数据之后,再用multimap进行排序(按second),不能使用map排序是因为去重问题,利用second时,相同个数的水果会被去重
class Fruit
{
private:
vector<string> _str;
int _k;
public:
Fruit(vector<string> &str, int k)
:_str(str)
, _k(k)
{}
void TopK()
{
map<string, int> count_map;
typedef map<string, int>::iterator count_it;
for (auto&e : _str)//统计水果的次数
{
count_map[e]++;
}
multimap<int, string, greater<int>> map_sort;//排降序
for (auto e : count_map)//将first和second颠倒位置(用int进行排序)
{
map_sort.insert(make_pair(e.second,e.first));
}
auto it = map_sort.begin();
while (_k--)
{
cout << it->second << " " << it->first << endl;
it++;
}
}
};
int main()
{
vector<string>arr = { "苹果", "苹果", "西瓜", "西瓜", "香蕉", "西瓜" };
Fruit(arr, 2).TopK();
system("pause");
return 0;
}
前K个高频单词:
给一非空的单词列表,返回前 k 个出现次数最多的单词。返回的答案应该按单词出现频率由高到低排序。如果不同的单词有相同出现频率,按字母顺序排序。
方法1:
先用map统计出现的次数,然后建立一个小堆,就转变成了topK问题。需要注意的是优先级队列的仿函数的写法,当second的值相等的时候,就用first的值去比较(单词的ASCII)。将ASCII较大的单词放在堆顶(小根堆 -> 最后结果需要进行逆转)
typedef map<string,int>::iterator count_it;
typedef struct com
{
bool operator()(count_it &x,count_it &y)
{
if(x->second!=y->second)
return x->second > y->second;
else//出现频率相同,按照字母顺序排->即ASCII
return x->first < y->first;
}
}Greater;
class Solution {
public:
vector<string> topKFrequent(vector<string>& words, int k) {
map<string,int> count_map;
//统计单词出现次数,并且将单词按照ASCII排了升序
for(auto&e:words)
{
count_map[e]++;
}
priority_queue<count_it,vector<count_it>,Greater> heap;
count_it it=count_map.begin();
while(it!=count_map.end())
{
if(heap.size()<k)
heap.push(it);
else
{
//由于前面map排序了 -> 因此频率相等的单词,较小的ASCII已经入堆了 ->就不再判断
if(it->second>heap.top()->second)
{
heap.pop();
heap.push(it);
}
}
it++;
}
vector<string> ret;
while(!heap.empty())
{
ret.push_back(heap.top()->first);
heap.pop();
}
reverse(ret.begin(),ret.end());
return ret;
}
};
方法2:
先用map统计单词出现的频率,并且将单词按照ASCII进行了排序
然后用multimap对单词出现的频率,排降序
因为map之中已经按照单词的ASCII进行了排序,因此当频率相同的单词出现时,谁先进入multimap之中,遍历时谁就先出来,因此ASCII小的先进入,ASCII小的先出来,就保证了单词的顺序性
class Solution {
public:
vector<string> topKFrequent(vector<string>& words, int k) {
map<string,int> count_map;
//统计单词出现的次数
for(auto&e:words)
{
count_map[e]++;
}
multimap<int,string,greater<int>>mt_map;//按照逆序排
for(auto&e:count_map)
{
mt_map.insert(make_pair(e.second,e.first));
}
vector<string> ret;
auto it=mt_map.begin();
while(k--)
{
ret.push_back(it->second);
it++;
}
return ret;
}
};