- 总结:
set
是STL中的一种关联容器。元素值本身val
就是键-key
。因此容器中元素是唯一的。set
内部采用红黑树实现,因此容器内部的数据都是有序的。set
中插入、删除操作复杂度为O(logN)
,查找的复杂度基本为O(logN)
。set
容器中元素的值不能在容器进行修改,但可以对元素进行插入、删除操作。
unordered_set
底层基于哈希表实现,内部是无序的。unordered_set
中val
具有唯一性,插入、查询、删除速度接近于O(1)
,最差O(N)
。unordered_set
中元素的值-val
也是元素的键-key
unordered_set
容器内部存储的各个元素的值都互不相等,且不能被修改。
1. std::set
1.1 构造、常用函数
- 构造函数
explicit set(const key_compare& comp = key_compare(), // 1.
const allocator_type& alloc = allocator_type());
explicit set(const allocator_type& alloc);
template <class InputIterator> // 2. range
set(InputIterator first, InputIterator last,
const key_compare& comp = key_compare(),
const allocator_type& = allocator_type());
set(const set& x);
set(const set& x, const allocator_type& alloc); // 3. copy
set(set&& x);
set(set&& x, const allocator_type& alloc); // 4. move
set(initializer_list<value_type> il, // 5. initializer list
const key_compare& comp = key_compare(),
const allocator_type& alloc = allocator_type());
测试代码:
#include <iostream>
#include <set>
bool fncomp(int lhs, int rhs) {
return lhs<rhs;
}
struct classcomp {
bool operator()(const int& lhs, const int& rhs) const{
return lhs<rhs;
}
};
int main() {
std::set<int> first; // 1.
int myints[]= {10,20,30,40,50};
std::set<int> second(myints,myints+5); // 2. range
std::set<int> third(second); // 3. a copy of second
std::set<int> fourth(second.begin(), second.end()); // 4. range
std::set<int,classcomp> fifth; // class as Compare
bool(*fn_pt)(int,int) = fncomp;
std::set<int,bool(*)(int,int)> sixth(fn_pt); // function pointer as Compare
return 0;
}
常用函数:
begin 返回指向map头部的迭代器
cbegin 返回指向容器头的迭代器-const
cend 返回指向容器尾元素后一个位置的迭代器 - const
clear 删除所有元素,size=0
count 返回对应val出现的次数,0或1
crbegin 返回指向容器最后一个元素的 逆序 迭代器 - const
crend 返回指向容器头元素前一个位置的 逆序 迭代器 - const
emplace 当容器中未包含val 时,插入该元素 move
emplace_hint 使用迭代器作为插入标示,当容器中无该key时,才会将其插入在标示位前
empty 容器是否为空
end 返回指向容器末尾+1处的迭代器
equal_range 返回一个pair,包含两个迭代器,first是lower_bound(),second是upper_bound()
erase 删除元素
find 查找元素,找到返回迭代器,未找到返回map.end()
get_allocator 返回与容器相关联的allocator的副本
insert 插入元素
key_comp 返回该容器的比较对象副本
lower_bound 返回一个迭代器,该迭代器指向容器中 >=给定val 的元素位置
max_size 返回可以容纳的最大元素个数
operator=
rbegin 返回指向容器最后一个元素的 逆序 迭代器
rend 返回指向容器头元素前一个位置的 逆序 迭代器
size 返回map中元素的个数
swap 当前容器与 作为参数输入的容器 交换元素,当前容器size改变
upper_bound 返回一个迭代器,该迭代器指向>给定key的 第一个位置
value_comp 返回该容器的比较对象副本
1.2 插入、删除
- 插入可以使用
emplace()
、emplace_hint()
、insert()
。 emplace()
:当容器中未包含val
时,通过move
插入该元素 ,容器中无该元素时插入,否则不插入。返回值为pair,first为指向该元素的迭代器,second为插入是否成功。功能与map类似。emplace_hint()
: 比emplace()
多了个插入位置的参数,这里说说set是有序的,为什么还要插入点?
:因为没插入点,元素按顺序与容器中元素比较,然后插入。
:有插入点了,元素会从提示处搜索位置,然后插入,速度更快了。insert()
:不多介绍。
template <class... Args>
pair<iterator,bool> emplace(Args&&... args); // 1. emplace
template <class... Args>
iterator emplace_hint(const_iterator position, Args&&... args); // 2. emplace_hint
pair<iterator,bool> insert(const value_type& val);
pair<iterator,bool> insert(value_type&& val); // 3. insert-defualt
iterator insert(const_iterator position, const value_type& val); // 3. insert-hint
iterator insert(const_iterator position, value_type&& val);
template <class InputIterator>
void insert(InputIterator first, InputIterator last); // 3. insert-range
void insert(initializer_list<value_type> il); // 3. insert-initializer list
测试代码:
int main () {
std::set<std::string> myset;
myset.emplace("foo");
myset.emplace("bar");
auto ret = myset.emplace("foo"); // emplace
if (!ret.second) // 打印 emplace 返回值
std::cout << "foo 已存在 myset 中.\n";
auto it = myset.cbegin();
myset.emplace_hint(it,"cuda"); // emplace_hint, yes!! 更快更高更强
std::cout << "myset:\n"; // 通过迭代器打印 set
for (it=myset.begin(); it!=myset.end(); ++it)
std::cout << *it << " ";
std::cout << "\n";
return 0;
}
foo 已存在 myset 中.
myset:
bar cuda foo
erase()
也不多BB,贴个函数原型。
iterator erase(const_iterator position); // 1. 通过迭代器位置删除元素
size_type erase(const value_type& val); // 2. 通过元素,删除元素
iterator erase(const_iterator first, const_iterator last); // 3. 删除迭代器范围内[first,last)元素
1.3 find、count
find()
:查找元素val
,找到了返回指向这个val
的迭代器,找不到返回set.end()
。count
:查找val
,返回val
的个数,因为set中val就是key
, 元素唯一,所以找到元素返回1,否则返回0。
const_iterator find(const value_type& val); // find
iterator find(const value_type& val);
size_type count(const value_type& val) const; // count
测试代码:
int main() {
std::set<int> myset;
std::set<int>::iterator it;
for (int i=1; i<=5; i++)
myset.insert(i*10); // set: 10 20 30 40 50
it = myset.find(20); // find()
myset.erase(it); // 删除val-20
myset.erase(myset.find(40)); // 删除val-40
if (myset.count(20) != 0) // count. 返回val-20的个数
std::cout << "20 is an element of myset.\n";
else
std::cout << "20 is not an element of myset.\n";
std::cout << "myset contains:";
for (it=myset.begin(); it!=myset.end(); ++it)
std::cout << ' ' << *it;
std::cout << '\n';
return 0;
}
20 is not an element of myset.
myset contains: 10 30 50
1.4 lower_bound、upper_bound、equal_range
lower_bound()
:返回一个迭代器,该迭代器指向 >=参数 的第一个元素的位置。upper_bound()
: 返回一个迭代器,该迭代器指向 >参数 的第一个位置。equal_range()
: 返回pair<iterator,iterator>
,是上述俩返回值的组合。函数原型:
iterator lower_bound(const value_type& val);
const_iterator lower_bound(const value_type& val) const; // lower_bound
iterator upper_bound(const value_type& val);
const_iterator upper_bound(const value_type& val) const; // upper_bound
pair<const_iterator,const_iterator> equal_range(const value_type& val) const;
pair<iterator,iterator> equal_range(const value_type& val); // equal_range
测试代码:
int main() {
std::set<int> myset;
std::set<int>::iterator itlow,itup;
for (int i=1; i<10; i++)
myset.insert(i*10); // 10 20 30 40 50 60 70 80 90
itlow=myset.lower_bound(30); // ^
itup=myset.upper_bound(60); // ^
std::cout << "the lower bound points to: " << *itlow << '\n';
std::cout << "the upper bound points to: " << *itup << "\n\n";
std::pair<std::set<int>::const_iterator,std::set<int>::const_iterator> ret;
ret = myset.equal_range(30);
std::cout << "equal_range.return.first: " << *ret.first << '\n';
std::cout << "equal_range.return.second: " << *ret.second << "\n\n";
myset.erase(itlow, itup); // 删除上述范围[30,...,70) 内元素
std::cout << "myset contains:"; // 10 20 70 80 90
for (std::set<int>::iterator it=myset.begin(); it!=myset.end(); ++it)
std::cout << ' ' << *it;
std::cout << '\n';
return 0;
}
the lower bound points to: 30
the upper bound points to: 70
equal_range.return.first: 30
equal_range.return.second: 40
myset contains: 10 20 70 80 90
2. std::unordered_set
- 这里对于其底层实现就不过多介绍了,可以看这篇博文:unordered_map底层详解。其中有详细的底层介绍。
unordered_set
、unordered_map
底层都是哈希表。
2.1 构造、常用函数
- 构造函数与其他容器类似。
// 1. default
explicit unordered_set(size_type n = /* see below */,
const hasher& hf = hasher(),
const key_equal& eql = key_equal(),
const allocator_type& alloc = allocator_type() );
explicit unordered_set( const allocator_type& alloc );
// 2. range
template <class InputIterator>
unordered_set(InputIterator first, InputIterator last,
size_type n = /* see below */,
const hasher& hf = hasher(),
const key_equal& eql = key_equal(),
const allocator_type& alloc = allocator_type() );
// 3. copy
unordered_set( const unordered_set& ust );
unordered_set( const unordered_set& ust, const allocator_type& alloc );
// 4. move
unordered_set( unordered_set&& ust );
unordered_set( unordered_set&& ust, const allocator_type& alloc );
// initializer list
unordered_set( initializer_list<value_type> il,
size_type n = /* see below */,
const hasher& hf = hasher(),
const key_equal& eql = key_equal(),
const allocator_type& alloc = allocator_type() );
测试代码:
#include <iostream>
#include <string>
#include <unordered_set>
template<class T>
T cmerge(T a, T b) {
T t(a);
t.insert(b.begin(),b.end());
return t;
}
int main() {
std::unordered_set<std::string> first; // 1. empty
std::unordered_set<std::string> second({"red","green","blue"}); // 5. init list
std::unordered_set<std::string> third({"orange","pink","yellow"}); // 5. init list
std::unordered_set<std::string> fourth(second); // 3. copy
std::unordered_set<std::string> fifth(cmerge(third,fourth)); // 4. move
std::unordered_set<std::string> sixth(fifth.begin(), fifth.end()); // 5. range
std::cout << "sixth contains:"; // 可以通过c++ 方式遍历元素
for (const std::string& x: sixth) std::cout << " " << x;
std::cout << std::endl;
return 0;
// 打印:
// sixth contains: pink yellow red green orange blue
}
常用函数如下:
begin 返回指向容器头的迭代器
bucket 返回val 所对应的桶(bucket)的编号
bucket_count 返回容器中桶(bucket)的个数
bucket_size 返回对应桶(bucket)中的元素个数
cbegin 返回指向容器头的迭代器-const
cend 返回指向容器尾元素后一个位置的迭代器 - const
clear 清空容器
count 返回val 的个数,因为unordered_set不允许有重复val,所以返回0或1
emplace move
emplace_hint 通过迭代器位置进行emplace, 因此可以从参数位置开始搜索,速度更快
empty 判断容器是否为空
end 返回指向容器尾的迭代器
equal_range
erase 删除
find 查找
get_allocator
hash_function
insert 插入
key_eq
load_factor 返回容器当前负载系数
max_bucket_count 返回容器所能包含的桶的最大数量
max_load_factor 容器最大负载系数,可通过该函数进行设置
max_size 返回容器可以容纳的最大元素数
operator= 重载运算符 =
rehash 参数n大于当前桶数,rehash,否则容器无变化
reserve n大于bucket_count*max_load_factor,rehash,否则容器无变化
size 返回容器中元素个数
swap 与参数容器中元素进行交换
2.2 key_eq、hash_function
key_eq
、hash_function
、这几个函数在unordered_map底层详解一文中未进行详细介绍,这里挑出来讲讲。
- 除这三个函数外的-常用函数具体功能可见文章:unordered_map底层详解。
key_eq
:两个元素的val
作为参数输入,返回这两元素是否相等hash_function
:哈希函数是一个一元函数,key
作为输入参数,返回与key
相匹配唯一hash值
key_equal key_eq() const;
hasher hash_function() const;
测试代码:
typedef std::unordered_set<std::string> stringset;
int main() {
stringset myset;
// 分别计算 "that"、"meme" 的 hash-val
stringset::hasher fn = myset.hash_function();
std::cout << "that: " << fn("that") << std::endl;
std::cout << "THAT: " << myset.hash_function()("THAT") << std::endl;
// key_eq
bool case_insensitive = myset.key_eq()("checking","CHECKING");
std::cout << "是否区分大小写:";
std::cout << (case_insensitive ? "no." : "yes!" );
std::cout << std::endl;
return 0;
}
that: 15843861542616104093
THAT: 13972550031453310737
是否区分大小写:yes!
3. set与 unordered_set的区别
- 在unordered_map底层详解 中介绍了
map
与unordered_map
的区别,这里总结一下set
与unordered_set
的区别: - set与 unordered_set的区别与
map与unordered_map的区别
类似。
std::set
底层是用红黑树实现
优点:
I. 内部元素有序,其元素的有序性在很多应用中都会简化很多的操作。
II. 红黑树结构使得set
中的插入、删除、查找都可在O(logn)
下完成。
缺点:
I. 内存占用较大
std::unordered_set
基于哈希表实现
优点:
I. 查找、插入、删除速度非常的快,复杂度接近O(1),最差情况为O(n)
缺点:
I. 哈希表结构使得其内部元素无序。
II. 内存方面,红黑树 VS 哈希表:unordered_set
占用的内存要高一些。
- 对于处理不重复的数据,无论是查找、插入 还是 删除操作的效率:
unordered_set
都优于set
。因此通常情况下使用unordered_set
会更加高效一些,当对于那些有顺序要求的问题,用set会更高效一些。