在C++98中,STL提供了底层为红黑树结构的一系列关联式容器,在查询时效率可达到 ,即最差情况下
需要比较红黑树的高度次,当树中的节点非常多时,查询效率也不理想。最好的查询是,进行很少的比较次
数就能够将元素找到,因此在C++11中,STL又提供了4个unordered系列的关联式容器,这四个容器与红黑
树结构的关联式容器使用方式基本类似,只是其底层结构不同,本文中只对unordered_map和
unordered_set进行介绍,unordered_multimap和unordered_multiset学生可查看文档介绍
unordered_map在线文档说明
1、unordered_map的构造
函数声明 | 功能介绍 |
---|---|
unordered_map | 构造不同格式的unordered_map对象 |
2、unordered_map的容量
函数声明 | 功能介绍 |
---|---|
bool empty()const | 监测unordered_map是否为空 |
size_t size() const | 获取unordered_map的有效元素个数 |
3、unordered_map的迭代器
函数声明 | 功能介绍 |
---|---|
begin | 返回unordered_map第一个元素的迭代器 |
end | 返回unordered_map最后一个元素下一个位置的迭代器 |
cbegin | 返回unordered_map第一个元素的const迭代器 |
cend | 返回unordered_map最后一个元素下一个位置的const迭代器 |
4、unordered_map的元素访问
函数声明 | 功能介绍 |
---|---|
operator[] | 返回与key对应的value,没有一个默认值 |
注意:该函数中实际调用哈希桶的插入操作,用参数key与V()构造一个默认值往底层哈希桶中插入,如果key不在哈希桶中,插入成功,返回V(),插入失败,说明key已经在哈希桶中,将key对应的value返
回。
5、unordered_map的查询
函数声明 | 功能介绍 |
---|---|
iterator find(const K& key) | 返回key在哈希桶中的位置 |
size_t count(const K& key) | 返回哈希桶中关键码为key的键值对的个数 |
6、 unordered_map的修改操作
函数声明 | 功能介绍 |
---|---|
insert | 向容器中插入键值对 |
erase | 删除容器中的键值对 |
void clear() | 清空容器中有效元素个数 |
void swap(unordered_map&) | 交换两个容器中的元素 |
7、unordered_map的桶操作
函数声明 | 功能介绍 |
---|---|
size_t bucket count() const | 返回哈希桶中桶的总个数 |
size_t bucket size(size_t n)const | 返回n号桶中有效元素的总个数 |
size_t bucket(const K& key) | 返回元素key所在的桶号 |
unordered_set 在线文档说明
unordered系列的关联式容器之所以效率比较高,是因为其底层使用了哈希结构。
顺序结构以及平衡树中,元素关键码与其存储位置之间没有对应的关系,因此在查找一个元素时,必须要经过关键码的多次比较。顺序查找时间复杂度为O(N),平衡树中为树的高度,即O(log2N),搜索的效率取决于搜索过程中元素的比较次数。
理想的搜索方法:可以不经过任何比较,一次直接从表中得到要搜索的元素。如果构造一种存储结构,通过某种函数(hashFunc)使元素的存储位置与它的关键码之间能够建立一一映射的关系,那么在查找时通过该函数可以很快找到该元素。
当向结构中
该方式即为哈希(散列)方法,哈希方法中使用的转换函数为哈希(散列)函数,构造 出来的结构称为哈希表(Hash Table)(或者称散列表)
例如:数据集合{1,7,6,4,5,9};
哈希函数设置为:hash(key) = key%capacity;(capacity为存储元素底层空间总的大小)
用该方法进行搜索不必进行多次关键码的比较,因此搜索的速度比较快。
问题:按照上述哈希方式,向集合中插入44,会出现什么问题?
对于两个数据元素的关键字ki和kj(i != j),有ki != kj,但有:Hash(ki) == Hash(kj),即:不同关键字通过相同哈希函数计算出相同的哈希地址,这种现象称为哈希冲突或哈希碰撞。
把具有不同关键码而具有相同哈希地址的数据元素称为“同义词”。
发生哈希冲突该如何处理呢?
引起哈希冲突的一个原因可能是:哈希函数设计不够合理。 哈希函数设计原则:
常见哈希函数
直接定制法–(常用)
取关键字的某个线性函数为散列地址:Hash(Key)= A*Key + B 优点:简单、均匀 缺点:需要事先知道关键字的分布情况
使用场景:适合查找比较小、连续、范围比较集中,每个数据分配一个位置。
面试题:字符串中第一个只出现一次字符
除留余数法–(常用)
设散列表中允许的地址数为m,取一个不大于m,但最接近或者等于m的质数p作为除数,按照哈希函数:Hash(key) = key% p(p<=m),将关键码转换成哈希地址
使用场景:范围不集中,分布分散。
平方取中法–(了解)
假设关键字为1234,对它平方就是1522756,抽取中间的3位227作为哈希地址; 再比如关键字为
4321,对它平方就是18671041,抽取中间的3位671(或710)作为哈希地址 平方取中法比较适合:不知
道关键字的分布,而位数又不是很大的情况
折叠法–(了解)
折叠法是将关键字从左到右分割成位数相等的几部分(最后一部分位数可以短些),然后将这几部分叠加
求和,并按散列表表长,取后几位作为散列地址。
折叠法适合事先不需要知道关键字的分布,适合关键字位数比较多的情况
随机数法–(了解)
选择一个随机函数,取关键字的随机函数值为它的哈希地址,即H(key) = random(key),其中random为
随机数函数。
通常应用于关键字长度不等时采用此法
数学分析法–(了解)
设有n个d位数,每一位可能有r种不同的符号,这r种不同的符号在各位上出现的频率不一定相同,可能
在某些位上分布比较均匀,每种符号出现的机会均等,在某些位上分布不均匀只有某几种符号经常出
现。可根据散列表的大小,选择其中各种符号分布均匀的若干位作为散列地址。例如:
假设要存储某家公司员工登记表,如果用手机号作为关键字,那么极有可能前7位都是 相同的,那么我
们可以选择后面的四位作为散列地址,如果这样的抽取工作还容易出现 冲突,还可以对抽取出来的数字
进行反转(如1234改成4321)、右环位移(如1234改成4123)、左环移位、前两数与后两数叠加(如1234改
成12+34=46)等方法。
数字分析法通常适合处理关键字位数比较大的情况,如果事先知道关键字的分布且关键字的若干位分布
较均匀的情况
注意:哈希函数设计的越精妙,产生哈希冲突的可能性就越低,但是无法避免哈希冲突
解决哈希冲突两种常见的方法是:闭散列和开散列
闭散列:也叫开放定址法,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有空位置,那
么可以把key存放到冲突位置中的“下一个” 空位置中去。那如何寻找下一个空位置呢?
1、线性探测
比如2.1中的场景,现在需要插入元素44,先通过哈希函数计算哈希地址,hashAddr为4,因此44理论
上应该插在该位置,但是该位置已经放了值为4的元素,即发生哈希冲突。
线性探测:从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止。同时由于一些相邻聚集位置连续冲突,可能形成“踩踏”。
··通过哈希函数获取待插入元素在哈希表中的位置
··如果该位置中没有元素则直接插入新元素,如果该位置中有元素发生哈希冲突,使用线性探
测找到下一个空位置,插入新元素
采用闭散列处理哈希冲突时,不能随便物理删除哈希表中已有的元素,若直接删除元素会影响其他
元素的搜索。比如删除元素4,如果直接删除掉,44查找起来可能会受影响。因此线性探测采用标
记的伪删除法来删除一个元素
#pragma once
#include
#include
#include
using namespace std;
enum State
{
EMPTY,
EXIST,
DELETE,
};
template<class K, class V>
struct HashData
{
pair<K, V> _kv;
State _state = EMPTY;
};
template<class K,class V>
class HashTable
{
public:
bool Insert(const pair<K, V>& kv)
{
if(Find(kv.first))
负载因子超过0.7就扩容
//if (_table.size()==0 ||_n * 10 / _table.size() >= 7)
//{
// 1、表为空,扩不上去
// 2、reserv扩容只扩capacity
// //_tables.reserve(_tables.capacity() * 2);
// /*size_t newsize = tables.size() == 0 ? 10 : _tables.size() * 2;
// _tables.resize(newsize);
// //size变了之后,查找的时候,hashi也会跟着变,导致以前的值找不到
// */
// size_t newsize = tables.size() == 0 ? 10 : _tables.size() * 2;
// vector newtables(newsize);
// //遍历旧表,重新映射到新表
// for (auto& data : _tables)
// {
// if (data._state == EXIST)
// {
// //重新算在新表的位置
// size_t hashi = kv.first % _tables.size();
// //线性探测
// size_t i = 1;
// size_t index = hashi;
// while (_tables[hashi]._state == EXIST)
// {
// index == hashi + i;
// index %= _table.size();
// ++i;
// }
// _tables[index]._kv = kv;
// _tables[index]._state = EXIST;
// _n++;
// }
// }
// _tables.swap(newtables);
//负载因子超过0.7就扩容
if (_tables.size()==0 ||_n * 10 / _tables.size() >= 7)
{
size_t newsize = _tables.size() == 0 ? 10 : _tables.size() * 2;
HashTable<K, V> newht;
newht._tables.resize(newsize);
// 遍历旧表,重新映射到新表
for (auto& data : _tables)
{
if (data._state == EXIST)
{
newht.Insert(data._kv);
}
}
_tables.swap(newht._tables);
}
size_t hashi = kv.first % _tables.size();
//线性探测
size_t i = 1;
size_t index = hashi;
while (_tables[hashi]._state == EXIST)
{
index ==hashi+i;
index %= _tables.size();
++i;
}
_tables[index]._kv = kv;
_tables[index]._state = EXIST;
_n++;
return true;
}
HashData<K, V>* Find(const K& key)
{
if (_tables.size() == 0)
{
return nullptr;
}
size_t hashi = key % _tables.size();
//线性探测
size_t i = 1;
size_t index = hashi;
while (_tables[index]._state==EXIST&&_tables[index]._state != EMPTY)
{
if (_tables[index]._kv.first == key)
{
return &_tables[index];
}
index = hashi + i;
index %= _tables.size();
++i;
//如果已经查找了一圈,说明全是EXIST或DELETE
if (index == hashi)
break;
}
return nullptr;
}
bool Erase(const K& key)
{
HashData<K, V>* ret = Find(key);
if (ret)
{
ret->_state = DELETE;
return true;
}
else
return false;
}
private:
vector<HashData<K,V>> _tables;
size_t _n = 0;//存储的数据个数
};
2、二次探测
线性探测的缺陷是产生冲突的数据堆积在一块二,这与其找下一个空位置有关系,因为找空位置的方式就是挨着往后逐个去找,因此二次探测为了避免该问题,找下一个空位置的方法为:Hi=(H0+i2)%m,或者:Hi=(H0+i2)%m。其中:i=1,2,3……,H0是通过散列函数Hash(x)对元素的关键码key进行计算得到的位置,m是表的大小。对于上述插入44引起的哈希冲突,使用二次探测解决后的情况为。
研究表明:当表的长度为质数且表装载因子a不超过0.5时,新的表项一定能够插入,而且任何一个位置
都不会被探查两次。因此只要表中有一半的空位置,就不会存在表满的问题。在搜索时可以不考虑表装
满的情况,但在插入时必须确保表的装载因子a不超过0.5,如果超出必须考虑增容。
因此:闭散列最大的缺陷就是空间利用率比较低,这也是哈希的缺陷。
1、开散列概念
开散列法又叫链地址法(开链法),首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码
归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各链表的头结点存储在哈希表中。
从上图可以看出,开散列中每个桶中放的都是发生哈希冲突的元素
2、开散列的扩容
桶的个数是一定的,随着元素的不断插入,每个桶中元素的个数不断增多,极端情况下,可能会导致一个桶中链表节点非常多,会影响的哈希表的性能,因此在一定条件下需要对哈希表进行增容,那该条件怎么确认呢?开散列最好的情况是:每个哈希桶中刚好挂一个节点,再继续插入元素时,每一次都会发生哈希冲突,因此,在元素个数刚好等于桶的个数时,可以给哈希表增容。也就是
//哈希函数采用处理余数法,被模的key必须为整形才可以处理,此处提供将key转化为整形的方法
template<class K,class V>
struct HashNode
{
HashNode<K, V>* _next;
pair<K, V> _kv;
HashNode(const pair<K, V>& kv)
:_next(nullptr)
,_kv(kv)
{}
};
//整数类型不需要转化
template<class K>
struct HashFunc
{
size_t operator()(const K& key)
{
return key;
}
};
//特化,key为string时,转化为整形
template<>
struct HashFunc<string>
{
//BKDR 避免字母相同但是顺序不同 (ab ba)
size_t operator ()(const string& s)
{
size_t hash = 0;
for (auto ch : s)
{
hash += ch;
hash *= 31;
}
return hash;
}
};
template<class K, class V,class Hash = HashFunc<K>>
class HashTable
{
typedef HashNode<K, V> Node;
public:
/*
函数增删查改等其他函数
*/
private:
vector<Node*> _tables;
size_t _n = 0;//存储有效数据个数
};
size_t GetNextPrime(size_t prime)
{
//SGI
static const __stl_num_primes = 28;
static const unsigned long __stl_prime_list[__stl_num_primes] =
{
53ul, 97ul, 193ul, 389ul, 769ul,
1543ul, 3079ul, 6151ul, 12289ul, 24593ul,
49157ul, 98317ul, 196613ul, 393241ul, 786433ul,
1572869ul, 3145739ul, 6291469ul, 12582917ul, 25165843ul,
50331653ul, 100663319ul, 201326611ul, 402653189ul, 805306457ul,
1610612741ul, 3221225473ul, 4294967291ul
};
size_t i = 0;
for (; i < __stl_num_primes; i++)
{
if (__stl_prime_list[i] > prime)
return __stl_prime_list[i];
}
return __stl_prime_list[i];
}
开散列代码实现
namespace HashBacket
{
template<class K,class V>
struct HashNode
{
HashNode<K, V>* _next;
pair<K, V> _kv;
HashNode(const pair<K, V>& kv)
:_next(nullptr)
,_kv(kv)
{}
};
template<class K>
//默认仿函数,内置类型如int不用传参数直接调用
struct HashFunc
{
size_t operator()(const K& key)
{
return key;
}
};
//特化
template<>
struct HashFunc<string>
{
//BKDR
size_t operator ()(const string& s)
{
size_t hash = 0;
for (auto ch : s)
{
hash += ch;
hash *= 31;
}
return hash;
}
};
template<class K, class V,class Hash = HashFunc<K>>
class HashTable
{
typedef HashNode<K, V> Node;
public:
~HashTable()
{
for (auto& cur : _tables)
{
while (cur)
{
Node* next = cur->_next;
delete cur;
cur = next;
}
cur = nullptr;
}
}
bool Erase(const K& key)
{
Hash hash;
size_t hashi = hash(key) % _tables.size();
Node* prev = nullptr;
Node* cur = _tables[hashi];
while (cur)
{
if (cur->_kv.second == key)
{
if (prev == nullptr)
{
_tables[hashi] = cur->_next;
}
else
{
prev->_next = cur->_next;
}
delete cur;
return true;
}
else
{
prev = cur;
cur = cur->_next;
}
}
}
bool Insert(const pair<K, V>& kv)
{
if (Find(kv.first))
{
return false;
}
//负载因子==1时扩容
if (_n == _tables.size())
{
/*size_t newsize = _table.size() == 0 ? 10 : J_tables.size() * 2;
HashTable newht;
newht.resize(newsize);
for (auto cur : _tables)
{
while (cur)
{
newht.Insert(cur->_kv);
cur = cur->_next;
}
}
_tables.swap(newht._tables);*/
size_t newsize = _tables.size() == 0 ? 10 : _tables.size() * 2;
vector<Node*>newtables(newsize, nullptr);
for (auto& cur : _tables)
{
while (cur)
{
Node* next = cur->_next;
Hash hash;
size_t hashi = hash(kv.first) % _tables.size();
//头插到新表
cur->_next = newtables[hashi];
newtables[hashi] = cur;
cur = next;
}
}
_tables.swap(newtables);
}
Hash hash;
size_t hashi = hash(kv.first) % _tables.size();
//头插
Node* newnode = new Node(kv);
newnode->_next = _tables[hashi];
_tables[hashi] = newnode;
++_n;
//return make_pair(iterator(newnode,this),false);
return true;
}
Node* Find(const K& key)
{
if (_tables.size() == 0)
return nullptr;
Hash hash;
size_t hashi =hash( key) % _tables.size();
Node* cur = _tables[hashi];
while (cur)
{
if (cur->_kv.first == key)
{
return cur;
}
cur = cur->_next;
}
return nullptr;
}
private:
vector<Node*> _tables;
size_t _n = 0;//存储有效数据个数
};
}
1、面试题
给40亿个不重复的无符号整数,没排过序。给一个无符号整数,如何快速判断一个数是否在这40亿个数中。【腾讯】
数据是否在给定的整形数据中,结果是在或者不在,刚好是两种状态,那么可以使用一个二进制比特位来代表数据是否存在的信息,如果二进制比特位为1,代表存在,为0代表不存在。比如:
2、位图概念
所谓位图,就是用每一位来存放某种状态,适用于海量数据,数据无重复的场景。通常是用来判断某个数据存不存在的。
优点:速度快、节省空间
缺点:只能映射整形,其他类型如:浮点数,string等不能存储映射
#pragma once
#include
#include
using namespace std;
template<size_t N>
class bitset
{
public:
bitset()
{
_bits.resize(N / 8+1, 0);
}
void set(size_t x)
{
//计算x映射在char数组的位置
size_t i = x / 8;
size_t j = x % 8;
_bits[i] |= (1 << j);
}
void reset(size_t x)
{
//计算x映射在char数组的位置
size_t i = x / 8;
size_t j = x % 8;
_bits[i] &= ~ (1<< j);
}
bool test(size_t x)
{
size_t i = x / 8;
size_t j = x % 8;
return _bits[i] & (1 << j);
}
private:
vector<char> _bits;
};
void test_bitset1()
{
bitset<100> bs;
bs.set(10);
bs.set(11);
bs.set(15);
cout << bs.test(10) << endl;
cout << bs.test(15) << endl;
bs.reset(10);
cout << bs.test(10) << endl;
cout << bs.test(15) << endl;
bs.reset(10);
bs.reset(15);
cout << bs.test(10) << endl;
cout << bs.test(15) << endl;
}
我们在使用新闻客户端看新闻时,他会给我们不停的推荐新的内容,它每次推荐时要去重,去掉那些已经看过的内容。问题来了,新闻客户端推荐系统如何实现推送去重的?用服务器纪记录了用户看过的所有历史记录,当推荐系统推荐新闻时会从每个用户的历史记录里面进行筛选,过滤掉那些已经存在的记录。如何快速查找呢?
布隆过滤器是由布隆(Burton Howard Bloom)在1970年提出的一种紧凑型的、比较巧妙的概率型数据结构,特点是高效地插入和查询,可以用来告诉你“某样东西一定不存在或者可能存在”,它是用多个哈希函数,将一个数据映射到位图结构中。此种方式不仅可以提升查询效率,也可以节省大量的内存空间。
void set(const K& key)
{
size_t len = N * _X;
size_t hash1 = Hash1()(key) % len;
_bs.set(hash1);
size_t hash2 = Hash2()(key) % len;
_bs.set(hash2);
size_t hash3 = Hash3()(key) % len;
_bs.set(hash3);
//cout << hash1 << " " << hash2 << " " << hash3 << endl;
}
** 注意:布隆过滤器如果说某个元素不存在时,该元素一定不存在,如果该元素存在时,该元素可能存在,因为有些哈希函数存在一定的误判**
比如:在布隆过滤器中查找“alibaba”时,假设3个哈希函数计算的哈希值为:1,3,7,刚好和其他元素的比特位重叠,此时布隆过滤器告诉该元素不存在,但其实该元素是不存在的。
bool test(const K& key)
{
size_t len = N * _X;
size_t hash1 = Hash1()(key) % len;
if(!_bs.test(hash1))
{
return false;
}
size_t hash2 = Hash2()(key) % len;
if (!_bs.test(hash2))
{
return false;
}
size_t hash3 = Hash3()(key) % len;
if (!_bs.test(hash3))
{
return false;
}
return true;
//在 --不准确的、存在误判、可能是别的数映射的
//不在 --准确的、只要有一个不存在,就一定说明不存在
}
布隆过滤器不能直接支持删除工作,因为再删除一个元素时,可能会影响其他元素
比如:删除上图中“tencent”元素,如果直接将该元素所对应的二进制比特位置0,“baidu”元素也被删除了,因为这两个元素在多个哈希函数计算出的比特位上刚好有重叠。
一种支持删除的方法:将布隆过滤器中的每个比特位扩展成一个小的计数器,插入元素时给k个计数器(k个哈希函数计算出的哈希地址)加一,删除元素时,给k个计数器减一,通过多占用几倍存储空间的代价来增加删除操作。
缺陷:
struct BKDRHash
{
size_t operator()(const string& s)
{
size_t hash = 0;
for (auto ch : s)
{
hash += ch;
hash *= 31;
}
return hash;
}
};
struct APHash
{
size_t operator()(const string& s)
{
size_t hash = 0;
for (long i = 0; i < s.size(); i++)
{
size_t ch = s[i];
if ((i & 1) == 0)
{
hash ^= ((hash << 7) ^ ch ^ (hash >> 3));
}
else
{
hash ^= (~((hash << 11) ^ ch ^ (hash >> 5)));
}
}
return hash;
}
};
struct DJBHash
{
size_t operator()(const string& s)
{
size_t hash = 5381;
for (auto ch : s)
{
hash += (hash << 5) + ch;
}
return hash;
}
};
template<size_t N, class K = string,
class Hash1 =BKDRHash ,
class Hash2 = APHash,
class Hash3 = DJBHash>
class BloomFilter
{
public:
void set(const K& key)
{
size_t len = N * _X;
size_t hash1 = Hash1()(key) % len;
_bs.set(hash1);
size_t hash2 = Hash2()(key) % len;
_bs.set(hash2);
size_t hash3 = Hash3()(key) % len;
_bs.set(hash3);
//cout << hash1 << " " << hash2 << " " << hash3 << endl;
}
bool test(const K& key)
{
size_t len = N * _X;
size_t hash1 = Hash1()(key) % len;
if(!_bs.test(hash1))
{
return false;
}
size_t hash2 = Hash2()(key) % len;
if (!_bs.test(hash2))
{
return false;
}
size_t hash3 = Hash3()(key) % len;
if (!_bs.test(hash3))
{
return false;
}
return true;
//在 --不准确的、存在误判、可能是别的数映射的
//不在 --准确的、只要有一个不存在,就一定说明不存在
}
private:
static const size_t _X= 4;
bitset<N * _X> _bs;
};
给一个超过100G大小的log file, log中存着IP地址, 设计算法找到出现次数最多的IP地址? 与上题条件相同,
如何找到top K的IP?如何直接用Linux系统命令实现?
解决思路:用两个单位图,用两个相同位置的比特位,来反映出现次数(00–0次,01–一次,10二次及以上)
template <size_t N>
class twobitset
{
public:
void set(size_t x)
{
//00->01
if (_bs1.test(x) == false
&& _bs2.test(x)== false)
{
_bs2.set(x);
}
//01->10
else if (_bs1.test(x) == false
&& _bs2.test(x) == true)
{
_bs1.set(x);
_bs2.reset(x);
}
//10
}
void Print()
{
for (size_t i = 0; i < N; i++)
{
if (_bs2.test(i) == 1)
{
cout << i<<" ";
}
}
}
private:
bitset<N> _bs1;
bitset<N> _bs2;
};
void test_twobitset1()
{
int a[] = { 1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,0 };
twobitset<1000> bs;
for (auto e : a)
{
bs.set(e);
}
bs.Print();
}
与上面位图应用1同理(00–零次,01一次,10两次,11–三次及以上)
template <size_t N>
class twobitset
{
public:
void set(size_t x)
{
//00->01
if (_bs1.test(x) == false
&& _bs2.test(x)== false)
{
_bs2.set(x);
}
//01->10
else if (_bs1.test(x) == false
&& _bs2.test(x) == true)
{
_bs1.set(x);
_bs2.reset(x);
}
//10->11
else if (_bs1.test(x) == true
&& _bs2.test(x) == false)
{
_bs2.set(x);
}
}
void Print()
{
for (size_t i = 0; i < N; i++)
{
if (_bs2.test(i) == 1)
{
cout << i<<" ";
}
}
}
private:
bitset<N> _bs1;
bitset<N> _bs2;
};
void test_twobitset1()
{
int a[] = { 1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,0 };
twobitset<1000> bs;
for (auto e : a)
{
bs.set(e);
}
bs.Print();
}