Set 和 multiset 会根据特定的排序准则,自动将元素排序。两者不同之处在于 multiset 允许元素重复而 set 不允许。
set 和 multiset 包含在头文件中:
#include
在这个头文件中,上述两个类型都被定义为命名空间 std 内的 class template:
namespace std {
template <typename T,
typename Compare = less<T>,
typename Allocator = allocator<T> >
class set;
template <typename T,
typename Compare = less<T>,
typename Allocator = allocator<T> >
class multiset;
}
只要是可依据某排序准则被比较(所谓 comparable )的任意类型 T 都可以成为 set 或 multiset 的元素类型。可有可无的第二个 template 实参用来定义排序准则。如果没有传入某个排序准则,就采用默认准则 less——这是个函数对象,以 operator < 对元素进行比较。可有可无的第三实参用来定义内存模型。默认的内存模型是 allocator,由 C++ 标准库提供。
“排序准则”,必须定义 strict weak ordering,其意义如下:
注意,这意味着你必须区分 less 和 equal。一个像 operator <= 这样的准则并不满足这个条件。
根据这些性质,排序准则也被用来检查等效性。也就是说,两个元素如果没有任何一个小于另一个(或如果 op(x, y) 和 op(y, x) 都取得 false ),则它们被视为重复。
Multiset 的等效元素的次序是随机但稳定的。因此 C++11 保证安插和抹除动作都会保存等效元素的相对次序。
和所有标准的关联式容器类似,set 和 multiset 通常以平衡二叉树(如下图)完成——C++ standard 并未明定,但由 set和 multiset 各项操作的复杂度可以得出这个结论——事实上,set 和 multiset 通常以红黑树实现。红黑树在改变元素数量和元素搜寻方面都很出色,它保证节点安插时最多只会做两个重新链接动作,而且到达某一元素的最长路径的深度,至多只是最短路径的深度的两倍。
自动排序的主要优点在于令二叉树于查找元素时拥有良好效能。其查找函数具有对数复杂度。在拥有 1000 个元素的 set 或 multiset 中查找元素,二叉树查找动作(由成员函数执行)的平均时间为线性查找(由STL 算法执行)的 1/50。。
但是,自动排序造成 set 和 multiset 的一个重要限制:你不能直接改变元素值,因为这样会打乱原本正确的顺序。
因此,要改变元素值,必须先删除旧元素,再插入新元素。以下接口反映了这种行为:
操作 | 描述 |
---|---|
set c | Default 构造函数,建立一个空 set / multiset,不含任何元素 |
set c(op) | 建立一个空 set / multiset,以 op 为排序准则 |
set c(c2) | Copy 构造函数,为相同类型之另一个 set / multiset 建立一份拷贝,所有元素均被复制 |
set c = c2 | Copy 构造函数,为相同类型之另一个 set / multiset 建立一份拷贝,所有元素均被复制 |
set c(rv) | Move 构造函数,建立一个新的 set / multiset,有相同类型,取 rvalue rv 的内容(始自C++11) |
set c = rv | Move 构造函数,建立一个新的 set / multiset,有相同类型,取 rvalue rv 的内容(始自C++11) |
set c(beg, end) | 以区间 [beg, end) 内的元素为初值,建立一个 set / multiset |
set c(beg, end, op) | 以区间 [beg, end) 内的元素为初值,并以 op 为排序i准则,建立一个 set / multiset |
set c(initlist) | 建立一个 set / multiset,以初值列 initlist 的元素为初值(始自C++11) |
set c = initlist | 建立一个 set / multiset,以初值列 initlist 的元素为初值(始自C++11) |
c.~set() | 销毁所有元素,释放内存 |
其中set可为下列形式: | |
set | 描述 |
– | – |
set< Elem > | 一个 set,以 less<> (operator <) 为排序准则 |
set< Elem, Op > | 一个 set,以 Op 为排序准则 |
multiset< Elem > | 一个 multiset,以 less<> (operator <) 为排序准则 |
multiset< Elem, Op > | 一个 multiset,以 Op 为排序准则 |
#include
#include
using namespace std;
struct op{
bool operator() (const int& a, const int& b) const{
return a > b;
}
};
int main(){
int a[] = {1, 2, 3, 4, 5, 6, 7, 8};
set<int> c(a, a + 6);
set<int> c1;
set<int> c2(op);
set<int> c3(c);
set<int> c4 = c;
printf("c: ");
for(set<int>::iterator i = c.begin(); i != c.end(); i ++)
cout << *i << " ";
printf("\n");
printf("c3: ");
for(set<int>::iterator i = c3.begin(); i != c3.end(); i ++)
cout << *i << " ";
printf("\n");
printf("c4: ");
for(set<int>::iterator i = c4.begin(); i != c4.end(); i ++)
cout << *i << " ";
printf("\n");
return 0;
}
操作 | 描述 |
---|---|
c.key_comp() | 返回“比较准则” |
c.value_comp() | 返回针对 value 的“比较准则”(和 key_comp() 相同) |
c.empty() | 返回是否容器为空(相当于 size()==0 但也许较快) |
c.size() | 返回目前的元素个数 |
c.max_size() | 返回元素个数之最大可能量 |
c1 == c2 | 返回 c1 是否等于 c2(对每个元素调用==) |
c1 != c2 | 返回 c1 是否不等于 c2(相当于 !(c1 == c2) ) |
c1 < c2 | 返回 c1 是否小于 c2 |
c1 > c2 | 返回 c1 是否大于 c2(相当于 c2 < c1) |
c1 <= c2 | 返回 c1 是否小于等于 c2(相当于 !(c2 < c1) ) |
c1 >= c2 | 返回 c1 是否大于等于 c2(相当于 !(c1 < c2) ) |
#include
#include
using namespace std;
struct op{
bool operator() (const int& a, const int& b) const{
return a > b;
}
};
int main(){
int a[] = {1, 2, 3, 4, 5, 6, 7, 8};
set<int, op> c(a, a + 6);
set<int, op> c1 = c;
set<int, op> c2(a + 1, a + 7);
printf("key_comp(): %s\n", c.key_comp()(1, 2) ? "true" : "false");//1>2: false
printf("key_comp(): %s\n", c.key_comp()(2, 1) ? "true" : "false");//2>1: true
printf("value_comp(): %s\n", c.value_comp()(1, 2) ? "true" : "false");//1>2: false
printf("value_comp(): %s\n", c.value_comp()(2, 1) ? "true" : "false");//2>1: true
printf("empty(): %s\n", c.empty() ? "true" : "false");
printf("size(): %d\n", c.size());
printf("max_size(): %lld\n", c.max_size());
printf("c1 == c: %s\n", c1 == c ? "true" : "false");
printf("c1 != c2: %s\n", c1 != c2 ? "true" : "false");
printf("c1 < c: %s\n", c1 < c ? "true" : "false");
printf("c1 > c2: %s\n", c1 > c2 ? "true" : "false");
printf("c1 <= c: %s\n", c1 <= c ? "true" : "false");
printf("c1 >= c2: %s\n", c1 >= c2 ? "true" : "false");
return 0;
}
操作 | 描述 |
---|---|
c.count(val) | 返回“元素值为 val ”的元素个数 |
c.find(val) | 返回“元素值为 val ”的第一个元素,如果找不到就返回 end() |
c.lower_bound(val) | 返回 val 的第一个可安插位置,也就是“元素值 >= val ”的第一个元素位置 |
c.upper_bound(val) | 返回 val 的最后一个可安插位置,也就是“元素值 > val ”的第一个元素位置 |
c.equal_range(val) | 返回 val 可被安插的第一个位置和最后一个位置,也就是“元素值 == val ”的元素区间。将 lower_bound() 和 upper_bound() 的返回值做成一个 pair 返回。 如果 lower_bound() 或“ equal_range() 的 first 值”等于“ equal_range() 的 second 值”或 upper_bound(),则此 set 或 multiset 内不存在同值元素。 |
#include
#include
using namespace std;
int main(){
set<int> c;
c.insert(1);
c.insert(2);
c.insert(3);
c.insert(4);
c.insert(5);
c.insert(6);
printf("count(): %d\n", c.count(2));
printf("find(): %d\n", c.find(3));
cout << "lower_bound(3): " << *c.lower_bound(3) << endl;
cout << "upper_bound(3): " << *c.upper_bound(3) << endl;
cout << "equal_range(3): " <<*c.equal_range(3).first <<
*c.equal_range(3).second << endl;
cout <<endl;
cout << "lower_bound(5): " << *c.lower_bound(5) << endl;
cout << "upper_bound(5): " << *c.upper_bound(5) << endl;
cout << "equal_range(5):" << *c.equal_range(5).first <<
*c.equal_range(5).second << endl;
return 0;
}
操作 | 描述 |
---|---|
c = c2 | 将 c2 的全部元素赋值给 c |
c = rv | 将 rvalue rv 的所有元素以 move assign 方式给予 c (始自C++11) |
c = initlist | 将初值列 initlist 的所有元素赋值给 c (始自C++11) |
c1.swap(c2) | 置换 c1 和 c2 的数据 |
swap(c1, c2) | 置换 c1 和 c2 的数据 |
#include
#include
using namespace std;
int main(){
int a[] = {1, 2, 3, 4, 5, 6, 7, 8};
set<int> c(a, a + 6);
set<int> c1 = c;
set<int> c2 = {2, 3, 4, 5, 6, 7};
printf("c1: ");
for(set<int>::iterator i = c1.begin(); i != c1.end(); i ++)
cout << *i << " ";
printf("\n");
printf("c2: ");
for(set<int>::iterator i = c2.begin(); i != c2.end(); i ++)
cout << *i << " ";
printf("\n");
swap(c1, c2);
printf("\n交换c1和c2!\n");
printf("c1: ");
for(set<int>::iterator i = c1.begin(); i != c1.end(); i ++)
cout << *i << " ";
printf("\n");
return 0;
}
操作 | 描述 |
---|---|
c.begin() | 返回一个 bidirectional iterator 指向第一元素 |
c.end() | 返回一个 bidirectional iterator 指向最末元素的下一位置 |
c.cbegin() | 返回一个 const bidirectional iterator 指向第一元素(始自C++11) |
c.cend() | 返回一个 const bidirectional iterator 指向最末元素的下一位置(始自C++11) |
c.rbegin() | 返回一个反向的 (reverse) iterator 指向反向迭代的第一元素 |
c.rend() | 返回一个反向的 (reverse) iterator 指向反向迭代的最末元素的下一位置 |
c.crbegin() | 返回一个 const reverse iterator 指向反向迭代的第一元素(始自C++11) |
c.crend() | 返回一个 const reverse iterator 指向反向迭代的最未元素的下一位置(始自C++11) |
#include
#include
using namespace std;
int main(){
int a[] = {1, 2, 3, 4, 5, 6, 7, 8};
set<int> c(a, a + 6);
set<int> c1 = c;
printf("利用begin()和end()遍历set(c1): ");
for(set<int>::iterator i = c1.begin(); i != c1.end(); i ++)
cout << *i << " ";
printf("\n");
printf("利用rbegin()和rend()遍历set(c1): ");
for(set<int>::reverse_iterator i = c1.rbegin(); i != c1.rend(); i ++)
cout << *i << " ";
printf("\n");
return 0;
}
操作 | 描述 |
---|---|
c.insert(val) | 安插一个 val 拷贝,返回新元素位置,不论是否成功——对 set 而言 |
c.insert(pos, val) | 安插一个 val 拷贝,返回新元素位置( pos 是个提示,指出安插动作的查找起点。若提示恰当可加快速度) |
c.insert(beg, end) | 将区间 [beg, end) 内所有元素的拷贝安插到 c (无返回值) |
c.insert(initlist) | 安插初值列 initlist 内所有元素的一份拷贝(无返回值;始自C++11) |
c.emplace(args . . .) | 安插一个以 args 为初值的元素,并返回新元素的位置,不论是否成功——对 set 而言(始自C++11) |
c.emplace_hint(pos , args . . .) | 安插一个以 args 为初值的元素,并返回新元素的位置( pos 是个提示,指出安插动作的查找起点。若提示恰当可加快速度) |
c.erase(val) | 移除“与 val 相等”的所有元素,返回被移除的元素个数 |
c.erase(pos) | 移除 iterator 位置 pos 上的元素,无返回值 |
c.erase(beg, end) | 移除区间 [beg, end) 内的所有元素,无返回值 |
c.clear() | 移除所有元素,将容器清空 |
Set 提供如下接口:
pair<iterator, bool> insert(const value_type& val);
iterator insert(const_iterator posHint, const value_type& val);
template <typename... Args> pair<iterator, bool> emplace(Args&&... args);
template <typename... Args> iterator emplace_hint(const_iterator posHint, Args&&... args);
Multiset 提供如下接口:
iterator insert(const value_type& val);
iterator insert(const_iterator posHint, const value_type& val);
template <typename... Args> iterator emplace(Args&&... args);
template <typename... Args> iterator emplace_hint(const_iterator posHint, Args&&... args);
返回类型之所以不相同,原因是: multiset 允许元素重复而 set 不允许。因此,如果将某元素安插至 set 内,而该 set 已经内含同值元素,安插动作将告失败。所以 set 的返回类型是以 pair 组织起来的两个值:
其他任何情况下,此函数都返回新元素的位置(如果 set 已经内含同值元素,则返回同值元素的位置)。
#include
#include
using namespace std;
int main(){
int a[] = {1, 2, 3, 4, 5, 6, 7, 8};
set<int> c(a, a + 6);
set<int> c1;
pair<set<int>::iterator, bool> p = c.insert(7);
printf("%s%d: ", p.second ? "成功插入" : "插入失败", *p.first);
for(set<int>::iterator i = c.begin(); i != c.end(); i ++)
cout << *i << " ";
printf("\n");
set<int>::iterator p1 = c.insert(c.end(), 8);
printf("成功插入%d: ", *p1);
for(set<int>::iterator i = c.begin(); i != c.end(); i ++)
cout << *i << " ";
printf("\n");
c1.insert(a, a + 6);
printf("c1插入a到a+6的数据: ");
for(set<int>::iterator i = c1.begin(); i != c1.end(); i ++)
cout << *i << " ";
printf("\n\n");
p = c1.emplace(7);
printf("%s%d: ", p.second ? "成功插入" : "插入失败", *p.first);
for(set<int>::iterator i = c1.begin(); i != c1.end(); i ++)
cout << *i << " ";
printf("\n");
p1 = c1.emplace_hint(c1.end(), 8);
printf("成功插入%d: ", *p1);
for(set<int>::iterator i = c1.begin(); i != c1.end(); i ++)
cout << *i << " ";
printf("\n\n");
int cnt = c.erase(4);
printf("成功移除%d个4: ", cnt);
for(set<int>::iterator i = c.begin(); i != c.end(); i ++)
cout << *i << " ";
printf("\n");
c.erase(c.begin());
printf("成功移除首位元素: ");
for(set<int>::iterator i = c.begin(); i != c.end(); i ++)
cout << *i << " ";
printf("\n");
c.erase(c.begin(), c.end());
printf("成功移除所有元素: ");
for(set<int>::iterator i = c.begin(); i != c.end(); i ++)
cout << *i << " ";
printf("\n");
c1.clear();
printf("成功清除所有元素: ");
for(set<int>::iterator i = c1.begin(); i != c1.end(); i ++)
cout << *i << " ";
printf("\n");
return 0;
}
利用set内部默认的compare函数,可以将整数从小到大排序。
可以通过定义结构体(或类),并在其中重载()运算符,来自定义排序函数。然后,在定义set的时候,将结构体加入其中例如如下代码中的 set
规则代码如下:
//结构体
struct op{
bool operator() (const int& a, const int& b) const{
return a > b;
}
};
//类
class op1{
public:
bool operator() (const int& a, const int& b) const{
return a > b;
}
};
看一个示例:
#include
#include
using namespace std;
struct op{
bool operator() (const int& a, const int& b) const{
return a > b;
}
};
class op1{
public:
bool operator() (const int& a, const int& b) const{
return a > b;
}
};
int main(){
int a[] = {1, 2, 3, 4, 5, 6, 7, 8};
set<int> c1(a, a + 6);
set<int, op> c2(a, a + 6);
set<int, op1> c3(a, a + 6);
printf("默认排序准则c1: ");
for(set<int>::iterator i = c1.begin(); i != c1.end(); i ++)
cout << *i << " ";
printf("\n");
printf("自定义排序准则c2: ");
for(set<int>::iterator i = c2.begin(); i != c2.end(); i ++)
cout << *i << " ";
printf("\n");
printf("自定义排序准则c3: ");
for(set<int>::iterator i = c3.begin(); i != c3.end(); i ++)
cout << *i << " ";
printf("\n");
return 0;
}
在自定义结构体中重载< 也可以实现默认排序,示例代码如下:
struct Student{
string id;
string college;
int age;
bool operator <(const Student& a) const{
if(college != a.college)
return college < a.college;
else if(id != a.id)
return id < a.id;
else
return age < a.age;
}
};