排序算法之 BitMap算法 原理及Go实现

1.BitMap

BitMap是一种排序算法,他的特点是速度快,节省容量。可以对整数型型数据进行排序,同时也可以应用于文本去重,排序前先要对文本进行MD5操作。

  • 优点
    1. 节省空间,对于一个字节进行排序,只要一个字节的空间。
    2. 运行效率高
  • 缺点:
    1. 重复数据只能记录一次。

假设要对30 亿个 数据 进行排序,每个整数如果占 4 个字节 那么 30亿的数据就要占用 11 个G,对于一些配置较差的机器来说要加载进内存就不够用了,BitMaps可以很好的解决这个问题。

如果要对 1 - 8 的数排序 要怎么办呢。

在这里插入图片描述
只要用一个 字节 表示 8个数 如上图 如果要记录 4 那么只要在第四位设置为1。
所以如果要对四排序那么久在第四位设置为1。一个字节能表示成 8 位,2 个字节 就能表示16 位的大小了。

bitsmap := make([]byte,2,2)

上面 开辟了2个字节 也就能表示16 个数字。
在这里插入图片描述

如果 要对 第4位 第12位 和 第14位 排序 该怎么做呢?
首先要确定 每个块的值域。 假设 第n个byte 他的值域是多少呢 ?
8 ( n − 1 ) < = x < = 8 n n ∈ ( 1 , 无 穷 ) 8(n-1) <= x <= 8n n∈(1,无穷) 8(n1)<=x<=8nn(1,) 假设 第一个字节 就是 1 ~8 的范围。 如果有2个字节就是
8 ~ 16 的范围

那么给定 一个数 7 怎么计算它属于哪个第几个字节呢?可以用求余操作。
7 % 8 = 商:0 余: 7 那么 前面就表示他在第几个字节 后面就是第几个字节的第几个位。

写成代码:

func setbit(bm []byte,i int){
	i  -= 1 //数组索引是从0 开始的 传入是从1开始的
	p := i / 8 //计算商表示在第几个字节 等价也可以成 p := i >> 3
	l := i % 8 //计算余数 表示位
	bytem := bitsmap[p] //这就取出8位了
	fmt.Printf("改变前状态:%08b 第 %d 位要置为1 \n", bytem,l + 1)
	bytem = bytem ^ 1 << l //取异或操作
	fmt.Printf("改变后    :%08b\n", bytem)
	bm[p] = bytem   //保存更改
	}

在这里插入图片描述

bytem ^ 1 << l

这里要 ^ 是取 异或的操作 假设00000000 要设置第 1位为 1 但是又不修改其他位数呢
可以先 1 左移 << 1 1位 变成 10 然后用 异或操作 异或的定义 如果2个操作数不相同则为1.
这样 00000000 和 00000010 操作 变成了00000010 。

这里实现了 添加数。 接下来要实现 获取这个 数 是否在bitmaps中已进存在。
和上面是一样的。

这么理解吧 00000010 假设 要插入的数 已经 存在了 那么再一次 的进行 异或操作,
在这里插入图片描述
00000010 XOR 00000010 一旦异或 就会被置为 0 这样的话 就变小了。
所以 逻辑就是拿 原来的bitmasp 再 做一次 插入 如果不存在 置入1 一定会变大
如果原先 那一位 就存在的话 就一定会变小 比较一下

如果 原bitbyte > 插入一个数的bitbyte 就说明是已经存在了

func getbit(bm []byte,i int) bool{
	i  -= 1
	p := i / 8
	l := i % 8
	bytem := bm[p] //这就取出8位了
	return bytem > bytem ^ 1 << l //如果原来那一位为1 再一次异或 一定为 0 那么相比原来就变小了 
}

测试代码:

	bitsmap := make([]byte,2,2)
	setbit(bitsmap,8)
	setbit(bitsmap,16)
	fmt.Println(getbit(bitsmap,8))
	fmt.Println(getbit(bitsmap,2))
	fmt.Println(iter(bitsmap))

在这里插入图片描述

