c++ set、unordered_set

  • 总结:
  • set是STL中的一种关联容器。元素值本身val 就是键-key。因此容器中元素是唯一的。
  • set内部采用红黑树实现,因此容器内部的数据都是有序的。
  • set中插入、删除操作复杂度为O(logN),查找的复杂度基本为O(logN)
  • set容器中元素的值不能在容器进行修改,但可以对元素进行插入、删除操作。
  • unordered_set底层基于哈希表实现,内部是无序的。
  • unordered_setval 具有唯一性,插入、查询、删除速度接近于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_setunordered_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_eqhash_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底层详解 中介绍了 mapunordered_map的区别,这里总结一下setunordered_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会更高效一些。

猜你喜欢

转载自blog.csdn.net/u013271656/article/details/113883569