LSD基数排序为负整数,无队列

时间:2018-08-07 08:09:35

标签: java algorithm sorting

首先,我知道这里还有一个类似的问题: 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;
        }

这变得更容易理解。

这个想法是,第一个正数只能在最后一个负数之后,并且所有正数必须按排序顺序在负数之后。因此,我们只需要将所有负数的总负数相加即可确保正数确实在负数之后。 负数也是如此。

1 个答案:

答案 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)])中前0123的索引。

现在,算法将这些累积计数用作(中间)输出中的索引。每当它从该存储桶复制一个元素时,它都会增加存储桶索引,因此,同一存储桶中的元素将被复制到连续的位置。

[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-10。它们的二进制表示形式为1101100,因此映射为012,{分别为{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]数组用于两个不同的目的。首先,它用于对单独的事件进行计数,然后用于对累积的事件进行计数。当单独计数时,不使用数组的第一个元素。累积计数时,不使用数组的最后一个元素。

我认为,如果改为使用两个单独的数组,算法会更简单。