接下来 再来实现以下把 所有的内容从大大小输出吧,这其实就很简单了,就遍历数组。就行了。

func iter(bm []byte) []int{
	list := make([]int,0,len(bm))
	//2个字节 总共可以表达 16的大小 遍历16位
	for i:=1;i<=len(bm) * 8;i++ { //这里字节 要乘以 8=16bit
		val := getbit(bm,i)
		if val {
			list = append(list, i)
		}
	}
	return list

}

在这里插入图片描述
这里可以看到结果是正确的。

下面再实现 一个 删除存在的数字,其实把上面的方法组合下就可以了。

  1. 查找是否存在
  2. 存在 再执行一次Set 异或 就变为0 了
func UnSet(bm []byte,i int){
	fmt.Println("删除数字:",i)
	val := getbit(bm,i)//判断存在吗
	if val {
		setbit(bm,i)//再一次插入就是删除了	}
}
func main(){

	bitsmap := make([]byte,2,2)
	setbit(bitsmap,8)
	setbit(bitsmap,16)
	fmt.Println(getbit(bitsmap,8))
	fmt.Println(getbit(bitsmap,2))
	fmt.Println(iter(bitsmap))
	UnSet(bitsmap,8) //删除
	fmt.Println(iter(bitsmap))
}

在这里插入图片描述
可以正确删除,到这里BitMap核心的功能就实现了,实际上上面的是精简版,没有判断范围是否超出表示范围,而且 求 余 可以用 n & 8 也是一样的 。
其实很简单 就是用到了求余,求商和异或这三点知识。

上面使用了 byte 表示 每个字节只能 8个大小 ,但 用一个int64记录呢,是不是可以做到 一个int如果64个bit 可以记录 64位呢 ?原理其实是一样的 。

下面一个完整版

const (
	//这里 ptrBits 可以知道int 在系统中表示的大小byte大小 (^uintptr(0) >> 63) 在 32位 为 0 在 64位 为 1
	//ptrBits = 32 << uint( (^uintptr(0) >> 63)) //^uintptr(0) 表示 64位0 取反  也就是 2 << 63 -1 = 18446744073709551615
	减1是因为 从0 开始
	//byteModeMask = ptrBits - 1 //能记录的长度 上面 ptrBits 是 64 这里 从0 开始 所以减1
	//byteShift =(1 << 7 + ptrBits) >> 6	//  8位 / 2 << 7 64 位就是 2 << 63
	byteModeMask = 7	//2 ^3 -1
	byteShift = 3 // 2 ^ 3
)
type BitSet interface {
	Get(i int)bool	//提取数据
	Set(i int)		//设置数据
	Unset(i int)	//取消设置
	SetBool(i int,b bool)//设置
}
type Bytes [] byte //指针 可以指向 表示 2 << 63 的大小整数

func NewBytes(numBits int) Bytes{
	// numBits 范围,byteModeMask 起始  如果 numBits = 0    byteModeMask >> byteShift 63 >> 6 = 0
	return make(Bytes,(numBits+ byteModeMask) >> byteShift)
}

//提取数据
func (p Bytes)Get(i int)bool{
	return p[uint(i) >> byteShift] & (1 << (uint(i) & byteModeMask)) != 0

}

//提取数据
func (p Bytes)Set(i int){
	// (uint(i)&byteModeMask  10  % 63 等价于 i(10) & byteModeMask(63)
	//uint(i) >> byteShift 这个等价于 10 / (1 << 63)  计算第几个uintptr
	//|= 一位一位的取或运算
	//所以下面代码 就是对  一个 64位0101的数 对指定为设置为1
	//下面 用|= 和 ^= 是一样的 区别是 如果对同一个数添加2次 用^=2次 就会成为0
	p[uint(i) >> byteShift] |= 1 << (uint(i)&byteModeMask)
}


func (p Bytes)UnSet(i int){
	// 001000 001000 &^= 如果右侧是0,则左侧数保持不变 如果右侧是1,则左侧数一定清零 所以会被清零
	p[uint(i) >> byteShift] &^= 1 << (uint(i)&byteModeMask)
}


func (p Bytes)SetBool(i int,b bool){
	if b{
		p.Set(i)
	}
	p.UnSet(i)
}
//增长数据
func (p * Bytes)Grow (numBits int){
	ptrs := *p
	//等价于 10 + 8  /  8
	targetlen := (numBits + byteModeMask) >> byteShift
	//计算出需要多少个容纳 更搭的数
	missing := targetlen - len(ptrs)
	if missing >0 && missing <= targetlen{
		//添加一个新的 uint64
		*p = append(ptrs,make(Bytes,missing)...)
	}
}
func main(){
	p :=NewBytes(10)
	p.Set(11)
	p.Set(14)
	p.Grow(31)
	fmt.Println(p.Get(11))
	fmt.Println(p.Get(31))
}
位运算总结

在数字没有溢出的前提下,对于正数和负数,左移一位都相当于乘以2的1次方,左移n位就相当于乘以2的n次方,右移一位相当于除2,右移n位相当于除以2的n次方。
<< 左移,相当于乘以2的n次方,例如:1<<6 相当于1×64=64,3<<4 相当于3×16=48
, >> 右移,相当于除以2的n次方,例如:64>>3 相当于64÷8=8
^ 异或,相当于求余数,例如:48^32 相当于 48%32=16
求余 : a % b = a &b

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
BITMAP调度算法是一种常用的内存管理算法,用于管理分配和释放内存块。下面是一个使用C语言实现BITMAP调度算法的示例代码: ```c #include <stdio.h> #include <stdlib.h> #include <stdbool.h> #define MAX_BLOCKS 1024 #define BLOCK_SIZE 4096 typedef struct { unsigned char* bitmap; unsigned char* memory; } BitmapScheduler; BitmapScheduler* createScheduler() { BitmapScheduler* scheduler = (BitmapScheduler*)malloc(sizeof(BitmapScheduler)); scheduler->bitmap = (unsigned char*)calloc(MAX_BLOCKS / 8, sizeof(unsigned char)); scheduler->memory = (unsigned char*)malloc(MAX_BLOCKS * BLOCK_SIZE * sizeof(unsigned char)); return scheduler; } void destroyScheduler(BitmapScheduler* scheduler) { free(scheduler->bitmap); free(scheduler->memory); free(scheduler); } void* allocateBlock(BitmapScheduler* scheduler) { for (int i = 0; i < MAX_BLOCKS; i++) { int byteIndex = i / 8; int bitIndex = i % 8; if ((scheduler->bitmap[byteIndex] & (1 << bitIndex)) == 0) { scheduler->bitmap[byteIndex] |= (1 << bitIndex); return scheduler->memory + (i * BLOCK_SIZE); } } return NULL; } void freeBlock(BitmapScheduler* scheduler, void* block) { int blockIndex = ((unsigned char*)block - scheduler->memory) / BLOCK_SIZE; int byteIndex = blockIndex / 8; int bitIndex = blockIndex % 8; scheduler->bitmap[byteIndex] &= ~(1 << bitIndex); } void printBitmap(BitmapScheduler* scheduler) { printf("Bitmap:\n"); for (int i = 0; i < MAX_BLOCKS; i++) { int byteIndex = i / 8; int bitIndex = i % 8; bool allocated = (scheduler->bitmap[byteIndex] & (1 << bitIndex)) != 0; printf("%d ", allocated); if ((i + 1) % 32 == 0) { printf("\n"); } } } int main() { BitmapScheduler* scheduler = createScheduler(); // 分配内存块 void* block1 = allocateBlock(scheduler); void* block2 = allocateBlock(scheduler); void* block3 = allocateBlock(scheduler); // 释放内存块 freeBlock(scheduler, block2); // 打印位图 printBitmap(scheduler); destroyScheduler(scheduler); return 0; } ``` 这个示例代码实现BITMAP调度算法的内存分配和释放功能。通过`allocateBlock`函数可以分配一个内存块,通过`freeBlock`函数可以释放一个内存块。`printBitmap`函数用于打印当前的位图情况。 希望对你有帮助!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值