## 手机分配短讯id的面试题目(分析解答篇)

2010-09-03 01:37 by Milo Yip, ... 阅读, ... 评论, 收藏, 编辑

## 问题分析

x \in U - A

x \in U \wedge x \notin A

## 纯函数API的解

### 测试函数

#define M 256 // ID的数目，且所有ID在[0, M)的区间内

#define TEST_COUNT 10000
#ifdef NDEBUG
#define TEST_REPEATCOUNT 100
#else
#define TEST_REPEATCOUNT 1
#endif

typedef unsigned char byte;
typedef unsigned long dword;

typedef byte (*idalloc_func)(byte*, size_t);


// 检测ids里是否含id (C++ 版本)
inline bool contain(byte* ids, size_t n, byte id) {
assert(ids != NULL);

return find(ids, ids + n, id) != ids + n;
}

// 检测ids里是否含id (C 版本)
inline bool contain(byte* ids, size_t n, byte id) {
assert(ids != NULL);

for (size_t i = 0; i < n; i++)
if (ids[i] == id)
return true;
return false;
}


// 测试平均情况
void test_average(idalloc_func idalloc) {
assert(idalloc != NULL);

byte ids[M];

for (size_t i = 0 ; i < M; i++)
ids[i] = (byte)i;

srand(0); // 使每次测试的伪随机数相同

size_t n = 0;
for (int test = 0; test < TEST_COUNT; test++) {
random_shuffle(ids, ids + M); // 把整个数组洗牌

for (int repeat = 0; repeat < TEST_REPEATCOUNT; repeat++) {
byte id = idalloc(ids, n);
(void)id;
assert(!contain(ids, n, id));

// 测试是否最小的id
for (size_t i = 0; i < id; i++)
assert(contain(ids, n, (byte)i));
}

n = (n + 1) % M;
}
}


// 测试最坏情况(ids为无序的[0, M - 2], 结果必然是id = M - 1)
void test_worst(idalloc_func idalloc) {
assert(idalloc != NULL);

const size_t n = M - 1;
byte ids[n];

srand(0); // 使每次测试的伪随机数相同

for (size_t i = 0 ; i < n; i++)
ids[i] = (byte)i;

for (int test = 0; test < TEST_COUNT; test++) {
random_shuffle(ids, ids + n);

for (int repeat = 0; repeat < TEST_REPEATCOUNT; repeat++) {
byte id = idalloc(ids, n);
(void)id;
assert(id == M - 1);
}
}
}


### 线性查找

// 线性查找 (总是传回最小id)
// 时间复杂度: O(n^2)
// 临时内存大小: 0 字节
// 注: 因为n < M，无论ids内的值为何(甚至有重复元素)，必然可找到一个id，所以id的for不用边界检查。
byte linear_search(byte* ids, size_t n) {
assert(ids != NULL);
assert(n < M);

// 逐个id检查是否存在于[ids, ids + n)
for (byte id = 0; ; id++)
if (!contain(ids, n, id))
return id;
}


### 二分查找

// 数ids内有多少个id在[min, max)的区间内
inline size_t count_interval(byte* ids, size_t n, size_t min, size_t max) {
size_t count = 0;

for (size_t i = 0; i < n; i++)
if (ids[i] >= min && ids[i] < max)
count++;

return count;
}

// 二分查找 (总是传回最小id)
// 时间复杂度: O(n lg n)
// 临时内存大小: 0 字节
byte binary_search(byte* ids, size_t n) {
assert(ids != NULL);
assert(n < M);

size_t l = 0, r = M;

for(;;) {
size_t c = (l + r) / 2; // 把id范围从[l, r)分割为[l, c), [c, r)两个区间
size_t count;

// 以下的条件测试次序保证了传回最小id
if ((count = count_interval(ids, n, l, c)) < c - l) {
if (count == 0)
return (byte)l;
r = c;
}
else if ((count = count_interval(ids, n, c, r)) < r - c) {
if (count == 0)
return (byte)c;
l = c;
}
else
assert(false); // 因为n < M，不可能找不到任何id
}
}

### 排序

// 排序 (总是传回最小id)
// 时间复杂度: O(n lg n)
// 临时内存大小: M 字节(如果可改变ids则是0)
byte sort_stl(byte* ids, size_t n) {
assert(ids != NULL);
assert(n < M);

byte buffer[M];
memcpy(buffer, ids, n);

sort(buffer, buffer + n); // 平均 O(n lg n)

for (size_t i = 0; i < n; i++)
if (buffer[i] != i)
return (byte)i;

return (byte)n;
}

### 堆

