我遇到了一个小问题,无法找到满意的解决方案。 有一个字节数组,我需要这些字节按高7位排序 保留低位的顺序。
原来它看起来像这样:
// sort buf[N] to tmp[N]
uint offs[128+1]; uint c,i,s;
for( i=0; i<128; i++ ) offs[i]=0;
for( i=0; i<l; i++ ) offs[buf[i]>>1]++;
for( i=0,s=0; i<128; i++ ) c=offs[i], offs[i]=s, s+=c; offs[i]=s;
byte* tmp = new byte[N];
for( i=0; i<N; i++ ) c=buf[i], tmp[offs[c>>1]++]=c; // sort
但是这些块足够大(目前是8M),我想使用多个线程, 每个线程额外的8M是显而易见的。
所以我尝试使用一些简单的基数排序:
void radix( byte* buf, uint h, uint l, uint mask ) {
uint p = (h+l)>>1, q = h;
uint i = offs[h], j = offs[l]-1; h = offs[p];
if( (i<h) && (j>=h) ) {
byte c = buf[i], d = buf[j];
while( (i<h) && (j>=h) ) {
while( (c&mask)==0 ) c = buf[++i]; // find value with bit 1
while( (d&mask)!=0 ) d = buf[--j]; // find value with bit 0
buf[i]=d; buf[j]=c; // swap 1-0 -> 0-1
c = buf[++i]; d = buf[--j];
}
if( mask>=4 ) {
radix( buf, q,p, mask>>1 );
radix( buf, p,l, mask>>1 );
}
}
}
但是它改变了这些低位的顺序,它变得无法使用。
实际上一些比较简单的方法,比如bubblesort,就是我想做的, 但它们要慢得多,速度也是一个问题。
所以目前我通过临时缓冲区对较小的块进行排序,然后使用 索引表按顺序访问部分排序的块:
struct tmpsort {
enum{ blocksize = (1<<16)-1 };
unsigned short ofs[(max_quants+blocksize-1)/blocksize][probN];
tmpsort( byte* buf, uint f_len ) {
uint i,j,k;
uint freq[2*probN]; // prob freqs
byte tmp[blocksize+1];
for( k=0,j=0; k<f_len; k+=blocksize,j++ ) {
uint l = Min(k+blocksize,f_len)-k;
byte* p = &buf[k];
// compute offsets of sorted chunks
for( i=0; i<2*probN; i++ ) freq[i]=0;
for( i=0; i<l; i++ ) freq[p[i]]++;
for( i=0; i<probN; i++ ) freq[i+1]=freq[2*i+0]+freq[2*i+1]; // 1=0+1, 2=2+3, 3=4+5
freq[0] = 0;
for( i=0; i<probN; i++ ) freq[i+1]+=freq[i];
for( i=0; i<probN; i++ ) ofs[j][i]=freq[i+1];
// sort the block via tmp
for( i=0; i<l; i++ ) { byte c=p[i]; tmp[freq[c>>1]++]=c; }
for( i=0; i<l; i++ ) p[i]=tmp[i];
}
}
};
[...]
tmpsort ts( buf, f_len );
for( i=0; i<probN; i++ ) {
for( k=0,j=0; k<f_len; k+=ts.blocksize,j++ ) {
uint x = i>0 ? ts.ofs[j][i-1] : 0;
for(; x<ts.ofs[j][i]; x++ ) putc( buf[k+x],g );
}
}
但是tmp []和ofs []数组使用了太多的堆栈空间 不是一个完整的类别,所以我一直在想是否有一些 这是一个很好的解决方案。
这里有一个数据样本和我的实现: http://nishi.dreamhosters.com/u/tmpsort_v0.rar
答案 0 :(得分:1)
为什么不直接使用任何标准,稳定 sorting algorithm,例如Insertion Sort,并实施适当的比较器功能?
答案 1 :(得分:1)
这可以使用相对简单的代码在O(n log n)时间内完成,使用基数排序版本,对7个重要位中的每一个执行稳定排序,从最不重要到最重要。这种技术相对于稳定的就地合并排序的优点是,如果您自己编写代码,代码就会简单得多。
这是通过一个指定位执行就地稳定排序的函数。这里,为了简单起见,使用O(lg n)堆栈空间递归写入(如果您希望通过使用for循环来组织分而治之的方法,则可以消除此堆栈空间使用情况):
// sort array x from i to j by bit b
sort(x, i, j, b) {
if (i >= j - 1) return;
mid = (i + j) / 2;
sort(x, i, mid, b);
sort(x, mid, j, b);
first1 = -1;
last0 = -1;
for (k = i; k < j; k++) {
if (first1 < 0 && isSet(x[k], b)) first1 = k;
if (!isSet(x[k], b)) last0 = k;
}
if (last0 < first1) return;
// the sequence of bit b generally looks something like 0000011100000111111
// so we reverse from the first 1 to the last 0
reverse(x, first1, last0afterfirst1);
newlast0 = first1;
while (!isSet(x[++newlast0], b));
newlast0--;
// the elements in the range first1..last0 are in the wrong order, so reverse
reverse(x, first1, newlast0);
reverse(x, newlast0 + 1, last0);
}
函数isSet
测试是否设置了一个位并且reverse
执行就地数组反转。上面的排序子程序在每个位上调用如下(如基数排序):
sort(x) {
for (b = 1; b < 8; b++) {
sort(x, 0, n, b);
}
}
总运行时间为“O(7 * n log n)”。如果该算法被推广,则额外因子7可以是可变的。
答案 2 :(得分:0)
可以将quicksort实现为稳定排序。就big-O而言,它并不比插入排序更好,但在实践中它会更好地执行 lot 。如果您对大小最大为6或8的叶子进行硬编码排序,我认为这将是您获得稳定的就地排序的最佳性能。
实际上......据说有一种就地稳定的合并排序。在理想的理论特征方面,它是排序的圣杯 - 就地,真O(n log n)
,和稳定,同时。但是我怀疑实施它是一个巨大的痛苦,并且与那个大O有相当大的常数条件。
答案 3 :(得分:0)
有额外的64kB,你可以(你已经注意到)以压缩形式存储一个512 kbit的块(减去一些固定数量的索引数据)(只存储每个键的最低位)转过大块并转换它们对于它们的压缩排序形式,在整个数组的开头压缩它们。
现在将压缩的表单合并为一个大的压缩表单(轻松释放7M。)然后解压缩回排序的数组。
这是O(N),虽然常量看起来相当大,有3次传递,涉及一些非平凡的位操作。