现在我们来看两个最终的结构,它们利用我们的集合表示整数这一事实。位向量是列1中的老朋友。这是它们的私有数据和函数:
enum { BITSPERWORD = 32, SHIFT = 5, MASK = 0x1F }; int n, hi, *x; void set(int i) { x[i>>SHIFT] |= (1<<(i & MASK)); } void clr(int i) { x[i>>SHIFT] &= ~(1<<(i & MASK)); } int test(int i) { return x[i>>SHIFT] &= (1<<(i & MASK)); }
正如我所收集的那样,代表列整数的位向量的中心思想,如第1列所述,是当且仅当整数i在集合中时,第i个位才打开。
但是,我对上述三个函数所涉及的算法确实感到茫然。而且这本书没有给出解释。
我只能得到i & MASKi的低5位,而i>>SHIFT将i向右移动5位。
i & MASK
i>>SHIFT
有人会详细介绍这些算法吗?对我而言,位运算始终是一个神话,:(
理解发生了什么的关键是要知道BITSPERWORD= 2 SHIFT。因此,x[i>>SHIFT]找到数组的哪个32位元素x具有与对应的位i。(通过i向右移动5位,您只需将其除以32。)找到的正确元素后x,i可以使用的低5位查找x[i>>SHIFT]对应于的哪个特定位i。那是什么i & MASK?通过将1移位该位数,可以将与1对应的位移动x[i>>SHIFT]到与中i第th个位相对应的确切位置x。
BITSPERWORD
SHIFT
x[i>>SHIFT]
x
i
这里有更多的解释:
想象一下,我们需要N位向量中的位容量。由于每个int持有32位,我们将需要(N + 31) / 32 int用于存储的值(即,N / 32向上舍入)。在每个int值内,我们将采用以下约定:位从最低有效位到最高有效位排序。我们还将采用以下约定:向量的前32位在中x[0],后32位在中x[1],依此类推。这是我们正在使用的内存布局(在我们的位向量中显示与内存的每个位相对应的位索引):
N
int
(N + 31) / 32
x[0]
x[1]
+----+----+-------+----+----+----+ x[0]: | 31 | 30 | . . . | 02 | 01 | 00 | +----+----+-------+----+----+----+ x[1]: | 63 | 62 | . . . | 34 | 33 | 32 | +----+----+-------+----+----+----+ etc.
我们的第一步是分配必要的存储容量:
x = new int[(N + BITSPERWORD - 1) >> SHIFT]
(我们可以为动态扩展此存储做准备,但这只会增加解释的复杂性。)
现在,假设我们要访问位i(对其进行设置,清除或仅了解其当前值)。我们需要首先弄清楚x要使用哪个元素。由于每个int值有32位,因此很容易:
subscript for x = i / 32
利用枚举常量,x我们想要的元素是:
x[i >> SHIFT]
(将其看作是进入N位向量的32位宽窗口)。现在,我们必须找到与相对应的特定位i。从内存布局来看,不难发现窗口中的第一个(最右边)位对应于bit index 32 * (i >> SHIFT)。(窗口在中的i >> SHIFT插槽之后开始x,每个插槽都有32位。)由于那是窗口中的第一位(位置0),因此我们感兴趣的位在位置
32 * (i >> SHIFT)
i >> SHIFT
i - (32 * (i >> SHIFT))
在窗户上。经过一些试验,您可以使自己确信该表达式始终等于i % 32(实际上,这是mod运算符的一个定义),而该表达式又始终等于i & MASK。由于最后一个表达式是计算我们想要的最快方法,因此我们将使用它。
i % 32
从这里开始,其余的工作非常简单。我们从窗口最低有效位置(即常量1)中的单个位开始,然后将其向左移动i & MASK一位i,以使其到达窗口中与位向量中的位相对应的位置。这是表达的地方
1
1 << (i & MASK)
来自。现在将钻头移到我们想要的位置,我们可以将其用作设置,清除或查询该位置上的钻头值的掩码,x[i>>SHIFT]并且我们知道实际上是在设置,清除或查询该值位i在我们的位向量。