使用位向量的场景
假设某大学有20000
名学生,今年6月份安排对自愿接种新冠疫苗的学生接种疫苗,对所有学生有无接种情况进行分类并排序,要求使用效率最高,在C++中可使用位集合(向量)实现:
1.20000个学生表示为N
,每个学生编号号唯一,具有两种状态0或1,分别表示有无接种疫苗
2.对于一个unsigned short
数据类型,具有16位,每一位可表示0或1,那么可用N/16+1=1251
个unsigned short
可表示所有可能取值0或1,此时可映射为二进位数组A[1251]
3.接下来就是对A[k]
(其中k属于[0,1250]
)进行位运算,如对第1680
号学生有无接种,只需设置A[1680/16]
中第0
位(有16位,位编号0-15
)置为0或1即可
位向量使用规则
1.N
个元素只具有2种不同的状态0或1,表示是否存在于二进位数组A[k]
中
2.元素个数表示为N
,确保N个元素中每个元素大小不超过N
3.每个元素具有唯一的编号,且元素的值必须为整数
实现过程
/*
计算按位取反的一般方法:
1.将待计算的数用2进制表示,位数最少为可以表示出当前数的绝对值的二进制位数加1(多1位符号位)。
也就是将9表示为01001,其中最左面的0是符号位,0为正,1为负。
2.将每个二进制位取反,及如果是1,结果为0,反之结果为1。取反后结果为10110
3.将结果看做是有符号数,转为十进制即可。最左面的一位是符号位,1代表是负的。在计算机中负数是补码表示的,有符号数10110转为10进制即-10
计算按位取反的简便算法
用-1减去待取反的数即为按位取反的结果:-1-9=-10
使用位向量排序输入数据作如下假定:
(1)都是非负整数,(2)每个整数最多出现一次 (3)最大整数小于n
*/
#include <iostream>
using namespace std;
class BitSet {
private:
//集合大小
int setSize;
//位数组大小
int vectorSize;
//位向量
unsigned short *bitVector;
public:
// sz表示集合大小,添加的元素限制范围:[0,sz-1]
BitSet(int sz);
//使用数组构造
BitSet(int arr[], int arrSize, int defaultSetSize);
//设置元素x的状态为v,元素x范围:[0,vectorSize-1],v取值为状态只能为0或1
void putMember(const int x, unsigned short v);
//取元素x的状态位,只能为0或1
unsigned short getMember(const int x);
//添加元素x,即设置x的状态位为1
bool addMember(const int x);
//删除元素x,即设置x的状态位为0
//对于正数,如5, 计算机内short int可表示16位,存储为:00000000 00000101
//对于取反操作, 如~5,先表示为 00000000 00000101,全部取反:11111111 11111010
//最高位符号位1表示负数,保持不变,其余各位取反,再对结果+1
//求11111111 11111010补码为 10000000 00000110,结果为 -6
bool delMember(const int x);
//获取集合中元素的个数
int getSetSize() { return setSize; }
int getVectorSize() { return vectorSize; }
//并集,p1 + p2
friend BitSet unionSet(BitSet &p1, BitSet &p2);
//交集,p1 * p2
friend BitSet intersectionSet(BitSet &p1, BitSet &p2);
//差集,p1 - p2
friend BitSet subtractionSet(BitSet &p1, BitSet &p2);
//集合判断是否相等
bool equal(BitSet &bitSet);
//判断this是否为bitSet的子集,如果A是B的子集,则分别对应1->1,0->1,0->0,但不能1->0
bool isSubset(BitSet &bitSet);
};
//判断子集
bool BitSet::isSubset(BitSet &bitSet) {
if (bitSet.vectorSize != vectorSize) {
return false;
}
//当this的其中一位为1,而bitSet对应的位为0,说明this不是子集
for(int i=0;i<setSize;i++){
if(getMember(i) & !bitSet.getMember(i)){
return false;
}
}
return true;
}
//判断是否相等
bool BitSet::equal(BitSet &bitSet) {
if (bitSet.vectorSize != vectorSize) {
return false;
}
for (int k = 0; k < vectorSize; k++) {
if (bitVector[k] != bitSet.bitVector[k]) {
return false;
}
}
return true;
}
//并集
BitSet unionSet(BitSet &p1, BitSet &p2) {
if (p1.vectorSize != p2.vectorSize) {
cout << "BitSet容量必须相等" << endl;
exit(0);
}
int nsetSize = p1.setSize;
BitSet nbitSet(nsetSize);
int nvectorSize = p1.getVectorSize();
for (int i = 0; i < nvectorSize; i++) {
nbitSet.bitVector[i] = p1.bitVector[i] | p2.bitVector[i];
}
return nbitSet;
}
//交集
BitSet intersectionSet(BitSet &p1, BitSet &p2) {
if (p1.vectorSize != p2.vectorSize) {
cout << "BitSet容量必须相等" << endl;
exit(0);
}
int nsetSize = p1.setSize;
BitSet nbitSet(nsetSize);
int nvectorSize = p1.getVectorSize();
for (int i = 0; i < nvectorSize; i++) {
nbitSet.bitVector[i] = p1.bitVector[i] & p2.bitVector[i];
}
return nbitSet;
}
//差集
BitSet subtractionSet(BitSet &p1, BitSet &p2) {
if (p1.vectorSize != p2.vectorSize) {
cout << "BitSet容量必须相等" << endl;
exit(0);
}
int nsetSize = p1.setSize;
BitSet nbitSet(nsetSize);
int nvectorSize = p1.getVectorSize();
for (int i = 0; i < nvectorSize; i++) {
nbitSet.bitVector[i] = p1.bitVector[i] & (~p2.bitVector[i]);
}
return nbitSet;
}
// bitVector[0]--表示第1~16个数,[0,15]
// bitVector[1]--表示第17~32个数,[16,31]
// bitVector[2]--表示第33~48个数,[32,47]
// bitVector[M]--表示第(M*16)+1~(M+1)*16,[M*16,(M+1)*16-1]
BitSet::BitSet(int sz) {
//当sz<15,sz>>4结果为0,显然不符合要求,或者使用: vectorSize = (sz>>4)+1;
vectorSize = (sz + 15) >> 4;
setSize = sz;
bitVector = new unsigned short[vectorSize];
for (int i = 0; i < vectorSize; i++) {
bitVector[i] = 0;
}
}
//直接构造一个BitSet
BitSet::BitSet(int arr[], int arrSize, int defaultSetSize) {
vectorSize = (defaultSetSize + 15) >> 4;
setSize = defaultSetSize;
bitVector = new unsigned short[vectorSize];
for (int i = 0; i < vectorSize; i++) {
bitVector[i] = 0;
}
for (int k = 0; k < arrSize; k++) {
addMember(arr[k]);
}
}
// 或这种方法
// void BitSet::putMember(const int x,unsigned short v){
// int ad = x/16;
// int id = x%16;
// unsigned short elem = bitVector[ad];
// unsigned short temp = elem>>(15-id);
// elem = elem<<(id+1);
// if(temp%2==0 && v==1){
// temp = temp+1;
// }else if(temp%2==1 && v==0){
// temp = temp - 1;
// }
// bitVector[ad] = temp<<(15-id)|(elem>>(id+1));
// }
void BitSet::putMember(const int x, unsigned short v) {
int ad = x / 16;
int k = x % 16;
//设置bitVector[ad]第k位设置状态为v,k从0开始
if (v) {
//将第k位设置为1
bitVector[ad] = bitVector[ad] | (1 << k);
} else {
//将第k位设置为0,例如:当k=3,(~(1<<k))为(~8)=>-9,计算机内存储(补码形式)为 11111111 11110111,此时第k位为0
bitVector[ad] = bitVector[ad] & (~(1 << k));
}
}
unsigned short BitSet::getMember(const int x) {
//判断是否越界
if (x < 0 || x >= setSize) {
return 0;
}
int ad = x / 16;
int id = x % 16;
unsigned short elem = bitVector[ad];
//取出位索引id指向的位,并且返回,其结果只能为0或1
return (elem >> id) & 1;
// return (elem>>(15-id))%2;
}
bool BitSet::addMember(const int x) {
if (x < 0 || x >= setSize) {
return false;
}
int state = getMember(x);
if (!state) {
putMember(x, 1);
return true;
}
return false;
}
bool BitSet::delMember(const int x) {
if (x < 0 || x >= setSize) {
return 0;
}
cout << "bitVector[0]=" << bitVector[0] << endl;
int state = getMember(x);
if (state) {
putMember(x, 0);
return true;
}
return false;
}
//添加测试
void addTest() {
BitSet bitSet = BitSet(100);
int arr[] = {0, 1, 2, 5, 10, 49, 50, 99, 100, 105, 110, 115, 116, 117, 133};
int len = sizeof(arr) / sizeof(int);
int res = 0;
for (int i = 0; i < len; i++) {
res = bitSet.addMember(arr[i]);
if (res) {
cout << arr[i] << "--" << res << endl;
}
}
cout << endl;
for (int i = 0; i < 100; i++) {
int x = i;
res = bitSet.getMember(x);
if (res) {
cout << x << "-->" << res << endl;
}
}
}
//----测试部分----
void setTest() {
int defaultSetSize = 50;
int arr1[] = {1, 2, 3,4};
int size1 = sizeof(arr1) / sizeof(int);
BitSet bitSet1(arr1, size1, defaultSetSize);
int arr2[] = {1, 2, 3};
int size2 = sizeof(arr2) / sizeof(int);
BitSet bitSet2(arr2, size2, defaultSetSize);
//并集
// BitSet newSet = unionSet(bitSet1,bitSet2);
//交集
// BitSet newSet = intersectionSet(bitSet1, bitSet2);
//差集
BitSet newSet = subtractionSet(bitSet1, bitSet2);
for (int k = 0; k < newSet.getSetSize(); k++) {
int res = newSet.getMember(k);
if (res) {
cout << k << "-->" << res << endl;
}
}
cout<<"子集:"<<bitSet2.isSubset(bitSet1)<<endl;
}
void delTest() {
int arr[] = {1, 2, 3};
int defaultSize = 50;
int len = sizeof(arr) / sizeof(int);
BitSet bitSet = BitSet(arr, len, defaultSize);
bitSet.delMember(3);
bitSet.delMember(7);
for (int i = 0; i < len; i++) {
int res = bitSet.getMember(arr[i]);
if (res) {
cout << arr[i] << "->";
}
}
}
int main() {
// addTest();
// delTest();
setTest();
}
位向量性能
N
个元素,若使用M
位的数据类型表示
-
时间复杂度:
N
个元素,通过位运算可直接定位到其值为0或1,时间复杂度为O(1)
-
空间复杂度:二进制数组只需要
N/M+1
的空间,即O(1)
总之,位向量表示集合数据类型,对于元素分布比较集中,且极差较小的情况下,具有优越的性能
以上为个人总结分析,如有问题欢迎批评指正!