我有一台有1mb内存的电脑,没有其他本地存储。我必须使用它通过TCP连接接受100万个8位十进制数字,对它们进行排序,然后通过另一个TCP连接发送排序的列表。
数字列表可能包含重复的,我不能丢弃。代码将放在ROM中,所以我不需要从1 MB中减去我的代码的大小。我已经有了驱动以太网端口和处理TCP/IP连接的代码,它需要2 KB的状态数据,包括1 KB的缓冲区,代码将通过它读取和写入数据。这个问题有解决办法吗?
问答来源:
slashdot.org
cleaton.net
我有一台有1mb内存的电脑,没有其他本地存储。我必须使用它通过TCP连接接受100万个8位十进制数字,对它们进行排序,然后通过另一个TCP连接发送排序的列表。
数字列表可能包含重复的,我不能丢弃。代码将放在ROM中,所以我不需要从1 MB中减去我的代码的大小。我已经有了驱动以太网端口和处理TCP/IP连接的代码,它需要2 KB的状态数据,包括1 KB的缓冲区,代码将通过它读取和写入数据。这个问题有解决办法吗?
问答来源:
slashdot.org
cleaton.net
当前回答
我有一台有1M内存的电脑,没有其他本地存储
另一种作弊方法:你可以使用非本地(网络)存储代替(你的问题不排除这一点),调用一个网络服务,它可以使用直接的基于磁盘的归并排序(或者只需要足够的RAM来在内存中排序,因为你只需要接受1M的数字),而不需要(公认非常巧妙的)已经给出的解决方案。
这可能是作弊,但不清楚你是在寻找一个现实问题的解决方案,还是一个让人扭曲规则的谜题……如果是后者,那么简单的欺骗可能比复杂但“真实”的解决方案(正如其他人指出的那样,后者只能用于可压缩输入)得到更好的结果。
其他回答
我想试试基数树。如果可以将数据存储在树中,那么就可以执行顺序遍历来传输数据。
我不确定你是否能把它装进1MB,但我认为值得一试。
If it is possible to read the input file more than once (your problem statement doesn't say it can't), the following should work. It is described in Benchley's book "Programming Perls." If we store each number in 8 bytes we can store 250,000 numbers in one megabyte. Use a program that makes 40 passes over the input file. On the first pass it reads into memory any integer between 0 and 249,999, sorts the (at most) 250,000 integers and writes them to the output file. The second pass sorts the integers from 250,000 to 499,999 and so on to the 40th pass, which sorts 9,750,000 to 9,999,999.
下面是这类问题的一般解决方案:
一般程序
所采取的方法如下。该算法在一个32位字的缓冲区上操作。它在循环中执行以下过程:
We start with a buffer filled with compressed data from the last iteration. The buffer looks like this |compressed sorted|empty| Calculate the maximum amount of numbers that can be stored in this buffer, both compressed and uncompressed. Split the buffer into these two sections, beginning with the space for compressed data, ending with the uncompressed data. The buffer looks like |compressed sorted|empty|empty| Fill the uncompressed section with numbers to be sorted. The buffer looks like |compressed sorted|empty|uncompressed unsorted| Sort the new numbers with an in-place sort. The buffer looks like |compressed sorted|empty|uncompressed sorted| Right-align any already compressed data from the previous iteration in the compressed section. At this point the buffer is partitioned |empty|compressed sorted|uncompressed sorted| Perform a streaming decompression-recompression on the compressed section, merging in the sorted data in the uncompressed section. The old compressed section is consumed as the new compressed section grows. The buffer looks like |compressed sorted|empty|
执行此过程,直到所有数字都已排序。
压缩
当然,这种算法只有在知道实际要压缩什么之前,才有可能计算出新排序缓冲区的最终压缩大小。其次,压缩算法需要足够好来解决实际问题。
所使用的方法使用三个步骤。首先,算法将始终存储排序序列,因此我们可以只存储连续条目之间的差异。每个差值都在[0,99999999]的范围内。
这些差异随后被编码为一元比特流。这个流中的1表示“向累加器添加1,0表示“将累加器作为一个条目发出,并重置”。所以差N由N个1和1个0表示。
所有差异的和将接近算法支持的最大值,所有差异的计数将接近算法中插入的值的数量。这意味着我们期望流在最后包含最大值1和计数0。这允许我们计算流中0和1的期望概率。即,0的概率为count/(count+maxval), 1的概率为maxval/(count+maxval)。
我们使用这些概率来定义这个比特流上的算术编码模型。这个算术代码将在最佳空间中精确地编码1和0的数量。我们可以计算该模型对于任何中间位流所使用的空间:bits = encoded * log2(1 + amount / maxval) + maxval * log2(1 + maxval / amount)。若要计算算法所需的总空间,请将encoded设置为amount。
为了不需要大量的迭代,可以向缓冲区添加少量开销。这将确保算法将至少对适合这个开销的数量进行操作,因为到目前为止,算法最大的时间成本是每个周期的算术编码压缩和解压缩。
除此之外,在算术编码算法的定点近似中,存储簿记数据和处理轻微的不准确性是需要一些开销的,但总的来说,即使使用可以包含8000个数字的额外缓冲区,该算法也能够容纳1MiB的空间,总共1043916字节的空间。
最优
除了减少算法的开销外,理论上不可能得到更小的结果。为了仅仅包含最终结果的熵,1011717个字节是必要的。如果我们减去为提高效率而增加的额外缓冲区,该算法使用1011916字节来存储最终结果+开销。
在10^8的范围内有10^6个值,所以平均每100个码点有一个值。存储第N个点到第(N+1)个点的距离。重复值的跳过值为0。这意味着跳跃平均需要7比特来存储,所以100万个跳跃将很适合我们的800万比特存储空间。
这些跳跃需要被编码成一个比特流,比如通过霍夫曼编码。插入是通过遍历比特流并在新值之后重写。通过遍历并写出隐含值来输出。出于实用性考虑,它可能被做成10^4个列表,每个列表包含10^4个代码点(平均100个值)。
随机数据的霍夫曼树可以通过假设跳跃长度上的泊松分布(均值=方差=100)先验地构建,但可以在输入上保留真实的统计数据,并用于生成处理病理病例的最佳树。
(我原来的答案是错误的,对不起,数学不好,见下面的休息。)
这个怎么样?
前27位存储您所看到的最小数字,然后是与下一个数字的差值,编码如下:5位存储用于存储差值的位数,然后是差值。使用00000表示您再次看到了该数字。
这是因为插入的数字越多,数字之间的平均差值就越小,所以当你添加更多的数字时,你用更少的比特来存储差值。我想这叫做增量表。
我能想到的最糟糕的情况是所有数字都等距(以100为间隔),例如假设0是第一个数字:
000000000000000000000000000 00111 1100100
^^^^^^^^^^^^^
a million times
27 + 1,000,000 * (5+7) bits = ~ 427k
Reddit来拯救你!
如果你要做的只是把它们排序,这个问题就简单了。它需要122k(100万比特)来存储你看到的数字(如果看到0,则第0位,如果看到2300,则第2300位,等等。
读取数字,将它们存储在位域中,然后在保持计数的同时将位移出。
但是,你必须记住你看过多少。我受到上面的子列表答案的启发,想出了这个方案:
用2位或27位代替1位:
00表示你没有看到这个数字。 01表示你看过一次 1表示你看过,接下来的26位是看了多少次。
我认为这是可行的:如果没有重复,你就有一个244k的列表。 在最坏的情况下,你看到每个数字两次(如果你看到一个数字三次,它会缩短列表的其余部分),这意味着你不止一次看到了50,000个,你0次或1次看到了950,000个项目。
50,000 * 27 + 950,000 * 2 = 396.7k.
如果你使用以下编码,你可以做进一步的改进:
0表示你没有看到这个数字 10表示你看过一次 11是你计数的方式
这将导致平均280.7k的存储空间。
编辑:我周日早上的数学算错了。
最坏的情况是,我们两次看到50万个数字,所以数学就变成了:
500,000 *27 + 500,000 *2 = 1.77M
交替编码导致平均存储为
500,000 * 27 + 500,000 = 1.70M
: (