4

位图的使用与实现 - Grey Zeng

 2 years ago
source link: https://www.cnblogs.com/greyzeng/p/16634282.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

位图的使用与实现

作者:Grey

原文地址:

博客园:位图的使用与实现

CSDN:位图的使用与实现

说明#

本文内容使用的编程语言是 Java。其他语言有类似的数据结构。

位图的使用#

在 Java 中,使用HashSet可以实现如下操作:

add(T v)

加入一个元素到HashSet中,重复则覆盖。

contains(T v)

判断一个元素是否加入过HashSet

remove(T v)

HashSet中删除一个元素。

如果数据范围固定,使用位图比使用HashSet省空间。

在 Java 中,一个 int 类型的整数可以表示 32 个 bit,所以,如果数据范围0 ~ 31,可以直接用一个 int 类型的数来完成上述三个操作。

add(4)这个操作,就是在如下 int 类型数(二进制表示)中,第 4 号位置设置为 1。如下图

image

继续执行add(7)这个操作,就是在如下 int 类型数(二进制表示)中,第 7 号位置设置为 1。如下图

image

contains(4)这个操作,就是判断 4 号位置是 0 还是 1,如果是 1, 就说明 4 存在,如果是 0 ,说明 4 不存在。

remove(7)这个操作,就是把 7 号位置置为 0。如下效果

image

如果数据范围是 0 ~ 1023, 则可以用一个 int 类型数组来表示,这个数组只需要 32 个元素即可。因为 32 个 int 类型元素,可以表示 1024 位,正好可以覆盖数据范围中的所有数字。对于0 ~ 1023中任意一个数 num,num在数组中存在第num / 32个元素中的第num % 32位中。

举例说明:

num = 37,客观上,num 应该在如下位置:

image

在 1 号(37 / 32)数组元素的第 5 号( 37 % 32)位置。

位图的实现#

为了扩大表示范围,我们可以使用 long 类型来替代 int 类型,因为 long 类型可以表达 64 个 bit,思路还是和如上一样。现在说明如何实现上述三个方法。

先把位图的数据结构和相关方法定义好

public static class BitMap {

        // 使用每个位置的信息。
        private long[] bits;

        public BitMap(int max) {
            // TODO
            // 位图初始化
        }

        public void add(int num) {
            // TODO
            // 添加一个元素
        }

        public void remove(int num) {
            // TODO
            // 删除一个元素
        }

        public boolean contains(int num) {
            // TODO
            // 判断一个元素是否在位图中
        }
    }

注:我们这里只考虑非负数,对于负数的情况,也可以转换成正数来处理,比如:-3~6,可以转换成0~9

首先是位图的初始化,即如何根据数据范围确定位图应该开辟多大的数组?

由于是 long 类型,所以,对于 0 ~ x 区间来说,需要准备

(x + 64) / 64

这么大的 long 类型数组。

位图中增加一个元素,比如我们要增加 53 这个元素,先定位它是数组中的哪个元素,即53 / 64 = 0,第 0 号位置的元素,再定位是这个元素中的第几位,即:53 % 64 = 11,即第 11 位,我们可以用 1L << 11后的值| bit[0]即可,代码实现如下

        public void add(int num) {
            bits[num / 64] |= (1L << (num % 64));
        }

由于 num / 64其实就是 num >> 6

num % 64其实就是num & 63

由于位运算比算术运算效率要高,所以add方法可以进一步写成如下形式

        public void add(int num) {
            //  bits[num / 64] |= (1L << (num % 64));
            // num % 64 ---> num & 63
            // 只适用于 2 的 n 次方
            bits[num >> 6] |= (1L << (num & 63));
        }

位图中删除一个元素,其实就是把对应位置的二进制位置为 0,其他位置保持不变,通过

~((1L << (num & 63))) 

可以预先得到一个除目标位置是 0,其他位置都是 1 的数。

然后通过这个数去 &上数组目标位置的元素,即可把对应位置的 1 改为 0,其他位置不变。

        public void remove(int num) {
            bits[num >> 6] &= ~(1L << (num & 63));
        }

位图中是否包含某个元素,其实就是判断对应位置是 0 还是 1, 如果是 0 ,就说明存在,不是 0 , 则不存在。

        public boolean contains(int num) {
            return (bits[num >> 6] & (1L << (num & 63))) != 0;
        }

位图的完整代码见

    public static class BitMap {

        private long[] bits;

        public BitMap(int max) {
            // 准备多少个整数? 0 ~ 63 需要1个整数
            // >> 6 就是 除以 64
            bits = new long[(max + 64) >> 6];
        }

        public void add(int num) {
            //  bits[num / 64] |= (1L << (num % 64));
            // num % 64 ---> num & 63
            // 只适用于 2 的 n 次方
            bits[num >> 6] |= (1L << (num & 63));
        }

        public void remove(int num) {
            bits[num >> 6] &= ~(1L << (num & 63));
        }

        public boolean contains(int num) {
            return (bits[num >> 6] & (1L << (num & 63))) != 0;
        }
    }

测试#

通过实现的位图和 Java 自带的 HashSet 进行对比测试,可以判断我们写的位图是否正确,测试代码如下

package snippet;

import java.util.HashSet;
import java.util.Set;


public class Code_0009_BitMap {

   public static void main(String[] args) {
        System.out.println("测试开始!");
        int max = 70000;
        BitMap bitMap = new BitMap(max);
        Set<Integer> set = new HashSet<>();
        int testTime = 90000000;
        for (int i = 0; i < testTime; i++) {
            int num = (int) (Math.random() * (max + 1));
            double decide = Math.random();
            if (decide < 0.333) {
                bitMap.add(num);
                set.add(num);
            } else if (decide < 0.666) {
                bitMap.remove(num);
                set.remove(num);
            } else {
                if (bitMap.contains(num) != set.contains(num)) {
                    System.out.println("Oops!");
                    break;
                }
            }
        }
        for (int num = 0; num <= max; num++) {
            if (bitMap.contains(num) != set.contains(num)) {
                System.out.println("Oops!");
            }
        }
        System.out.println("测试结束!");
    }
}

运行,未打印报错信息,说明我们的算法正确。

更多#

算法和数据结构笔记

参考资料#

算法和数据结构新手班-左程云


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK