C++使用位向量实现集合数据类型

使用位向量的场景

假设某大学有20000名学生,今年6月份安排对自愿接种新冠疫苗的学生接种疫苗,对所有学生有无接种情况进行分类并排序,要求使用效率最高,在C++中可使用位集合(向量)实现:

1.20000个学生表示为N,每个学生编号号唯一,具有两种状态0或1,分别表示有无接种疫苗

2.对于一个unsigned short数据类型,具有16位,每一位可表示0或1,那么可用N/16+1=1251unsigned 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)

总之,位向量表示集合数据类型,对于元素分布比较集中,且极差较小的情况下,具有优越的性能

以上为个人总结分析,如有问题欢迎批评指正!

目 录 一、课程设计目的 •••••••••••••••••••••••••••••••••••••••••••••••• 2 1.1、实现集合 •••••••••••••••••••••••••••••••••••••••••••••••• 2 二、课程设计内容 •••••••••••••••••••••••••••••••••••••••••••••••• 2 2.1、实现集合 •••••••••••••••••••••••••••••••••••••••••••••••• 2 三、数据结构分析 •••••••••••••••••••••••••••••••••••••••••••••••• 2 3.1、实现集合 •••••••••••••••••••••••••••••••••••••••••••••••• 2 3.1.1 集合的相等运算 •••••••••••••••••••••••••••••••••••••••• 2 3.1.2 集合的并运算 ••••••••••••••••••••••••••••••••••••••••• 3 3.1.3 集合的包含、差运算 •••••••••••••••••••••••••••••••••••• 3 四、算法分析 ••••••••••••••••••••••••••••••••••••••••••••••••••••• 3 4.1、实现集合 •••••••••••••••••••••••••••••••••••••••••••••••• 4 4.1.1 集合的相等运算 •••••••••••••••••••••••••••••••••••••••• 4 4.1.2 集合的并运算 ••••••••••••••••••••••••••••••••••••••••• 4 4.1.3 集合的包含、差运算 •••••••••••••••••••••••••••••••••••• 5 五、代码分析 ••••••••••••••••••••••••••••••••••••••••••••••••••••• 5 5.1、实现集合 •••••••••••••••••••••••••••••••••••••••••••••••• 5 5.1.1 集合的相等运算 •••••••••••••••••••••••••••••••••••••••• 5 5.1.2 集合的并运算 ••••••••••••••••••••••••••••••••••••••••• 6 5.1.3 集合的包含、差运算 •••••••••••••••••••••••••••••••••••• 7 六、问题分析 ••••••••••••••••••••••••••••••••••••••••••••••••••••• 9 6.1、实现集合 •••••••••••••••••••••••••••••••••••••••••••••••• 9 七、运行结果 ••••••••••••••••••••••••••••••••••••••••••••••••••••• 10 7.1、实现集合 •••••••••••••••••••••••••••••••••••••••••••••••• 10
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值