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

本文通过《编程珠玑》中的一道问题引入,解释位向量的概念并展示如何用Java实现位向量,用于升序排列不超过32位的整数集合。文章详细介绍了初始化、置位、置零和读取操作,并提供了完整的Java代码示例。最后,文章通过随机数示例验证了位向量排序的正确性,并提出了优化位操作的可能性。
摘要由CSDN通过智能技术生成

前序

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

/*第二步:读取文件,将整数对于的位置为1

for each i in the file

bit[i] = 1;

/*第三步:将已排序的整数遍历输出

for i = [0,n)

if bit[i] == 1

print(i)

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

二、提出问题

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

第一眼看到这个题目时我很纳闷儿,不是可以直接操作二进制位的数组吗,为什么还要使用位逻辑运算来实现位向量呢。

然后转念一想,像Java/C++这类高级语言是没有bit这种数据类型的,占位最小的数据类型就是byte,一个字节,八位。

所以我们如果要用Java实现位向量的话,需要考虑如何运用位运算了。

三、解决问题

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

问题解析:

首先我们要明确对于位向量来说,要存储一组小于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。

实现:

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 getSortedArray(){

List 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 randoms = getRandoms(amount);

System.out.println("排序前数组:");

BitVetory bitVetory = new BitVetory(amount);

for (Integer e : randoms) {

System.out.print(e+",");

bitVetory.set(e);

}

List sortedArray = bitVetory.getSortedArray();

System.out.println();

System.out.println("排序后数组:");

for (Integer e : sortedArray) {

System.out.print(e+",");

}

}

private static List getRandoms(int amount) {

Random random = new Random();

List 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)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值