编程珠玑是很经典的算法讨论书籍,想进大公司的朋友还是要好好啃啃的,思路可谓经典,引入位向量概念。
比如常见与国内某社交网站(自然很山寨,我们大学时常用)的面试题目。
问题:对1000w个整数排序,条件为(1)都是非负整数,(2)每个整数最多出现一次,(3)最大整数小于 n。
先将问题简化:
可以将小于32的非负整数集合用32个二进制位表示(big endian),换句话说就是在32bit操作系统中一个整型数据表示。
即将集合{2,1,31,5,8,19,18,23,27}表示成为
01100100100000000011000100010000。
这种思想便是位向量的思想,标示一个数据仅用二进制的一位。而我们熟知的C 、C++ 中没有对应一个二进制位的数据类型,因此,要实现位向量,需要借助于位操作。这里,我们采用整型数组模拟定义1000万个位的数组。问题的纠结处即是如何用整型数组模拟定义1000万个位的数组。其实上面的简化例子已将问题描述的相当清楚,如A[0](32位中的每位)可表示0-31的整数,A[1]表示32-63........................
那么对于原问题,我们可以将问题归纳为
(1)初始化,将数组的每个元素清 0;(2)插入数据并实现排序,对于输入数据 i,置 array[i] 为 1; (3)输出排序结果,如果 array[i] 为 1,就输出整数 i。
以上三条对应三个函数
其中
SHIFT为5
>>SHIFT表示将i向右移动5位,相当于将i除以32。因为一个int是32位,所以结果表示i在第几个int数组成员中。
1<<(i&MASK):使用掩码留下i的低5位再左移1位,相当于除以32所得的余数再左移1位。因为第一个是0,所以结果都需要左移1位。结果中1所在的位表示i在该int数组成员的第几位上。
void set(int i) { a[i>>SHIFT] |= (1<<(i & MASK) ); }//设置位数组中的从0开始的第i位为1
void clr(int i) { a[i>>SHIFT] &= ~(1<<(i & MASK) ); }//设置位数组中的从0开始的第i位为0
int test(int i) { return a[i>>SHIFT] & (1<<(i & MASK) ); }//取出从0开始的第i位的值,用于检测
相关源程序见下:
#include <stdio.h>
#define BITSPERWORD 32 // 表示一个整型含有32个位
#define SHIFT 5 // 单次位移量
#define MASK 0x1F // 掩码
#define N 10000000 // 表示有1000万个数
int a[1 + N/BITSPERWORD]; // 使用整型数组模拟定义1000万个位的数组
void set(int i) { 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;
}
这种方法的优点是,可以边输入数据边进行排序,这对于实时处理来说是非常合适的。当然它的缺点也是明显的,不能处理负数和小数。尽管如此, 对于排序问题,想必大家都非常熟悉。而且,应该都知道基于比较的排序方法的时间复杂度的下界是 O(n*logn)。尽管又出现了基数排序,使得排序类算法的时间复杂度改进到 O(d*n),但是基数排序方法实现起来还是比较麻烦的。上面这种排序方法的时间复杂度可以认为是 O(n),但是和基数排序方法相比,它的实现非常简单。