Reputation: 5297
First of all, i know there is a similar question over here: Radix Sort for Negative Integers
however it is not duplicate to this one.
I am studying radix sorts and have a question regarding the implementation of LSD radix sort by Prof. Sedgewick and Prof. Wayne.
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];
}
What is going on with the most significant byte? It is far more elegant than anything i came up with.
I am not confident in my ability to explain that block of code, it is obvious that it deals with negative numbers but i am not exactly sure how.
Could somebody explain that block of code in greater detail ?
UPDATE
I think i got additionally confused naming of variables shift1 and shift2. If we rename things a bit, and add a comment or two:
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;
}
this becomes easier to follow.
The idea is that first positive number can only be in position after last negative number, and all positive numbers must be after negative ones in sorted order. Therefore we simply need to add count of total negative numbers to all positives in order to ensure that positive numbers will indeed come after negatives. Same analogy for negatives numbers.
Upvotes: 1
Views: 1006
Reputation: 13177
Basic algorithm
Let's start by ignoring the block for the most significant bit and try to understand the rest of the code.
The algorithms handles the integers byte by byte. Every byte can have 256 different values, which are counted separately. This is what happens in the first block. After
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]++;
}
every count[i]
is the number of elements in a
that have value i-1
in their d
th byte (note that they use count[c + 1]++
, so count[0] == 0
)
The algorithm then continues to compute the cumulative counts with
for (int r = 0; r < R; r++)
count[r+1] += count[r];
After this, every count[i]
is the index where the first element of that bucket should end up in the (intermediate) output. (Note that count
has length 257 (R + 1
), so the last element of the cumulative array can be ignored. I'll put it in brackets in the examples below.) Let's look at an example with 4 values (instead of 256, to keep it concise):
Consider an array with byte values [0, 3, 3, 2, 1, 2]
. This gives counts [0, 1, 1, 2, 2]
and cumulative counts [0, 1, 2, 4, (6)]
. These are exactly the indices of the first 0
, 1
, 2
, and 3
in the sorted array (which would be [0, 1, 2, 2, 3, 3]
).
Now the algorithm uses those cumulative counts as indices in the (intermediate) output. It increments the bucket index whenever it copies an element from that bucket, so elements from the same bucket are copied to consecutive spots.
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];
Handling the sign bit
The most significant bit is a bit special because in two's complement it is the sign, which is 1 for negative numbers and 0 for positive numbers. So the cumulative array count
is incorrect for the final step. The counts for values whose most significant bit are 0 (the positive numbers) are in the first half of the array and the counts for the values whose most significant bit are 1 (the negative numbers) are in the second half of the array. Therefore, the first half and the second half of the array must be "flipped".
This is achieved by adding the total number of elements in the second half of the counts array to each element in the first half of the counts array. And by subtracting the total number of elements in the first half of the counts array from each element in the second half of the counts array. As noted earlier, the counts
array has length 257, so the first 128 elements (257 / 2) are the first half and the remaining 129 elements are the second half.
Let's look at a new example, now with two-bits signed values, i.e., -2
, -1
, 0
, and 1
. The binary representation for these is 10
, 11
, 00
, 01
, so mapped to unsigned numbers that is 2
, 3
, 0
, 1
, respectively.
Consider and array a
as [0, -1, -1, -2, 1, -2]
. Convert to unsigned: [0, 3, 3, 2, 1, 2]
. Apply the algorithm to get the cumulative counts: [0, 1, 2, 4, (6)]
. If we would not do the flipping, we would end up with the sorted unsigned array [0, 1, 2, 2, 3, 3]
, which is equivalent to the signed array [0, 1, -2, -2, -1, -1]
. That's not properly sorted.
Now, let's apply the extra step for the signed bytes. We split the cumulative counts
array in two halves: [0, 1]
and [2, 4, (6)]
. There are 2 (2 - 0) elements in the first half and 4 (6 - 2) elements in the second half. So we add 4 to each element in the first half: [4, 5]
and subtract 2 from each element in the second half: [0, 2, (4)]
. Combining the halves gives [4, 5, 0, 2, (4)]
.
If we now use these counts as indices in the final unsigned array, we get [2, 2, 3, 3, 0, 1]
(the first 0 is at index 4, the first 1 at index 5, and so on). Converting this back to signed values gives [-2, -2, -1, -1, 0, 1]
, which is indeed correct.
Possible confusion: one of the confusing parts in this algorithm is that the counts
array is used for two different purposes. First it's used to count separate occurrences and later it's used to count cumulative occurrences. When counting separately, the first element of the array is not used. When counting cumulatively, the last element of the array is not used.
I think the algorithm would have been simpler if two separate arrays were used instead.
Upvotes: 3