再探 set/map
Posted 看,未来
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了再探 set/map相关的知识,希望对你有一定的参考价值。
文章目录
set和map底层数据结构都是红黑树,红黑树的data域段为==pair<key, value>==类型。
set篇
放码过来
先来段源码看一下吧。
缩略版,太长了。
//默认排序方法:从小到大排。话说看了这么多都是从小到大排为默认的,数据库也是这样的、vector也是这样的
template <class Key, class Compare = less<Key>, class Alloc = alloc>
class set {
public:
//key_type 和 value_type 的类型是一样的
typedef Key key_type;
typedef Key value_type;
//key_compare 和 value_compare 也用到了同一个比較函数
typedef Compare key_compare;
typedef Compare value_compare;
private:
//底层采用红黑树来实现
typedef rb_tree<key_type, value_type,
identity<value_type>, key_compare, Alloc> rep_type;
rep_type t; // red-black tree representing set
public:
typedef typename rep_type::const_pointer pointer;
typedef typename rep_type::const_pointer const_pointer;
typedef typename rep_type::const_reference reference;
typedef typename rep_type::const_reference const_reference;
//set 的 iterator 定义为红黑树的 const_iterator,这表示 set 的迭代器无法运行写入操作。
//这是由于 set 的元素有一定次序安排,不同意用户在随意处进行写入操作
typedef typename rep_type::const_iterator iterator;
typedef typename rep_type::const_iterator const_iterator;
typedef typename rep_type::const_reverse_iterator reverse_iterator;
typedef typename rep_type::const_reverse_iterator const_reverse_iterator;
typedef typename rep_type::size_type size_type;
typedef typename rep_type::difference_type difference_type;
// set 一定要使用 RB-tree 的 insert-unique() 。这样当要插入
//已经存在的键值时,会选择忽略
set() : t(Compare()) {} // 传递 Compare() 产生的函数对象给底层的红黑树作为初始化时设定的比較函数
explicit set(const Compare& comp) : t(comp) {}
//下面全部的 set 操作行为,RB-tree 都已提供,所以 set 仅仅要调用就可以
iterator begin() const { return t.begin(); }
iterator end() const { return t.end(); }
bool empty() const { return t.empty(); }
size_type size() const { return t.size(); }
size_type max_size() const { return t.max_size(); }
void swap(set<Key, Compare, Alloc>& x) { t.swap(x.t); }
// insert/erase
typedef pair<iterator, bool> pair_iterator_bool;
pair<iterator,bool> insert(const value_type& x) {
pair<typename rep_type::iterator, bool> p = t.insert_unique(x);
return pair<iterator, bool>(p.first, p.second);
}
iterator insert(iterator position, const value_type& x) {
typedef typename rep_type::iterator rep_iterator;
return t.insert_unique((rep_iterator&)position, x);
}
void erase(iterator position) {
typedef typename rep_type::iterator rep_iterator;
t.erase((rep_iterator&)position);
}
size_type erase(const key_type& x) {
return t.erase(x);
}
void clear() { t.clear(); }
iterator find(const key_type& x) const { return t.find(x); }
size_type count(const key_type& x) const { return t.count(x); }
iterator lower_bound(const key_type& x) const {
return t.lower_bound(x);
}
iterator upper_bound(const key_type& x) const {
return t.upper_bound(x);
}
};
set的定义
从上述代码中我们可以看出
set的底层结构是红黑树_Rep_type _M_t;
因为是红黑树所以元素自动排序,排序函数为_Compare,默认按照标准函数std::less<_key>进行排序的
当然也可以传入自定义的排序函数:
自定义排序函数
#include <iostream>
#include <set>
using namespace std;
struct cmp {
/*
这里将函数设置为常量函数,不然会报错:
“const cmp”的表达式会丢失一些 const - volatile 限定符以调用“bool cmp::operator ()(const int&, const int&)”
报错内容大概意思为:传入的参数表达式应具有“const myCompare”类型,而你调用的“bool myCompare::operator ()(int,int)”不具备const属性,会丢失const限定,所以无法通过编译。
*/
bool operator() (int a, int b) const
{
return a > b;
}
};
int main()
{
set<int, cmp> s;
s.insert(5);
s.insert(3);
s.insert(6);
s.insert(7);
set<int>::iterator it;
for (it = s.begin(); it != s.end(); it++) {
cout << *it << endl;
}//输出7 6 5 3
return 0;
}
若是元素是结构体,那么能够直接把比较函数写在结构体内:
#include <iostream>
#include <set>
#include <string>
using namespace std;
struct Info {
string name;
double score;
//如果不定义这个函数,人家还不给插入
bool operator < (const Info& a) const {//重载<运算符
return a.score < score;
}
};
int main()
{
set<Info> s;
Info info;
info.name = "Jack";
info.score = 80;
s.insert(info);
info.name = "Tom";
info.score = 99;
s.insert(info);
info.name = "Bill";
info.score = 100;
s.insert(info);
set<Info>::iterator it;
for (it = s.begin(); it != s.end(); it++) {
cout << it->name << endl;
cout << it->score << endl;
}
return 0;
}
再来个模板版本的:
#include <iostream>
#include <set>
using namespace std;
/// set默认是从小到大排列,以下是让set从大到小排列
template <typename T>
struct greator
{
bool operator()(const T& x, const T& y) const
{
return x > y;
}
};
int main(void)
{
set<int, greator<int>> iset;
iset.insert(12);
iset.insert(1);
iset.insert(24);
for (set<int>::const_iterator iter = iset.begin(); iter != iset.end(); iter++)
{
cout << *iter << " ";
}
cout << endl;
system("pause");
return 0;
}
这个版本排不了结构体等自定义类型的数据哈,这个跟我没关系,是模板自己的短板。
set的迭代器
set的迭代器都是const的,因此无法通过迭代器修改set元素。
正常,人家的迭代器是主键,要用来排序的。你可以删,但不可以改。
就像数据库的主键一样,你可以整条记录删掉,但是你想修改主键,嘿,不好意思。
键可删,不可改!
元素被修改后,容器并不会自动重新调整顺序,不为什么,因为没提供这个方法。
begin() 、end()
begin()方法返回set的首元素,调用的是红黑树的begin()方法,实际上这个红黑树的begin()方法返回的并不是整棵树的根节点,而是整个树的最左节点。因为set的元素是按顺序排列的,最左节点也是最小节点。
同理end()方法则返回的是最右节点,最小的值。
是否成功插入
有时候吧,就需要判断一下是否成功插入了,我记得以前做过一道算法题就需要用到这个特性,如果重复插入,需要采取一些措施的。
也可以先find,再insert,我嫌麻烦。这样吧:
#include <iostream>
#include <set>
using namespace std;
int main(void)
{
set<int> iset;
cout << iset.insert(12).second << endl;
cout << iset.insert(12).second << endl;
return 0;
}
pair<iterator, bool> insert(const T & val);
如果 set 的 insert 成员函数的返回值是 pair 模板类对象 x,如果 x.second 为 true,则说明插入成功,此时 x.first 就是指向被插入元素的迭代器;如果 x.second 为 false,则说明要插入的元素已在容器中,此时 x.first 就是指向原有那个元素的迭代器。
元素的检索
使用find()方法对set进行检索。找到则返回元素位置的迭代器,不然返回end()。
这个我熟,就不多说了。
#include <iostream>
#include <set>
using namespace std;
int main()
{
set<int> s;
s.insert(5);
s.insert(3);
s.insert(6);
s.insert(7);
set<int>::iterator it;
it = s.find(3);
if(it == s.end()) cout << "Not found" << endl;
else cout << "Find it!" << endl;
return 0;//输出Find it!
}
为何map和set的插入删除效率比其他序列容器高,而且每次insert之后,以前保存的iterator不会失效?
1、因为存储的是结点,不需要内存拷贝和内存移动。
2、map和set的增删改查速度为都是logn,是比较高效的。
为什么 set 的底层不用hash?
1、用,干嘛不用。用完改名字了,叫 unordered_set。
2、如果只是判断set中的元素是否存在,那么hash显然更合适,因为set 的访问操作时间复杂度是log(N)的,而使用hash底层实现的hash_set是近似O(1)的。然而,set应该更加被强调理解为“集合”,而集合所涉及的操作并、交、差等,即STL提供的如交集set_intersection()、并集set_union()、差集set_difference()和对称差集set_symmetric_difference(),都需要进行大量的比较工作,那么使用底层是有序结构的红黑树就十分恰当了,这也是其相对hash结构的优势所在。
set.lower_bound(x)/upper_bound(x)
用法与find类似,但查找的条件略有不同,时间复杂度O(log n)
s.lower_bound(x)表示查找>=x的元素中最小的一个,并返回指向该元素的迭代器
s.upper_bound(x)表示查找>x的元素中最小的一个,并返回指向该元素的迭代器
举个例子:
在set{3,5,7,8,13,16}中
对于在set中存在的元素,比如8
s.lower_bound(8)返回8所在位置的迭代器
s.upper_bound(8)返回13所在位置的迭代器
对于在set中不存在的元素,比如12
两个函数返回的则都是13所在位置的迭代器
特殊地,
对于比set中最大的元素大的元素,比如20,两个函数返回的都是s.end()。
有一个结构体,里面有两个字符串,如何在一个set中查找这个结构体?
啊,这,刚刚的结构体排序就懵了一下吧,这个哈哈。。
话说这是一道面试题,算上排序,是两道,我在同一场压力面里都遇上了,都没答上来。
前面的排序通过重载了小于号运算符,那判断相等不就重载等于号运算符嘛,就很简单的。
(好吧,其实没有这么简单)
#include <iostream>
#include <set>
#include <string>
using namespace std;
struct Info {
string name;
double score;
//如果不定义这个函数,人家还不给插入
bool operator < (const Info& a) const {//重载<运算符
return a.score < score;
}
bool operator==(const Info& a) const {
if (this->name <= a.name) {
return true;
}
else {
return false;
}
}
};
class compoper {
public:
bool operator() (const Info& left, const Info& right) const {
if (left.name <= right.name) {
return true;
}
else {
return false;
}
}
};
int main()
{
set<Info, compoper> s;
Info info;
info.name = "Jack";
info.score = 80;
s.insert(info);
info.name = "Tom";
info.score = 99;
s.insert(info);
info.name = "Bill";
info.score = 100;
s.insert(info);
set<Info>::iterator it;
struct Info a;
//a.name = "Bill";
a.name = "Bil";
if (find(s.begin(), s.end(), a) == s.end()) {
cout << "false" << endl;
}
else {
cout << "true" << endl;
}
/* string str1 = "abc";
string str2 = "def";
cout << (str1 <= str2) << endl;*/
return 0;
}
除了在结构体内部要配备 “==” 运算符重载之外,还要给 set 传入comp函数。
map篇
放码过来
跟上边的一样,缩减版。
template <class Key, class T, class Compare = less<Key>, class Alloc = alloc>
class map {
public:
typedef Key key_type; // 键值型別
typedef T data_type; // 真值型別
typedef pair<const Key, T> value_type; // 元素型別(键值/真值)
typedef Compare key_compare; // 键值比较函式
// 以下定义一个 functor,其作用就是唤醒 元素比较函式。
class value_compare: public binary_function<value_type, value_type, bool> {
friend class map<Key, T, Compare, Alloc>;
protected :
Compare comp;
value_compare(Compare c) : comp(c) {}
public:
bool operator()(const value_type& x, const value_type& y) const {
return comp(x.first, y.first);
}
};
private:
typedef rb_tree<key_type, value_type, select1st<value_type>, key_compare, Alloc> rep_type;
rep_type t;
public:
typedef typename rep_type::pointer pointer;
typedef typename rep_type::const_pointer const_pointer;
typedef typename rep_type::reference reference;
typedef typename rep_type::const_reference const_reference;
typedef typename rep_type::iterator iterator;
···
// 注意, map 一定使用 insert_unique() 而不使用 insert_equal()。
// multimap 才使用 insert_equal()。
map() : t(Compare()) {}
explicit map(const Compare& comp) : t(comp) {}
map(const map<Key, T, Compare, Alloc>& x) : t(x.t) {}
map<Key, T, Compare, Alloc>& operator=(const map<Key, T, Compare, Alloc>& x)
{
t = x.t;
return *以上是关于再探 set/map的主要内容,如果未能解决你的问题,请参考以下文章