首先,我知道这里还有一个类似的问题: Radix Sort for Negative Integers
但是这不是重复的。
我正在研究基数排序,并且对Sedgewick教授和Wayne教授对LSD基数排序的实现有疑问。
public static void sort(int[] a) {
final int BITS = 32; // each int is 32 bits
final int R = 1 << BITS_PER_BYTE; // each bytes is between 0 and 255
final int MASK = R - 1; // 0xFF
final int w = BITS / BITS_PER_BYTE; // each int is 4 bytes
int n = a.length;
int[] aux = new int[n];
for (int d = 0; d < w; d++) {
// compute frequency counts
int[] count = new int[R+1];
for (int i = 0; i < n; i++) {
int c = (a[i] >> BITS_PER_BYTE*d) & MASK;
count[c + 1]++;
}
// compute cumulates
for (int r = 0; r < R; r++)
count[r+1] += count[r];
// for most significant byte, 0x80-0xFF comes before 0x00-0x7F
if (d == w-1) {
int shift1 = count[R] - count[R/2];
int shift2 = count[R/2];
for (int r = 0; r < R/2; r++)
count[r] += shift1;
for (int r = R/2; r < R; r++)
count[r] -= shift2;
}
// move data
for (int i = 0; i < n; i++) {
int c = (a[i] >> BITS_PER_BYTE*d) & MASK;
aux[count[c]++] = a[i];
}
// copy back
for (int i = 0; i < n; i++)
a[i] = aux[i];
}
最高有效字节是怎么回事?它比我想出的任何东西都要优雅得多。
我对解释该代码块的能力没有信心,很明显,它可以处理负数,但我不确定如何处理。
有人可以更详细地解释该代码块吗?
更新
我认为我对变量 shift1 和 shift2 的命名感到困惑。如果我们重新命名,然后添加一两个注释:
if (d == w-1) {
int totalNegatives= count[R] - count[R/2];
int totalPositives= count[R/2];
for (int r = 0; r < R/2; r++)
// all positive number must come after any negative number
count[r] += totalNegatives;
for (int r = R/2; r < R; r++)
// all negative numbers must come before any positive number
count[r] -= totalPositives;
}
这变得更容易理解。
这个想法是,第一个正数只能在最后一个负数之后,并且所有正数必须按排序顺序在负数之后。因此,我们只需要将所有负数的总负数相加即可确保正数确实在负数之后。 负数也是如此。
答案 0 :(得分:2)
基本算法
让我们从忽略最高有效位的块开始,并尝试理解其余代码。
算法逐字节处理整数。每个字节可以具有256个不同的值,这些值分别进行计数。这就是在第一块中发生的情况。之后
int[] count = new int[R+1];
for (int i = 0; i < n; i++) {
int c = (a[i] >> BITS_PER_BYTE*d) & MASK;
count[c + 1]++;
}
每个count[i]
是a
中在其第i-1
个字节中具有值d
的元素数(请注意,它们使用count[c + 1]++
,因此{ {1}})
然后算法继续使用来计算累积计数
count[0] == 0
此后,每个for (int r = 0; r < R; r++)
count[r+1] += count[r];
是该存储桶的第一个元素应在(中间)输出中结尾的索引。 (请注意,count[i]
的长度为257(count
),因此可以忽略累积数组的最后一个元素。在下面的示例中,将其放在方括号中。)让我们来看一个示例4个值(为了简洁,不使用256个值):
考虑一个具有字节值R + 1
的数组。这给出了计数[0, 3, 3, 2, 1, 2]
和累积计数[0, 1, 1, 2, 2]
。这些恰好是排序数组(即[0, 1, 2, 4, (6)]
)中前0
,1
,2
和3
的索引。
现在,算法将这些累积计数用作(中间)输出中的索引。每当它从该存储桶复制一个元素时,它都会增加存储桶索引,因此,同一存储桶中的元素将被复制到连续的位置。
[0, 1, 2, 2, 3, 3]
处理符号位
最高有效位有点特殊,因为在two's complement中,它是符号,负数为1,正数为0。因此,累积数组for (int i = 0; i < n; i++) {
int c = (a[i] >> BITS_PER_BYTE*d) & MASK;
aux[count[c]++] = a[i];
}
for (int i = 0; i < n; i++)
a[i] = aux[i];
对于最后一步是不正确的。最高有效位为0(正数)的值的计数位于数组的前半部分,最高有效位为1(负数)的值的计数位于数组的后半部分。因此,必须将阵列的前半部分和后半部分“翻转”。
这是通过将counts数组后半部分的元素总数与counts数组前半部分的每个元素相加而实现的。并从counts数组的后半部分的每个元素中减去counts数组的前半部分的元素总数。如前所述,count
数组的长度为257,因此前128个元素(257/2)是前一半,其余129个元素是后一半。
让我们看一个新示例,该示例现在带有两位带符号的值,即counts
,-2
,-1
和0
。它们的二进制表示形式为1
,10
,11
,00
,因此映射为01
,2
,{分别为{1}}和3
。
将0
视为1
并将其数组。转换为无符号:a
。应用算法获得累积计数:[0, -1, -1, -2, 1, -2]
。如果不进行翻转,我们将得到排序后的无符号数组[0, 3, 3, 2, 1, 2]
,它等效于有符号数组[0, 1, 2, 4, (6)]
。排序不正确。
现在,让我们对带符号的字节执行额外的步骤。我们将累积[0, 1, 2, 2, 3, 3]
数组分为两半:[0, 1, -2, -2, -1, -1]
和counts
。前半部分有2(2-0)个元素,后半部分有4(6-2)个元素。因此,我们在上半部分的每个元素中添加4:[0, 1]
,并在下半部分的每个元素:[2, 4, (6)]
中减去2。将两半结合起来得到[4, 5]
。
如果现在将这些计数用作最终无符号数组中的索引,则会得到[0, 2, (4)]
(前0为索引4,前1为索引5,依此类推)。将其转换回带符号的值得到[4, 5, 0, 2, (4)]
,这确实是正确的。
可能的混淆:该算法中令人困惑的部分之一是[2, 2, 3, 3, 0, 1]
数组用于两个不同的目的。首先,它用于对单独的事件进行计数,然后用于对累积的事件进行计数。当单独计数时,不使用数组的第一个元素。累积计数时,不使用数组的最后一个元素。
我认为,如果改为使用两个单独的数组,算法会更简单。