这个位排序代码中的位操作是如何工作的?
Posted
技术标签:
【中文标题】这个位排序代码中的位操作是如何工作的?【英文标题】:How do the bit manipulations in this bit-sorting code work? 【发布时间】:2010-11-06 05:18:18 【问题描述】:Jon Bentley 在他的书 Programming Pearls 的第 1 列中介绍了一种使用位向量对非零正整数序列进行排序的技术。
我已从here 获取程序 bitsort.c 并将其粘贴在下面:
/* Copyright (C) 1999 Lucent Technologies */
/* From 'Programming Pearls' by Jon Bentley */
/* bitsort.c -- bitmap sort from Column 1
* Sort distinct integers in the range [0..N-1]
*/
#include <stdio.h>
#define BITSPERWORD 32
#define SHIFT 5
#define MASK 0x1F
#define N 10000000
int a[1 + N/BITSPERWORD];
void set(int i)
int sh = i>>SHIFT;
a[i>>SHIFT] |= (1<<(i & MASK));
void clr(int i) a[i>>SHIFT] &= ~(1<<(i & MASK));
int test(int i) return a[i>>SHIFT] & (1<<(i & MASK));
int main()
int i;
for (i = 0; i < N; i++)
clr(i);
/*Replace above 2 lines with below 3 for word-parallel init
int top = 1 + N/BITSPERWORD;
for (i = 0; i < top; i++)
a[i] = 0;
*/
while (scanf("%d", &i) != EOF)
set(i);
for (i = 0; i < N; i++)
if (test(i))
printf("%d\n", i);
return 0;
我了解 clr、set 和 test 函数的作用,并在下面解释它们:(如果我在这里错了,请纠正我)。
clr 清除第 i 位 set 设置第 i 位 test 返回第 i 位的值现在,我不明白这些函数是如何工作的。我无法弄清楚这三个函数中发生的所有位操作。
【问题讨论】:
我会接受 Laurence 的回答,因为它帮助我从根本上了解程序的机制。 【参考方案1】:前 3 个常量是相互关联的。 BITSPERWORD 是 32。您需要根据您的编译器+架构来设置。 SHIFT 是 5,因为 2^5 = 32。最后,MASK 是 0x1F,即二进制 11111(即:低 5 位全部设置)。等效地,MASK = BITSPERWORD - 1。
bitset 在概念上只是一个位数组。这个实现实际上使用了一个整数数组,并假设每个整数有 32 位。因此,每当我们想要设置、清除或测试(读取)位时,我们需要弄清楚两件事:
它在(数组的)哪个 int 中 我们讨论的是哪个 int 位因为我们假设每个 int 有 32 位,所以我们只需除以 32(并截断)即可得到我们想要的数组索引。除以 32 (BITSPERWORD) 与右移 5 (SHIFT) 相同。这就是 a[i>>SHIFT] 位的意义所在。你也可以把它写成 [i/BITSPERWORD] (事实上,假设你的编译器有一个合理的优化器,你可能会得到相同或非常相似的代码)。
现在我们知道我们想要 a 的哪个元素,我们需要找出哪个位。真的,我们想要剩下的。我们可以用 i%BITSPERWORD 做到这一点,但事实证明 i&MASK 是等价的。这是因为 BITSPERWORD 是 2 的幂(在这种情况下为 2^5),而 MASK 是所有设置的低 5 位。
【讨论】:
这是我想说的人类语言:) +1 便于理解! @Laurence Gonsalves 很棒的分析,+1 :)【参考方案2】:基本上是桶排序优化:
保留长度为 n 的位数组 位。 清空位数组(首先用于main)。 逐一阅读项目(它们都必须是不同的)。 如果读取的编号为 i,则设置位数组中的第 i 个位。 迭代位数组。 如果该位已设置,则打印该位置。或者换句话说(对于 N
从一个空的 10 位数组开始(通常是一个整数)
0000000000
读取 4 并设置数组中的位..
0000100000
读取 6 并设置数组中的位
0000101000
读取2并设置数组中的位
0010101000
迭代数组并打印每个位被设置为 1 的位置。
2、4、6
已排序。
【讨论】:
不用担心。感谢您的参与。 碰巧,那是 我 不太明白的部分。谢谢。【参考方案3】:从 set() 开始: 右移 5 与除以 32 相同。这样做是为了找到该位所在的 int。 MASK 是 0x1f 或 31。与地址进行与运算给出 int 内的位索引。它与将地址除以 32 的余数相同。 将 1 左移位索引 ("1 ORing 设置位。 行“int sh = i>>SHIFT;”是一个浪费的行,因为他们没有在它下面再次使用 sh ,而是重复了“i>>SHIFT”
clr() 与 set 基本相同,只是它不是与 1
位排序还会从列表中删除重复项,因为每个整数最多只能计数 1 个。使用整数而不是位来计算每个大于 1 的排序称为基数排序。
【讨论】:
quinmars,请您详细说明 他是对的。我想我描述的是计数排序,而不是基数排序。 @ardsrk,在基数排序中,您按数字排序;从将第一个(或最后一个)数字放在一起开始,然后与下一个数字放在一起,等等参见en.wikipedia.org/wiki/Radix_sort【参考方案4】:位魔法用作一种特殊的寻址方案,适用于为 2 的幂的行大小。
如果您尝试理解这一点(注意:我宁愿使用每行位而不是每字位,因为我们在这里讨论的是位矩阵):
// supposing an int of 1 bit would exist...
int1 bits[BITSPERROW * N]; // an array of N x BITSPERROW elements
// set bit at x,y:
int linear_address = y*BITSPERWORD + x;
bits + linear_address = 1; // or 0
// 0 1 2 3 4 5 6 7 8 9 10 11 ... 31
// . . . . . . . . . . . . .
// . . . . X . . . . . . . . -> x = 4, y = 1 => i = (1*32 + 4)
声明linear_address = y*BITSPERWORD + x
也表示x = linear_address % BITSPERWORD
和y = linear_address / BITSPERWORD
。
当您通过使用每行 32 位的 1 个字在内存中优化这一点时,您会发现可以使用设置 x 列的位
int bitrow = 0;
bitrow |= 1 << (x);
现在当我们遍历位时,我们拥有线性地址,但需要找到对应的字。
int column = linear_address % BITSPERROW;
int bit_mask = 1 << column; // meaning for the xth column,
// you take 1 and shift that bit x times
int row = linear_address / BITSPERROW;
所以要设置第 i 位,你可以这样做:
bits[ i%BITSPERROW ] |= 1 << (linear_address / BITSPERROW );
一个额外的问题是,如果第二个操作数是 2 的幂,则模运算符可以替换为逻辑 AND,并且 / 运算符也可以替换为移位。
a % BITSPERROW == a & ( BITSPERROW - 1 ) == a & MASK
a / BITSPERROW == a >> ( log2(BITSPERROW) ) == a & SHIFT
这最终归结为非常密集但难以理解的与比特混蛋无关的符号
a[ i >> SHIFT ] |= ( 1 << (i&MASK) );
但我看不到该算法适用于例如每个字 40 位。
【讨论】:
【参考方案5】:引用 Bentleys 在 DDJ 中的原始文章的节选,这是代码在高层次上所做的:
/* phase 1: initialize set to empty */
for (i = 0; i < n; i++)
bit[i] = 0
/* phase 2: insert present elements */
for each i in the input file
bit[i] = 1
/* phase 3: write sorted output */
for (i = 0; i < n; i++)
if bit[i] == 1
write i on the output file
【讨论】:
【参考方案6】:几个疑问: 1. 为什么需要 32 位? 2. 我们可以在 Java 中通过创建一个键从 0000000 到 9999999 的 HashMap 来做到这一点吗 和值 0 或 1 基于位的存在/不存在?有什么影响 对于这样的程序?
【讨论】:
以上是关于这个位排序代码中的位操作是如何工作的?的主要内容,如果未能解决你的问题,请参考以下文章