再看不懂BitMap算法,我请你吃饭(一)

1、基础

在Java语言中,一个int类型变量占用4Byte,即32Bit内存空间。
提问:10亿个int类型变量,需要占用多少内存空间?
回答:(10亿 * 4) ÷ (1024 * 1024 * 1024) ≈ 3.73G
如果要对10亿个,不重复的,int类型变量进行排序,将至少需要3.73G内存。
还有更好的办法吗?BitMap算法登场。

2、做个游戏

画8个相邻的小正方形,表示1Byte,即8Bit,给它起个名字叫bitMapArr[0],在它的下方同样画8个小正方形,起名叫bitMapArr[1]。

每个正方形内有一个灯泡,它只能是亮着的(用1表示),或灭着的(用0表示)。

从右至左,从上至下,依次为每个正方形编号:0、1、2 。。。,见下图:
在这里插入图片描述

你发现了吗?这些正方形下面的序号,“歪打正着”,恰好可以表示0和正整数。就上图而言,仅仅2个Byte就能表示0到15这16个整数。正常情况下,需要16*4,即64个Byte。

我在最初接触BitMap算法时,一度被正方形里的值,即0或1,和正方形下面的序号搞晕了。通过这个小游戏可以很形象地说清楚他们的区别了。

3、排序

假设要对3、1、2、7这几个数进行排序。如果能像下面描述的那样操作,不就达到排序的效果了吗?

  • 依次将序号为3、1、2、7的正方形里的灯点亮,即赋1。其余正方形里的灯默认为熄灭,即赋0。
  • 从右到左,从上到下,遍历这两排正方形,仅把亮灯的正方形下面的序号依次记录下来,就是排序后的结果。
4、Java代码
/**
 * 代码改编自:https://www.cnblogs.com/zldmy/p/11565991.html
 */
public class BitMapV1 {
    /**
     * 字节数组,即上图中若干8个相邻的小正方形
     */
    private byte[] bitMapArr;

    /**
     * 为描述方便,假设要对若干个整数进行排序。
     * 此处,capacity可以理解为,待排序的若干个整数中,最大的那个整数。
     * 举例:对1、9这两个数排序,capacity应该赋值为9,即最大值,而不是2,即有2个整数待排序
     */
    private int capacity;

    public BitMapV1(int capacity) {
        this.capacity = capacity;
        // 右移3位相当于除以2^3,即除以8,下面语句等效于capacity/8 + 1
        int len = (capacity >> 3) + 1;
        System.out.println("### 字节数组的大小=" + len);
        bitMapArr = new byte[len];
    }

    /**
     * 将某个整数添加到字节数组中
     * 形象地说,就是已知一个“序号”,把对应的正方形里的灯点亮
     */
    public void add(int num) {
        // num这个数,存在于第0排,还是第1排
        int index = num >> 3;
        // num这个数,在某排的,从右边数,第几个“序号”
        int position = num & 0x07;
        // 将“序号”对应的正方形里的灯点亮,即赋值为1
        bitMapArr[index] |= 1 << position;
    }

    /**
     * 是add(int num)方法的逆过程
     * 形象地说,就是已知一个“序号”,把对应的正方形里的灯熄灭
     */
    public void clear(int num){
        int index = num >> 3;
        if (index >= bitMapArr.length) {
            return;
        }
        int position = num & 0x07;
        bitMapArr[index] &= ~(1 << position);
    }

    /**
     * num这个数,是否存在于某个小正方形对应的“序号”
     */
    public boolean isExist(int num){
        int index = num >> 3;

        if (index >= bitMapArr.length) {
            return false;
        }
        
        int position = num & 0x07;
        int result = bitMapArr[index] & 1 << position;
        return result != 0;
    }

    public byte[] getBitMapArr() {
        return bitMapArr;
    }

    /**
     * 以二进制字符串形式表示一个byte
     * 形象地说,就是亮灯的显示为1,熄灭的显示为0
     */
    public static String getBit(byte by){
        StringBuffer sb = new StringBuffer();
        sb.append((by>>7)&0x1)
                .append((by>>6)&0x1)
                .append((by>>5)&0x1)
                .append((by>>4)&0x1)
                .append((by>>3)&0x1)
                .append((by>>2)&0x1)
                .append((by>>1)&0x1)
                .append((by>>0)&0x1);
        return sb.toString();
    }

    /**
     * 打印整个字节数组
     * 形象地说,就是亮灯的显示为1,熄灭的显示为0
     */
    public void print() {
        for (byte b : bitMapArr) {
            System.out.print(getBit(b));
            System.out.println();
        }
    }
}
import org.junit.Assert;
import org.junit.Test;

public class BitMapV1Test {

    /**
     * 假设要对3、1、2、7进行排序
     */
    @Test
    public void test_01() {
        System.out.println("### 待排序:3 1 2 7");
        // 7是指待排序中的最大值
        BitMapV1 bitMapV1 = new BitMapV1(7);
        int length = bitMapV1.getBitMapArr().length;
        // 用脚指头算,也知道1个Byte,即8Bit,就够用了
        Assert.assertEquals(1, length);

        bitMapV1.add(3);
        bitMapV1.print();

        bitMapV1.add(1);
        bitMapV1.print();

        bitMapV1.add(2);
        bitMapV1.print();

        bitMapV1.add(7);
        bitMapV1.print();

        // 看看哪些灯亮着,把它们的序号依次记录下来,即排序完成后的结果
        System.out.print("### 排序后的结果:");
        for (int i = 0; i < 8; i++) {
            if (bitMapV1.isExist(i)) {
                System.out.print(i + " ");
            }
        }

        System.out.println();

        // 把上面点亮的灯,逐一熄灭
        System.out.println("### 把上面点亮的灯,逐一熄灭:");
        bitMapV1.clear(3);
        bitMapV1.print();

        bitMapV1.clear(1);
        bitMapV1.print();

        bitMapV1.clear(2);
        bitMapV1.print();

        bitMapV1.clear(7);
        bitMapV1.print();

        // 测试一个不存在的整数
        bitMapV1.clear(9999999);
    }

