[珠玑拾遗]之一------通俗易懂解读位向量和Java实现

来源:互联网 发布:淘宝包店名字 编辑:程序博客网 时间:2024/06/08 15:50

前序

昨夜晚归,兴之所至,翻阅旧书,《编程珠玑》,薄尘轻蒙,遂感慨无数,静心而读。忽遇难解之习题,问诸西洋必应者,得一文曰[珠玑之椟]位向量/位图的定义和应用,其思明而言简,不得其意,研习良久,终顿开茅塞,于今日作拙,欲通俗易懂见诸Java矣。

一、背景介绍

首先我们来看一下Bentley大师在《编程珠玑》第一章提出的问题:

输入:一个最多包含n个正整数的文件,每个数都小于n,其中n=10^7。如果在输入文件中有任何整数重复出现就是致命错误(文件中的整数互异)。没有其他数据与该整数相关联。
输出:按升序排列的输入整数的列表。
约束:最多有1MB的内存空间可用,有充足的磁盘存储空间可用。运行时间最多几分钟,最理想的时间是10S。

其中具体的分析过程不再重复,感兴趣的朋友可以查看原书,下面直接列出大师的解决方案。

数据结构位向量(亦作位图,不过与图形学中的位图混淆,下作位向量)。一个n位的二进制数据,数据i如果出现在该二进制的第i位,则该位置为1,否则为0。如:用一个10位长的二进制数据表示元素都小于10的集合,{1,2,3,5,8},该集合用二进制数据的表现形式:01110100100

算法分析:分三步解决
1. 初始化集合,每个位都置为0;
2. 读入文件的每个整数,将对应的位置为1;
3. 遍历二进制数据,如果该位为1,则输出相应的整数。

伪代码

/*第一步:遍历二进制数组,都置为0,进行初始化for i = [0,n)    bit[i] = 0/*第二步:读取文件,将整数对于的位置为1for each i in the file    bit[i] = 1;/*第三步:将已排序的整数遍历输出for i = [0,n)    if bit[i] == 1    print(i)

好,以上我们将位向量数据结构的概念了解清楚了,下面遇到一个书上的习题。

二、提出问题

2.如何使用位逻辑运算(如与、或、移位)来实现位向量?

第一眼看到这个题目时我很纳闷儿,不是可以直接操作二进制位的数组吗,为什么还要使用位逻辑运算来实现位向量呢。
然后转念一想,像Java/C++这类高级语言是没有bit这种数据类型的,占位最小的数据类型就是byte,一个字节,八位。
所以我们如果要用Java实现位向量的话,需要考虑如何运用位运算了。

三、解决问题

  1. 重新定义问题:假设有一组互异且小于N的正整数,需要使用位向量来进行升序排列,请用Java实现。

  2. 问题解析:

    • 首先我们要明确对于位向量来说,要存储一组小于N的正整数,那么就需要N位的二进制数。
    • 而如果使用int数组来表示二进制位,1int=4byte=4*8bit,那么一个int元素就可以表示32位,即存储小于32的正整数集合,两个int元素可以表示64位,即存储小于64的正整数集合。
    • 那么问题来了,如果要表示小于N的正整数集合,需要多少个int元素?
      array.length = (N-1)/32 + 1
    • 如何定位正整数i在数组中的位置呢? 一句话:32整除i,商Q是int数组的下标,余数R是1在这个int元素中的位数。
      我们可以想象一下,i用位向量表示就是000000….1….,其中1所在的位置是i。可以想象成这个很长的位向量对齐int数组的首位,然后倒向int数组(数组按照一个二进制位一个槽来表示)
    • 目前我们已经定位了i的位置,下一步考虑如何进行三个很重要的操作 1.置位;2.置零,3.读取
    • 置位:用余数(二进制表示,即1 << Q)与相应的int元素做或操作
    • 置零:将余数(二进制表示,即1 << Q)取反,然后结果与int元素做与运算
    • 读取:用余数与相应的int元素做与运算,得到int元素中该位置的值,如为1则返回1,为0则返回0。
  3. 实现:

package com.rambo.P1;import java.util.ArrayList;import java.util.List;public class BitVetory {    private int n;    private int[] bitArray;    private static final int BIT_LENGTH = 32;//默认使用int类型    private static int  P;    private static int Q;    /**     * 初始化位向量     * @param n     */    public BitVetory(int n) {        this.n = n;        bitArray = new int[(n-1)/BIT_LENGTH + 1];        init();    }    /**     * 初始化操作     */    public void init(){        for (int i = 0; i < n; i++) {            clr(i);        }    }    /**     * 获取排序后的数组     * @return     */    public List<Integer> getSortedArray(){        List<Integer> sortedArray = new ArrayList<>();        for (int i = 0; i < n; i++) {            if (get(i) == 1) {                sortedArray.add(i);            }        }        return sortedArray;    }    /**     * 置位操作     * @param i     */    public void set(int i){        P = i / BIT_LENGTH;        Q = i % BIT_LENGTH;        bitArray[P] |= 1 << Q;    }    /**     * 置零操作     * @param i     */    public void clr(int i){        P = i / BIT_LENGTH;        Q = i % BIT_LENGTH;        bitArray[P] &= ~(1 << Q);    }    /**     * 读取操作     * @param i     * @return     */    public int get(int i){        P = i / BIT_LENGTH;        Q = i % BIT_LENGTH;        return Integer.bitCount(bitArray[P] & (1 << Q));    }}package com.rambo.P1.test;import java.util.ArrayList;import java.util.List;import java.util.Random;import com.rambo.P1.BitVetory;public class TestMain {    public static void main(String[] args) {        int amount = 15;        List<Integer> randoms = getRandoms(amount);        System.out.println("排序前数组:");        BitVetory bitVetory = new BitVetory(amount);        for (Integer e : randoms) {            System.out.print(e+",");            bitVetory.set(e);        }        List<Integer> sortedArray = bitVetory.getSortedArray();        System.out.println();        System.out.println("排序后数组:");        for (Integer e : sortedArray) {            System.out.print(e+",");        }    }    private static List<Integer> getRandoms(int amount) {        Random random = new Random();        List<Integer> randoms = new ArrayList<>();        while(randoms.size() < (amount - 1)){            int element = random.nextInt(amount - 1) + 1;//element ∈  [1,amount)            if (!randoms.contains(element)) {                randoms.add(element);            }        }        return randoms;    }}

输出:

排序前数组:
11,7,12,1,2,9,3,13,6,5,14,4,8,10
排序后数组:
1,2,3,4,5,6,7,8,9,10,11,12,13,14

好,如此这般我们便使用Java语言实现了位向量。

问题?

我们的代码是否还有可优化的空间呢?从位操作符的角度考虑。
.
.
.
.
.
.
..
.
.
.
.
.
.
.
.
.
.
.
.
.

将P = i / BIT_LENGTH改成P = i >> 5(2^5=32)
将Q = i % BIT_LENGTH改成Q = i & 0x1F (0x1F = 11111)

3 0
原创粉丝点击