如何 O(1) 排序

· · 个人记录

我们现做如下假设:

  1. 输入有 n 个数,以一个二进制数构成,比如二进制数 11,1011,1110,1 压缩为二进制数 00011\text{ }01011\text{ }01110\text{ }00001(请记住每个数字前面的 0,以后要考)。

  2. 输出以同样的方式压缩。

  3. 整数串中每个数的位数都必须相等,位数不够用零补足。这个位数称为定宽或 w,本文的例子的 w4

  4. 对于任意大小的整数,其赋值、四则运算、取余运算、比较运算、位运算都能够在 O(1) 的时间内完成。

  5. 没有重复。

那么我们开始。

考虑如下排序算法:

给定 n 个数 a_1,a_2,a_3,\dots,a_n,对于每个数,枚举有几个数小于其,设有 x_i 个数小于 a_i,那么将 a_i 存入 b_{x_i},那么 b_i 就是 a_i 排序的结果。

首先,假设我们要对 0100\text{ }0111\text{ }0001\text{ }0010 排序,我们考虑把原来的串扩展成 A,B 两个串:

A=0100\text{ }0111\text{ }0001\text{ }0010\text{ }0100\text{ }0111\text{ }0001\text{ }0010\text{ }0100\text{ }0111\text{ }0001\text{ }0010\text{ }0100\text{ }0111\text{ }0001\text{ }0010 B=0100\text{ }0100\text{ }0100\text{ }0100\text{ }0111\text{ }0111\text{ }0111\text{ }0111\text{ }0001\text{ }0001\text{ }0001\text{ }0001\text{ }0010\text{ }0010\text{ }0010\text{ }0010 而我们有: $$mask=\frac{1111\text{ }1111\text{ }1111\text{ }1111\text{ }1111\text{ }1111\text{ }1111\text{ }1111\text{ }1111\text{ }1111\text{ }1111\text{ }1111\text{ }1111\text{ }1111\text{ }1111\text{ }1111}{1111\text{ }1111\text{ }1111\text{ }1111}=\frac{2^{64}-1}{2^{16}-1}$$ 写成通用的形式,就是: $$mask=\frac{2^{n^2w}-1}{2^{nw}-1}$$ 这样我们在后面就可以一起比较 $O(n^2)$ 个数对了。 我们将 $B$ 看做: $$B=0000\text{ }0000\text{ }0000\text{ }0100\text{ }0000\text{ }0000\text{ }0000\text{ }0111\text{ }0000\text{ }0000\text{ }0000\text{ }0001\text{ }0000\text{ }0000\text{ }0000\text{ }0010\times0001\text{ }0001\text{ }0001\text{ }0001$$ 那么前面这个操作等价于加大 $w$,这个操作不好做,但我们发现,如果我们把 $A=0000\text{ }0000\text{ }0000\text{ }0000\text{ }0100\text{ }0111\text{ }0001\text{ }0010\text{ }0100\text{ }0111\text{ }0001\text{ }0010\text{ }0100\text{ }0111\text{ }0001\text{ }0010\text{ }0100\text{ }0111\text{ }0001\text{ }0010$ 和 $0000\text{ }0000\text{ }0000\text{ }0000\text{ }1111\text{ }0000\text{ }0000\text{ }0000\text{ }0000\text{ }1111\text{ }0000\text{ }0000\text{ }0000\text{ }0000\text{ }1111\text{ }0000\text{ }0000\text{ }0000\text{ }0000\text{ }1111$ 按位与一下,就可以得到: $$0000\text{ }0000\text{ }0000\text{ }0000\text{ }0100\text{ }0000\text{ }0000\text{ }0000\text{ }0000\text{ }0111\text{ }0000\text{ }0000\text{ }0000\text{ }0000\text{ }0001\text{ }0000\text{ }0000\text{ }0000\text{ }0000\text{ }0010$$ 这就很接近 $B$ 了,我们现在要缩小 $w$。 如果 $w'\times n\le w$,我们就可以将一个定宽 $w$ 的串缩减为定宽为 $w'$ 的序列,具体来说,将 $$0000\text{ }0000\text{ }0000\text{ }0000\text{ }0100\text{ }0000\text{ }0000\text{ }0000\text{ }0000\text{ }0111\text{ }0000\text{ }0000\text{ }0000\text{ }0000\text{ }0001\text{ }0000\text{ }0000\text{ }0000\text{ }0000\text{ }0010$$ 对 $1111\text{ }1111\text{ }1111\text{ }1111$ 取模,就可以得到原来的 $0100\text{ }0111\text{ }0001\text{ }0010$(其实这个操作相当于把原来的数截成 $16$ 位的然后再相加)。 然后我们通过两次操作把定宽扩大到 $(n+1)^2w$,然后再缩到 $n\cdot w$,再乘 $0001\text{ }0001\text{ }0001\text{ }0001$,我们就得到了 $B$。 然后我们拿到了 $A$ 和 $B$。 我们考虑计算 $a-b$,在计算机里是 $a+(\sim b)$,其中 $\sim$ 表示按位取反,如果最高位符号位是 $1$ 那么说明是负数,也就是 $a<b$,否则 $a>b$。 所以我们取的 $w$ 应该是最大位数加 $1$。 那么我们就可以按照如下步骤操作: $$0100\text{ }0111\text{ }0001\text{ }0010\text{ }0100\text{ }0111\text{ }0001\text{ }0010\text{ }0100\text{ }0111\text{ }0001\text{ }0010\text{ }0100\text{ }0111\text{ }0001\text{ }0010$$ A 串。 $$0100\text{ }0100\text{ }0100\text{ }0100\text{ }0111\text{ }0111\text{ }0111\text{ }0111\text{ }0001\text{ }0001\text{ }0001\text{ }0001\text{ }0010\text{ }0010\text{ }0010\text{ }0010$$ B 串。 $$1011\text{ }1011\text{ }1011\text{ }1011\text{ }1000\text{ }1000\text{ }1000\text{ }1000\text{ }1110\text{ }1110\text{ }1110\text{ }1110\text{ }1101\text{ }1101\text{ }1101\text{ }1101$$ 对 $B$ 取反。 $$0111\text{ }0111\text{ }0111\text{ }0111\text{ }0111\text{ }0111\text{ }0111\text{ }0111\text{ }0111\text{ }0111\text{ }0111\text{ }0111\text{ }0111\text{ }0111\text{ }0111\text{ }0111\text{ }$$ 一个掩码。 $$0011\text{ }0011\text{ }0011\text{ }0011\text{ }0000\text{ }0000\text{ }0000\text{ }0000\text{ }0110\text{ }0110\text{ }0110\text{ }0110\text{ }0101\text{ }0101\text{ }0101\text{ }0101$$ 将 $B$ 中符号位置 $0$。 $$0111\text{ }1010\text{ }0100\text{ }0101\text{ }0100\text{ }0111\text{ }0001\text{ }0010\text{ }1010\text{ }1101\text{ }0111\text{ }1000\text{ }1001\text{ }1100\text{ }0110\text{ }0111$$ $A$ 和上面一个序列相加。 $$1000\text{ }1000\text{ }1000\text{ }1000\text{ }1000\text{ }1000\text{ }1000\text{ }1000\text{ }1000\text{ }1000\text{ }1000\text{ }1000\text{ }1000\text{ }1000\text{ }1000\text{ }1000$$ 另外一个掩码。 $$0000\text{ }1000\text{ }0000\text{ }0000\text{ }0000\text{ }0000\text{ }0000\text{ }0000\text{ }1000\text{ }1000\text{ }0000\text{ }1000\text{ }1000\text{ }1000\text{ }0000\text{ }0000$$ 两者按位与。 $$0000\text{ }0001\text{ }0000\text{ }0000\text{ }0000\text{ }0000\text{ }0000\text{ }0000\text{ }0001\text{ }0001\text{ }0000\text{ }0001\text{ }0001\text{ }0001\text{ }0000\text{ }0000$$ 右移 $w-1$ 位。 最后这个结果中,$1$ 表示 $B$ 对应位置比 $A$ 大,否则就是 $A$ 对应位置比 $B$ 大。 如果原来的数字串没有重复,那么这个里面应该有 $\dfrac{n(n-1)}2$ 个 $1$。 把结果对 $1111\text{ }1111\text{ }1111\text{ }1111$ 取模,得到: $$0010\text{ }0011\text{ }0000\text{ }0001$$ 这里面第一个数 $0010$ 表示原串中有 $2$ 个数比第一个数 $0100$ 小,我们将这个序列称为 $index$。 我们先构造这个序列: $$count=0000\text{ }0001\text{ }0010\text{ }0011$$ 也就是: $$\sum_{i=0}^{n-1}i2^{w(n-i-1)}$$ 我们直接 Mathematica 算一下: $$\sum_{i=0}^{n-1}i2^{w(n-i-1)}=\frac{2^{nw}+n-2^wn-1}{(2^w-1)^2}$$ 然后我们把 $index$ 与 $count$ 比较一下,套用上面的所有步骤: $$0010\text{ }0010\text{ }0010\text{ }0010\text{ }0011\text{ }0011\text{ }0011\text{ }0011\text{ }0000\text{ }0000\text{ }0000\text{ }0000\text{ }0001\text{ }0001\text{ }0001\text{ }0001$$ (扩展的 $index$ 序列) $$0000\text{ }0001\text{ }0010\text{ }0011\text{ }0000\text{ }0001\text{ }0010\text{ }0011\text{ }0000\text{ }0001\text{ }0010\text{ }0011\text{ }0000\text{ }0001\text{ }0010\text{ }0011$$ (扩展的 $count$ 序列) $$0001\text{ }0000\text{ }0000\text{ }0001\text{ }0001\text{ }0001\text{ }0000\text{ }0000\text{ }0000\text{ }0001\text{ }0000\text{ }0000\text{ }0000\text{ }0000\text{ }0000\text{ }0000$$ (对应位置上 $index$ 小) $$0000\text{ }0000\text{ }0000\text{ }0000\text{ }0000\text{ }0000\text{ }0001\text{ }0000\text{ }0000\text{ }0000\text{ }0001\text{ }0001\text{ }0001\text{ }0000\text{ }0001\text{ }0001$$ (对应位置上 $count$ 小) 我们用 $\text{or}$ 和 $\text{xor}$ 提取出两个串都是 $0$ 的位置: $$0001\text{ }0001\text{ }0000\text{ }0001\text{ }0001\text{ }0001\text{ }0001\text{ }0000\text{ }0000\text{ }0001\text{ }0001\text{ }0001\text{ }0001\text{ }0000\text{ }0001\text{ }0001$$ 两个序列按位或。 $$0001\text{ }0001\text{ }0001\text{ }0001\text{ }0001\text{ }0001\text{ }0001\text{ }0001\text{ }0001\text{ }0001\text{ }0001\text{ }0001\text{ }0001\text{ }0001\text{ }0001\text{ }0001$$ 一个掩码。 $$0000\text{ }0000\text{ }0001\text{ }0000\text{ }0000\text{ }0000\text{ }0000\text{ }0001\text{ }0001\text{ }0000\text{ }0000\text{ }0000\text{ }0000\text{ }0001\text{ }0000\text{ }0000$$ 两者异或。 $$0000\text{ }0000\text{ }1111\text{ }0000\text{ }0000\text{ }0000\text{ }0000\text{ }1111\text{ }1111\text{ }0000\text{ }0000\text{ }0000\text{ }0000\text{ }1111\text{ }0000\text{ }0000$$ 乘 $2^w-1$。 然后与 $B$ 按位与: $$0000\text{ }0000\text{ }0100\text{ }0000\text{ }0000\text{ }0000\text{ }0000\text{ }0111\text{ }0001\text{ }0000\text{ }0000\text{ }0000\text{ }0000\text{ }0010\text{ }0000\text{ }0000$$ 最后的数满足出现的数与原串相同,且每个数都在排序后的位置上,所以我们对 $1111\text{ }1111\text{ }1111\text{ }1111$ 取模,就可以得到最终的结果: $$0001\text{ }0010\text{ }0100\text{ }0111$$ 好,前面说了这么多,我们放一下代码。 ```python def print_in_binary(num, n, w): res = bin(num)[2:] res = '0' * (n * w - len(res)) + res ans = '' for i in range(n): ans += res[i * w : i * w + w] + ' ' print(ans) def get_1(n): return (1 << n) - 1 def get_mask(n, w): return get_1(n * w) // get_1(w) def get_count(n, w): return ((1 << n * w) + n - (n << w) - 1) // (((1 << w) - 1) ** 2) def get_A(a, n, w): return a * get_mask(n, n * w) def inc_w(a, n, w): return get_A(a, n, w) & (get_mask(n, (n + 1) * w) * get_1(w)) def get_B(a, n, w): B = inc_w(a, n, w) B = inc_w(B, n, w * (n + 1)) B = B % get_1(w * (n + 1) * (n + 1) - w * n) B = B * get_mask(n, w) return B def sort_in_binary(a, n, w): A = get_A(a, n, w) B = get_B(a, n, w) notB = get_1(n * n * w) ^ B notB = notB & (get_mask(n * n, w) * get_1(w - 1)) index = ((notB + A) & (get_mask(n * n, w) << (w - 1))) >> (w - 1) index = index % get_1(n * w) count = get_count(n, w) indexB = get_B(index, n, w) countA = get_A(count, n, w) notindexB = get_1(n * n * w) ^ indexB notindexB = notindexB & (get_mask(n * n, w) * get_1(w - 1)) index_small = ((notindexB + countA) & (get_mask(n * n, w) << (w - 1))) >> (w - 1) notcountA = get_1(n * n * w) ^ countA notcountA = notcountA & (get_mask(n * n, w) * get_1(w - 1)) count_small = ((notcountA + indexB) & (get_mask(n * n, w) << (w - 1))) >> (w - 1) result = index_small | count_small result = result ^ get_mask(n * n, w) result = result * get_1(w) result = result & B answer = result % get_1(w * n) return answer # test [00011, 01011, 01110, 00001] #n = 4 #w = 5 #a = int("00011010110111000001", 2) # test [0100, 0111, 0001, 0010] n = 4 w = 4 a = int("0100011100010010", 2) print_in_binary(sort_in_binary(a, n, w), n, w) ``` 结束 结束 结束 结束 结束 结束 结束 结束 结束 结束