    /**
     * 假设要对3、1、2、13进行排序
     */
    @Test
    public void test_02() {
        System.out.println("### 待排序:3 1 2 13");

        BitMapV1 bitMapV1 = new BitMapV1(13);
        int length = bitMapV1.getBitMapArr().length;
        // 由于13大于第0排最大的序号7了,需要2个Byte,即16Bit
        Assert.assertEquals(2, length);

        bitMapV1.add(3);
        bitMapV1.print();

        bitMapV1.add(1);
        bitMapV1.print();

        bitMapV1.add(2);
        bitMapV1.print();

        bitMapV1.add(13);
        bitMapV1.print();

        // 看看哪些灯亮着,把它们的序号依次记录下来,即排序完成后的结果
        System.out.print("### 排序后的结果:");
        for (int i = 0; i < 16; i++) {
            if (bitMapV1.isExist(i)) {
                System.out.print(i + " ");
            }
        }

        System.out.println();

        // 把上面点亮的灯,逐一熄灭
        System.out.println("### 把上面点亮的灯,逐一熄灭:");
        bitMapV1.clear(3);
        // 注意:两行为一组了
        bitMapV1.print();

        bitMapV1.clear(1);
        bitMapV1.print();

        bitMapV1.clear(2);
        bitMapV1.print();

        bitMapV1.clear(13);
        bitMapV1.print();
    }
}

另一个实现版本:

package com.jjk;

/**
 * 改编自:https://stackoverflow.com/questions/23278469/why-is-my-bitmap-sort-not-infintely-faster-than-my-mergesort
 */
public class BitMapV2 {
    byte[] bits;
    int size;

    public BitMapV2(int n) {
        size = n;
        bits = new byte[(int) Math.ceil((double) n / (double) Byte.SIZE)];
        for (Byte b : bits) {
            b = 0;
        }
    }

    private String toBinary(byte b) {
        return String.format(Integer.toBinaryString(b & 0xFF)).replace(' ', '0');
    }

    void set(int i) {
        int index = i / Byte.SIZE;
        bits[index] = (byte) ((bits[index] | (byte) (1 << (Byte.SIZE - 1 - (i % Byte.SIZE)))));
    }

    void unset(int i) {
        int index = i / Byte.SIZE;
        bits[index] = (byte) ((bits[index] ^ (byte) (1 << (Byte.SIZE - 1 - (i % Byte.SIZE)))));
    }

    boolean isSet(int i) {
        int index = i / Byte.SIZE;
        byte mask = (byte) ((bits[index] & (byte) (1 << (Byte.SIZE - 1 - (i % Byte.SIZE)))));
        return (bits[index] & mask) != 0;
    }
}
5、BitMap的不足
  • 为了方便描述,还以排序为例,假设要为1、2、9999999这三个数排序,需要一个长度为1250000的字节数组。除第0排、最后一排的小正方形里有亮灯外,其余正方形里的灯都是熄灭的,好浪费啊。
  • 以排序为例,如果待排序的整数中有重复的数,排序后,重复的数仅保留了一个。
  • 如果待排序的整数中有负数,上述代码有缺陷,待完善。
在C#中,针对GDI位图的图像膨胀算法通常涉及到像素级别的处理,这可能涉及到对每个像素点周围的一组邻域进行检查,并将原像素颜色替换为新颜色,以达到膨胀的效果。这里我们可以使用Bitmap类及其LockBits方法来直接访问位图的像素数据。 以下是一个简单的示例,展示了如何使用C#来实现图像膨胀: ```csharp using System.Drawing; using System.Drawing.Imaging; public Bitmap ImageElevation(Bitmap originalBitmap, int pixelSize) { // 创建一个临时位图,用于存放膨胀后的结果 Bitmap resultBitmap = new Bitmap(originalBitmap.Width + pixelSize * 2, originalBitmap.Height + pixelSize * 2); // 获取原始位图的锁,以便修改像素 Rectangle srcRect = new Rectangle(0, 0, originalBitmap.Width, originalBitmap.Height); Rectangle destRect = new Rectangle(pixelSize, pixelSize, originalBitmap.Width + pixelSize * 2, originalBitmap.Height + pixelSize * 2); using (Graphics graphics = Graphics.FromImage(resultBitmap)) { // 将原始位图复制到临时位图的一部分,同时扩大边界 graphics.DrawImage(originalBitmap, destRect, srcRect, GraphicsUnit.Pixel); // 用新的颜色填充扩张区域 Brush brush = new SolidBrush(Color.Transparent); // 需要替换为你想要的颜色 graphics.FillRectangle(brush, destRect.X - pixelSize, destRect.Y - pixelSize, pixelSize * 2, pixelSize * 2); } return resultBitmap; } ``` 在这个例子中,`pixelSize` 参数表示每边要膨胀多少像素。这个函数会返回一个膨胀后的位图,其中边缘部分添加了透明色(你可以根据需要更改填充颜色)。 注意:实际应用中可能还需要考虑性能因素,如使用并行处理来加速膨胀过程(如果图片足够大),以及对内存使用的优化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值