C++STL包含了序列式容器和关联式容器:
序列式容器里面存储的是元素本身,其底层为线性序列的数据结构。比如:vector,list,deque,forward_list(C++11)等。
关联式容器里面存储的是
序列式容器,数据是挨着挨着放的。在哪里插入都可以。单纯目标就是内存中存储数据。
关联式容器也是用来存储数据的,与序列式容器不同的是,其里面存储的是结构的键值对,在数据检索时比序列式容器效率更高。目标是搜索内存中存储数据。
跟之前学习的二叉搜索树一样,搜索树有key
和key-value
的两个模型。
key
:查找key在不在set
key/value
:查找key在不在;通过key查找对应的value map
根据应用场景的不同,STL总共实现了两种不同结构的管理式容器:树型结构与哈希结构。
树型结构的关联式容器主要有四种:map、set、multimap、multiset。这四种容器的共同点是:使用平衡搜索树(即红黑树) 作为其底层结果,容器中的元素是一个有序的序列。
关联式容器 | 容器结构 | 底层实现 |
---|---|---|
set,map,multiset,multimap | 树形结构 | 平衡搜索树(红黑树) |
unordered_set,unordered_map,unordered_multiset,unordered_multimap | 哈希结构 | 哈希表 |
Tips:
函数声明 | 功能介绍 |
---|---|
set (const Compare& comp = Compare(), const Allocator& = Allocator() ); | 构造空的set |
set (InputIterator first, InputIterator last, const Compare& comp = Compare(), const Allocator& = Allocator() ); | 用[first, last)区间中的元素构造set |
set ( const set& x); | set的拷贝构造 |
默认比较方式为升序
set<double> s;
比较方式为降序
set<double,greater<double> > s;
set<double> s1;
set<double> s2(s1);
string str = "123456";
set<char> s(str.begin(),str.end());
函数声明成员函数 | 功能 |
---|---|
iterator begin() | 返回set中起始位置元素的迭代器 |
iterator end() | 返回set中最后一个元素后面的迭代器 |
reverse_iterator rbegin() | 返回set第一个元素的反向迭代器,即end |
reverse_iterator rend() | 返回set最后一个元素下一个位置的反向迭代器,即 rbegin |
函数声明 | 功能介绍 |
---|---|
pair insert ( const value_type& x) | 在set中插入元素x,实际插入的是构成的键值对, 如果插入成功,返回<该元素在set中的位置,true>,如果插入失败,说明x在set中已经存在,返回 |
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& st ); | 交换set中的元素(这种方式少一次拷贝);直接swap(x,y)会有一次拷贝 |
void clear ( ) | 将set中的元素清空 |
iterator find ( const key_type& x ) const | 返回set中值为x的元素的位置 |
size_type count ( const key_type& x ) const | 返回set中值为x的元素的个数 |
插入和遍历
int main()
{
set<int> s;
s.insert(5);
s.insert(1);
s.insert(3);
s.insert(4);
s.insert(5);
s.insert(6);
s.insert(3);
s.insert(3);
// 排序+去重
// 遍历方式1:正向迭代器
set<int>::iterator it = s.begin();
while (it != s.end())//注意set不能修改值
{
cout << *it << " ";
++it;
}
cout << endl;
// 遍历方式2:反向迭代器
set<int>::reverse_iterator rit = s.rbegin();
while (rit != s.rend())
{
cout << *rit << " ";
++rit;
}
cout << endl;
// 遍历方式3:auto
for (auto e : s)
{
cout << e << " ";
}
cout << endl;
}
查找和删除
int main()
{
set<int> s;
s.insert(5);
s.insert(1);
s.insert(3);
s.insert(4);
s.insert(5);
s.insert(6);
s.insert(3);
s.insert(3);
// 先查找,找到了再删。没找到,也去删会报错
auto pos = s.find(4);
if (pos != s.end())
{
s.erase(pos);
}
pos = s.find(40);
//s.erase(pos);
if (pos != s.end())
{
s.erase(pos);
}
// 在就删除,不在就不处理也报错
s.erase(3);
s.erase(30);
}
multiset和set的接口基本一致,有个别不同。
由于multiset可以存在多个相同的key,因此multiset查找的时候找的是中序的第一个。而且set的count只有1,multiset的count可以返回具体的元素个数。
删除的时候如果删除的是迭代器那么就是删除一个元素,如果指定key就是删除该key的所有元素。
底层上multiset对同key的处理统一规定挂在第一个同key的左边或者右边。
int main()
{
multiset<int> s;
s.insert(3);
s.insert(1);
s.insert(2);
s.insert(1);
s.insert(6);
s.insert(4);
s.insert(3);
s.insert(3);
s.insert(3);
// 排序
multiset<int>::iterator it = s.begin();
while (it != s.end())
{
cout << *it << " ";
++it;
}
cout << endl;
// find查找的val有多个的时候,那么他找到的是中序的第一个
multiset<int>::iterator pos = s.find(3);
while (*pos == 3)
{
cout << *pos << endl;
++pos;
}
cout << s.count(3) << endl;
cout << s.count(4) << endl;
s.erase(3);//删除所有的3
it = s.begin();
while (it != s.end())
{
cout << *it << " ";
++it;
}
cout << endl;
}
typedef pair value_type
;pair
:
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)
{}
};
make_pair
:
template <class T1, class T2>
pair<T1, T2> make_pair(T1 x, T2 y)
{
return (pair<T1, T2>(x, y));
}
map<int,int> map1;
map<int,bool , greater<int> > map4;
map<int,int> map1;
map<int,int> map2 (map1);
map<int,int> map1;
map<int,int> map3(map1.begin(),map1.end());
map::insert
:
pair<iterator,bool> insert (const value_type&val);
insert函数的参数显示是value_type类型的,实际上value_type就是pair类型的别名:
typedef pair<const Key, T> value_type;
int main()
{
map<int, double> m;
// 调用pair的构造函数,构造一个匿名对象插入
m.insert(pair<int, double>(1, 1.1));
m.insert(pair<int, double>(3, 3.3));
m.insert(pair<int, double>(2, 2.2));
m.insert(pair<int, double>(2, 3.3)); // key相同就会插入失败
}
但是这种做法每次要声明函数模板参数,比较麻烦。
不需要去声明pair的参数,让函数模板自己推演,用起来方便些
int main()
{
map<int, double> m;
m.insert(make_pair(2, 2.2));
}
关于这里的迭代器->访问,需要回顾STLlist存一个结构体的迭代器。
it -> first ,调用it.operator->(),返回pair* (回想链表,迭代器要承担的是一个相当于封装成的指针类)
需要对(pair*)->first,second,因此应该为it->->first。编译器把两个->->处理成一个。
int main()
{
map<int, double> m;
m.insert(pair<int, double>(1, 1.1));
m.insert(pair<int, double>(3, 3.3));
m.insert(pair<int, double>(2, 2.2));
m.insert(pair<int, double>(2, 3.3));
map<int, double>::iterator it = m.begin();
while (it != m.end())
{
//cout << (*it).first <<":"<<(*it).second<
cout << it->first << ":" << it->second << endl;
++it;
}
cout << endl;
}
但是这样如果要声明多个同类型迭代器就比较麻烦,而且实际项目中并不是using namespace std
。
通过typedef
简化命名
int main()
{
typedef std::map<std::string, std::string > DICT;
typedef std::pair<std::string, std::string > DICT_KV;
typedef std::map<std::string, std::string >:: iterator Dict_ITER;
DICT dict;
dict. insert(DICT_KV("insert","插入"));
dict. insert(std::make_pair("left","左边"));
dict. insert(std::make_pair("right","右边"));
DICT_ITER dit = dict.begin();
while (dit != dict.end())
{
//cout << (*it).first <<":"<<(*it).second<
cout << dit->first << ":" << dit->second << endl;
++dit;
}
cout << endl;
}
int main()
{
typedef std::map<std::string, std::string > DICT;
typedef std::pair<std::string, std::string > DICT_KV;
typedef std::map<std::string, std::string >:: iterator Dict_ITER;
DICT dict;
dict. insert(DICT_KV("insert","插入"));
dict. insert(std::make_pair("left","左边"));
dict. insert(std::make_pair("right","右边"));
DICT_ITER dit = dict.begin();
while (dit != dict.end())
{
dit -> second.insert(0,'{');
dit -> second += '}';
++dit;
}
cout << endl;
//修改map的特定value数据
auto ret = dict.find("left");
if(ret != dict.end() )
{
//ret -> second.insert(ret -> second.size() -1 , "、剩余" );
//可读性优化小技巧
string& str = ret->second;
str.insert( str.size() - 1 ,"、剩余");
}
dit = dict.begin();
while (dit != dict.end())
{
//cout << (*it).first <<":"<<(*it).second<
cout << dit->first << ":" << dit->second << endl;
++dit;
}
cout << endl;
}
通过key
进行删除。
int main()
{
map<string,string> dict;
dict.insert(make_pair("sort","排序"));//插入
dict["left"] = "左边"; //先string默认构造函数生成空字符串,然后再修改。(插入+修改)
dict["insert"];//gdb到这里此时value是空字符串(插入)
dict["insert"]= "插入";//(修改)
dict.erase("left");
}
思路:第一次出现,插入
int main()
{
//统计次数
string str[] = { "apple", "apple" , "apple" , "banana", "banana","banana","watermelon","peach","peach","pear","pear","pear","pear"};
map<string , int > countMap;
//思路:第一次出现,插入,后续再出现就++
for(const auto & i :str)
{
map<string,int>::iterator result = countMap.find(str);
if( result != countMap.end() )
{
result -> second ++;
}
else{
countMap.insert( make_pair(i,1) );
}
}
for( const auto& e: countMap)
{
cout<< e->first <<" " << e->second << endl;
}
}
insert
的返回值:
pair<iterator,bool> insert (const value_type&val);
int main()
{
//统计次数
string str[] = { "apple", "apple" , "apple" , "banana", "banana","banana","watermelon","peach","peach","pear","pear","pear","pear"};
map<string , int > countMap;
//先插入,如果str已经在map中,insert会返回str所在节点的迭代器,我们++次数即可。
for(const auto & i :str)
{
//pair
auto ret = countMap.insert(make_pair(i,1));
if( ret. second == false)
{
ret.first->second ++;
}
}
for( const auto& e: countMap)
{
cout<< e->first <<" " << e->second << endl;
}
}
使用operator[]
V& opeartor[] (const key_type& k);
实际上该整个函数:
member type | definition | |
---|---|---|
key_type |
The first template parameter (Key ) |
|
mapped_type |
The second template parameter (T ) |
V& opeartor[] (const key_type& k)
{
return (*((this->insert(make_pair(k,mapped_type()))).first)).second
}
//mapped_type()为传入value的匿名对象,对于内置类型来说cpp提供了兼容,比如int(10),默认的话就是0.因为若第一次插入value 为int,新插入节点的value =0 并返回。
我们可以简化
value_type& opeartor[] (const key_type& k)
{
pair<iterator , bool> ret=insert(make_pair ( k, mapped_typed()));
return ret.first -> second;
}
countMap[str]++;
countMap.operator[](str)++,对返回值++
int main()
{
//统计次数
string str[] = { "apple", "apple" , "apple" , "banana", "banana","banana","watermelon","peach","peach","pear","pear","pear","pear"};
map<string , int > countMap;
//先插入,如果str已经在map中,insert会返回str所在节点的迭代器,我们++次数即可。
for(const auto & i :str)
{
map[i]++;
}
for( const auto& e: countMap)
{
cout<< e->first <<" " << e->second << endl;
}
}
int main()
{
map<string,string> dict;
dict.insert(make_pair("sort","排序"));//插入
dict["left"] = "左边"; //先string默认构造函数生成空字符串,然后再修改。(插入+修改)
dict["insert"];//gdb到这里此时value是空字符串(插入)
dict["insert"]= "插入";//(修改)
}
针对次数(value)排序。
vector
存元素int main()
{
string str[] = { "apple", "apple" , "apple" , "banana", "banana","banana","watermelon","peach","peach","pear","pear","pear","pear"};
map<string,int> countMap;
for(const auto& i: str)
{
countMap[i]++;
}
vector<pair<string,int> >v;
}
然后重载pair<>使得按照second来排序。但是很明显,这样做要多一次拷贝,效率并不高。
vector
存储迭代器迭代器在使用上就可以考虑成是一个原生指针。
struct MapItCompare{
bool operator()(map<string,int>::iterator A,map<string,int>:: iterator B)
{
return A->second > B->second;
}
}
int main()
{
string str[] = { "apple", "apple" , "apple" , "banana", "banana","banana","watermelon","peach","peach","pear","pear","pear","pear"};
map<string,int> countMap;
for(const auto& i : str){
countMap[i]++;
}
vector< map<string,int>::iterator > v;
map<string,int> :: iterator countMapIt = countMap.begin();
while( countMapIt != countMap.end() )
{
v.push_back(countMapIt);
countMapIt++;
}
sort( v.begin() , v.end() , MapItCompare() );
//set
}
multimap
int main()
{
string str[] = { "apple", "apple" , "apple" , "banana", "banana","banana","watermelon","peach","peach","pear","pear","pear","pear"};
map<string,int> countMap;
for(const auto& i : str){
countMap[i]++;
}
multimap<int,string , greater<int> > sortMap;
for(const auto& e: countMap)
{
sortMap.insert(make_pair(e.second,e.first));
}
}
// 利用set排序 --不拷贝pair数据
set<map<string, int>::iterator, MapItCompare> sortSet;
countMapIt = countMap.begin();
while (countMapIt != countMap.end())
{
sortSet.insert(countMapIt);
++countMapIt;
}
typedef map<string, int>::iterator MI;
priority_queue<MI, vector<MI>, MapItCompare> pq;
countMapIt = countMap.begin();
while (countMapIt != countMap.end())
{
pq.push(countMapIt);
++countMapIt;
}
方法一:使用map和multimap的二叉搜索树自动排序
这道题唯一的一个点就是sort
底层是快排,所以就算写了仿函数也是做不到稳定的。而题目要求的是稳定的。
而使用multimap/map的二叉搜索树的排序是稳定的,就算旋转了相对关系也是不变的。
class Solution {
public:
vector<string> topKFrequent(vector<string>& words, int k) {
map<string,int> map1;
for(auto & i: words) map1[i]++;
vector< map<string,int>::iterator > v;
map<string,int>::iterator it = map1.begin();
while ( it != map1.end() )
{
v.push_back(it);
it ++ ;
}
multimap< int ,string ,greater<int> > mult;
for(const auto& i : map1)
{
mult.insert(make_pair(i.second , i.first ));
}
vector<string> ans;
multimap< int,string ,greater<int> > ::iterator muit = mult.begin();
while(k--)
{
ans.push_back( muit->second);
muit ++ ;
}
return ans;
}
};
方法二:本质是TopK问题,利用哈希表先处理次数,然后pair
TopK问题也是面试中的一个常见问题,其特点主要是考察空间复杂度。
multimap
和map
的接口大致相同。其关系类似multiset
和set
。多个查找中序的第一个。
multimap
不提供[]
int main()
{
map<string,string> dict;
dict.insert(make_pair("left","左边"));
dict.insert(make_pair("left","剩余"));
multimap<string,string> mdict;
mdict.insert(make_pair("left","左边"));
mdict.insert(make_pair("left","剩余"));
}