// 堆 (总是传回最小id)
// 时间复杂度: O(n lg n)
// 临时内存大小: M 字节(如果可改变ids则是0)
byte heap_stl(byte* ids, size_t n) {
assert(ids != NULL);
assert(n < M);

byte buffer[M];
memcpy(buffer, ids, n);

byte* end = buffer + n;
make_heap(buffer, end, greater()); // O(n)

for (byte id = 0; buffer != end; id++, end--) {
if (buffer[0] != id)
return id;
pop_heap(buffer, end, greater()); // O(lg n)
}

return (byte)n;
}

### 剖分

// 剖分 (总是传回最小id)
// 时间复杂度: O(n)
// 临时内存大小: M 字节(如果可改变ids则是0)
byte partition_stl(byte* ids, size_t n) {
assert(ids != NULL);
assert(n < M);

byte buffer[M];
memcpy(buffer, ids, n);

byte *first = buffer, *last = buffer + n;
size_t l = 0, r = M;

for (;;) {
size_t c = (l + r) / 2;
byte* middle = partition(first, last, bind2nd(less(), c)); // O(n)
// 后置条件: l <= [first, middle)内元素 < c 及 c <= [middle, last)内元素 < r

// 以下的条件测试次序保证了传回最小id
if (first == middle)
return (byte)l;
else if ((size_t)distance(first, middle) < c - l) {
last = middle;
r = c;
}
else if (middle == last)
return (byte)c;
else if ((size_t)distance(middle, last) < r - c) {
first = middle;
l = c;
}
else
assert(false);
}
}

### 布尔集合

f(i)=\left\{\begin{matrix} 1 & \text{if } i \in A\\ 0 & \text{if } i \notin A \end{matrix}\right.

// 布尔集合 (总是传回最小id)
// 时间复杂度: O(n)
// 临时内存大小: M 字节
byte boolset(byte* ids, size_t n) {
assert(ids != NULL);
assert(n < M);

bool id_used[M] = { false };

// 填充 id_used
for (size_t i = 0; i < n; i++) {
assert(!id_used[ids[i]]); // 此处断言失败代表ids有重复元素
id_used[ids[i]] = true;
}

// 扫描id_used去找出最小未用id
for (size_t i = 0; i < M; i++)
if (!id_used[i])
return (byte)i;

assert(false);
return 0;
}

### 位集合

// 位集合 (总是传回最小id)
// 时间复杂度: O(n)
// 临时内存大小: floor((M + 31) / 32) * 4 字节
byte bitset_standard(byte* ids, size_t n) {
assert(ids != NULL);
assert(n < M);

const size_t dword_count = (M + 31) / 32;
dword id_unused_bits[dword_count];

// 开始时设全部id为未用(即设位为1)
memset(id_unused_bits, ~0, sizeof(id_unused_bits));

// 填充id_unused_bits (ids内的位清为0)
for (size_t i = 0; i < n; i++) {
size_t index = ids[i] / 32;
dword bitIndex = ids[i] % 32;
assert(id_unused_bits[index] & (1 << bitIndex));
id_unused_bits[index] ^= (1 << bitIndex);
}

// 扫描id_unused_bits，找出最小未用id
for (size_t index = 0; index < dword_count; index++) {
if (dword bits = id_unused_bits[index]) {
for (dword bitIndex = 0; bitIndex < 32; bitIndex++)
if (bits & (1 << bitIndex)) {
dword id = index * 32 + bitIndex;
assert(id < M);
return (byte)id;
}
}
}

assert(false);
return 0;
}

// 位集合(使用内部函数(intrinsic))
byte bitset_intrinsic(byte* ids, size_t n) {
assert(ids != NULL);
assert(n < M);

const size_t dword_count = (M + 31) / 32;
dword id_unused_bits[dword_count];

// 开始时设全部id为未用(即设位为1)
memset(id_unused_bits, ~0, sizeof(id_unused_bits));

// 填充id_unused_bits (ids内的位清为0)
for (size_t i = 0; i < n; i++) {
size_t index = ids[i] / 32;
dword bitIndex = ids[i] % 32;
assert(id_unused_bits[index] & (1 << bitIndex));
id_unused_bits[index] ^= (1 << bitIndex);
}

// 扫描id_unused_bits，找出最小未用id
for (size_t index = 0; index < dword_count; index++) {
dword bitIndex;
if (_BitScanForward(&bitIndex, id_unused_bits[index])) {
dword id = index * 32 + bitIndex;
assert(id < M);
return (byte)id;
}
}

assert(false);
return 0;
}

## 含状态API的解

struct manager {
// 这里有一些状态变量(暂未决定)

byte alloc();
void dealloc(byte id);
};

### 测试函数

template <class T>
void test_manager() {
T manager;
bool id_allocated[M] = { false };
byte allocated_ids[M]; // allocated_ids[0]至allocated_ids[id_used_count - 1]储存无序的已分配id
size_t allocated_id_count = 0;

srand(0); // 使每次测试的伪随机数相同

for (int test = 0; test < TEST_COUNT * TEST_REPEATCOUNT; test++) {
// id集为空时必须进行分配，否则若id集未满时，有一半概率进行分配
if (allocated_id_count == 0 || (rand() > RAND_MAX / 2 && allocated_id_count < M)) {
byte id = manager.alloc();
assert(!id_allocated[id]);
id_allocated[id] = true;
allocated_ids[allocated_id_count++] = id;
}
else {
// 其他情况，随机抽一个已分配id进行释放
assert(allocated_id_count > 0);
size_t index = rand() % allocated_id_count;
byte id = allocated_ids[index];
assert(id_allocated[id]);
manager.dealloc(id);
id_allocated[id] = false;
allocated_ids[index] = allocated_ids[--allocated_id_count]; // 用列表末的id取代已释放的id
}
}
}

### 布尔集合(含状态)

// 布尔集合 (总是传回最小id)
// 分配的时间复杂度: O(n)
// 释放的时间复杂度: O(1)
// 状态所需内存: M 字节
struct boolset_manager {
bool id_used[M];

boolset_manager() {
for (size_t i = 0; i < M; i++)
id_used[i] = false;
}

byte alloc() {
for (size_t i = 0; i < M; i++) {
if (!id_used[i]) {
id_used[i] = true;
return (byte)i;
}
}

assert(0);
return false;
}

void dealloc(byte id) {
assert(id_used[id]);
id_used[id] = false;
}
};

### 栈

// 栈
// 分配的时间复杂度: O(1)
// 释放的时间复杂度: O(1)
// 状态所需内存: M + 4 字节(使用short top会是M + 2 字节)
struct stack_manager {
byte ids[M];
size_t top;

stack_manager() : top(M) {
for (size_t i = 0; i < M; i++)
ids[i] = (byte)i;
}

byte alloc() {
assert(top > 0);
return ids[--top]; // 弹出
}

void dealloc(byte id) {
assert(top < M);
ids[top++] = id; // 压入
}
};

### 数组链表

// 数组链表 (来自qiaojie)
// 分配的时间复杂度: O(1)
// 释放的时间复杂度: O(1)
// 状态所需内存: M + 1 字节(若以freelist形式储存，则所需额外内存只是1字节)
byte next[M];

// 填入完整的环
for(int i = 0; i < M; ++i)
next[i] = (byte)(i + 1);
}

