你可以通过计数来了解最佳方案。 (我希望stackoverflow允许像math.stackexchange一样使用TeX公式。无论如何...)
ceiling(log(Combination(2^32,1000)) / (8 * log(2))) = 2934
如果像你说的那样,选择是均匀分布的,那么在这种情况下,你所能期望的最佳压缩平均值为2934字节。最佳比率是未编码表示的73.35%。
Combination(2^32,1000)
是压缩算法可能输入的总数。如果它们是均匀分布的,则最优编码是一个巨大的整数,通过索引标识每个可能的输入。每个巨大的整数值唯一地标识一个输入。想象一下在巨大的表格中通过索引查找输入。
ceiling(log(Combination(2^32,1000)) / log(2))
是你需要用于该索引整数的位数。
更新:我找到了一种方法,可以使用现成的压缩工具接近理论最佳效果。我排序,应用增量编码,并从中减去1(因为连续不同元素之间的增量至少为1)。然后,技巧在于我将所有高字节写出,然后是下一个最重要的字节等等。增量的高字节减一倾向于为零,因此将许多零组合在一起,这是标准压缩工具所喜欢的。另外,下一个字节集往往偏低值。
对于示例(从0..2^32-1中获取1000个均匀且不同的样本),当通过
gzip -9
运行时,我得到了平均3110字节,并且通过
xz -9
得到了3098字节(xz使用与7zip相同的LZMA压缩)。这些数字非常接近理论最佳平均值2934。此外,gzip的开销为18字节,而xz的开销为24字节,都用于头和尾。因此,与理论最佳值的公正比较应该是
gzip -9
的3092和
xz -9
的3074。比理论最佳值大约5%。
更新2:
我实现了排列的直接编码,并获得了平均2974字节,仅比理论最佳值多了1%左右。我使用
GNU多精度算术库在一个巨大的整数中为每个排列编码了一个索引。下面显示了编码和解码的实际代码。我添加了注释,以说明
mpz_*
函数执行的算术操作可能不明显。
local void combination_encode_between(mpz_t pack, mpz_t base,
const unsigned long *set,
int low, int high)
{
int mid;
mid = (low + high) >> 1;
if (mid == low) {
assert(set[low] < set[high]);
return;
}
mpz_addmul_ui(pack, base, set[mid] - set[low] - 1);
mpz_mul_ui(base, base, set[high] - set[low] - 1);
combination_encode_between(pack, base, set, low, mid);
combination_encode_between(pack, base, set, mid, high);
}
local void combination_encode(mpz_t pack, const unsigned long *set, int num,
unsigned long max)
{
mpz_t base;
if (num < 1) {
mpz_set_ui(pack, 0);
return;
}
mpz_set_ui(pack, set[0]);
if (num < 2) {
assert(set[0] <= max);
return;
}
assert(set[num - 1] <= max);
mpz_init_set_ui(base, max - num + 2);
mpz_addmul_ui(pack, base, set[num - 1] - set[0] - 1);
mpz_mul_ui(base, base, max - set[0]);
combination_encode_between(pack, base, set, 0, num - 1);
mpz_clear(base);
}
local void combination_decode_between(mpz_t unpack, unsigned long *set,
int low, int high)
{
int mid;
unsigned long rem;
mid = (low + high) >> 1;
if (mid == low)
return;
rem = mpz_fdiv_q_ui(unpack, unpack, set[high] - set[low] - 1);
set[mid] = set[low] + 1 + rem;
combination_decode_between(unpack, set, low, mid);
combination_decode_between(unpack, set, mid, high);
}
local void combination_decode(const mpz_t pack, unsigned long *set, int num,
unsigned long max)
{
mpz_t unpack;
unsigned long rem;
if (num < 1)
return;
if (num < 2) {
set[0] = mpz_get_ui(pack);
return;
}
mpz_init(unpack);
set[0] = mpz_fdiv_q_ui(unpack, pack, max - num + 2);
rem = mpz_fdiv_q_ui(unpack, unpack, max - set[0]);
set[num - 1] = set[0] + 1 + rem;
combination_decode_between(unpack, set, 0, num - 1);
mpz_clear(unpack);
}
有mpz_*
函数可用于将数字写入文件并读取回来,或者将数字导出到指定的内存格式中,并将其导入回来。