byte alloc() {
byte id = head;

// next[id]在这里已经不需要，可以用来放短讯或其他数据，这里放置0作为测试。实际上这步是可有可无的。
next[id] = 0;

return id;
}

void dealloc(byte id) {
}
};

// 用于数组链表的freelist的结构例子
union sms {
byte next;
char message[160];
};

## 效能测试

  0.068476 test_average(dummy)
0.545491 test_average(linear_search)
3.030943 test_average(binary_search)
4.209131 test_average(sort_stl)
0.966749 test_average(heap_stl)
0.424917 test_average(partition_stl)
0.208690 test_average(boolset)
0.272523 test_average(bitset_standard)
0.271665 test_average(bitset_intrinsic)

0.068385 test_worst(dummy)
27.025864 test_worst(linear_search)
11.407150 test_worst(binary_search)
10.122118 test_worst(sort_stl)
13.912083 test_worst(heap_stl)
0.887030 test_worst(partition_stl)
0.498429 test_worst(boolset)
0.570213 test_worst(bitset_standard)
0.458865 test_worst(bitset_intrinsic)

0.042507 test_manager()
0.073745 test_manager()
0.042462 test_manager()
0.042526 test_manager()

## 讨论

 解 传回最小id 平均时间复杂度 额外内存(字节) 线性查找 是 O(n^2) 0 二分查找 是 O(n lg n) 0 排序 是 O(n lg n) (最坏O(n^2)) m 或0(可改动ids) 堆 是 O(n lg n) m 或0(可改动ids) 剖分 是 O(n) m 或0(可改动ids) 布尔集合 是 O(n) m 位集合 是 O(n) floor((m+31)/32)*4 布尔集合(含状态) 是 O(n), O(1) m 位集合(含状态) 是 O(n), O(1) floor((m+31)/32)*4 栈 否 O(1), O(1) m + 4 或m + 2 数组链表 否 O(1), O(1) m + 1 或1

1. 纯函数API
2. 可改动ids的函数API
3. 含状态API