Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions src/main/java/algorithms/sorting/radixSort/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Radix Sort

## Background

Radix Sort is a non-comparison based, stable sorting algorithm with a counting sort subroutine.

Radix Sort continuously sorts based on the least-significant segment of a element
to the most-significant value of a element.

The definition of a 'segment' is user defined and defers from implementation to implementation.
Within our implementation, we define each segment as a bit chunk.

For example, if we aim to sort integers, we can sort each element
from the least to most significant digit, with the digits being our 'segments'.

Within our implementation, we take the binary representation of the elements and
partition it into 8-bit segments, a integer is represented in 32 bits,
this gives us 4 total segments to sort through.

Note that the binary representation is weighted positional,
where each bit's value is dependent on its overall position
within the representation (the n-th bit from the right represents *2^n*),
hence we can actually increase / decrease the number segments we wish to conduct a split from.

![Radix Sort](https://miro.medium.com/v2/resize:fit:661/1*xFnpQ4UNK0TvyxiL8r1svg.png)

We place each element into a queue based on the number of possible segments that could be generated.
Suppose the values of our segments are in base-10, (limited to a value within range *[0, 9]*),
we get 10 queues. We can also see that radix sort is stable since
they are enqueued in a manner where the first observed element remains at the head of the queue

*Source: Level Up Coding*

### Implementation Invariant
At the start of the *i-th* segment we are sorting on, the array has already been sorted on the
previous *(i - 1)-th* segments.

### Common Misconceptions

While Radix Sort is non-comparison based,
the that total ordering of elements is still required.
This total ordering is needed because once we assigned a element to a order based on a segment,
the order *cannot* change unless deemed by a segment with a higher significance.
Hence, a stable sort is required to maintain the order as
the sorting is done with respect to each of the segments.

## Complexity Analysis
**Time**:
Let *b* be the length of a single element we are sorting and *r* is the amount of bit-string
we plan to break each element into.
(Essentially, *b/r* represents the number of segments we
sort on and hence the number of passes we do during our sort).

Note that we derive *(2^r + n)* from the counting sort subroutine,
since we have *2^r* represents the range since we have *r* bits.

We get a general time complexity of *O((b/r) * (2^r + n))*

**Space**: *O(n + 2^r)*

## Notes
- Radix sort's time complexity is dependent on the maximum number of digits in each element,
hence it is ideal to use it on integers with a large range and with little digits.
- This could mean that Radix Sort might end up performing worst on small sets of data
if any one given element has a in-proportionate amount of digits.
106 changes: 63 additions & 43 deletions src/main/java/algorithms/sorting/radixSort/RadixSort.java
Original file line number Diff line number Diff line change
@@ -1,53 +1,73 @@
package algorithms.sorting.radixSort;

/**
* Implementation of Radix sort.
* O((b/r)*(N + 2^r))
* where N is the number of integers,
* b is the total number of bits (32 bits for int),
* and r is the number of bits for each segment.
* Space: O(N) auxiliary space.
*/
* This class implements a Radix Sort Algorithm.
*/
public class RadixSort {
private static final int NUM_BITS = 8;
private static final int NUM_SEGMENTS = 4;

private static int getSegmentMasked(int num, int segment) {
int mask = ((1 << NUM_BITS) - 1) << (segment * NUM_BITS);
return (num & mask) >> (segment * NUM_BITS);
}
private static final int NUM_BITS = 8;
private static final int NUM_SEGMENTS = 4;

private static void radixSort(int[] arr, int[] sorted) {
// sort the N numbers by segments, starting from left-most segment
for (int i = 0; i < NUM_SEGMENTS; i++) {
int[] freqMap = new int[1 << NUM_BITS]; // at most this number of elements
/**
* Creates masking on the segment to obtain the value of the digit.
*
* @param num The number.
* @param segment The segment we are interested in.
*
* @return The value of the digit in the number at the given segment.
*/
private static int getSegmentMasked(int num, int segment) {
// Bit masking here to extract each segment from the integer.
int mask = ((1 << NUM_BITS) - 1) << (segment * NUM_BITS);
return (num & mask) >> (segment * NUM_BITS);
}

// count each element
for (int num : arr) {
freqMap[getSegmentMasked(num, i)]++;
}
// get prefix sum
for (int j = 1; j < freqMap.length; j++) {
freqMap[j] += freqMap[j-1];
}
// place each number in its correct sorted position up until the given segment
for (int k = arr.length-1; k >= 0; k--) {
int curr = arr[k];
int id = getSegmentMasked(curr, i);
sorted[freqMap[id] - 1] = curr;
freqMap[id]--;
}
// we do a swap so that our results above for this segment is saved and passed as input to the next segment
int[] tmp = arr;
arr = sorted;
sorted = tmp;
}
sorted = arr;
}
/**
* Radix sorts a given input array and updates the output array in-place.
*
* @param arr original input array.
* @param sorted output array.
*/
private static void radixSort(int[] arr, int[] sorted) {
// sort the N numbers by segments, starting from left-most segment
for (int i = 0; i < NUM_SEGMENTS; i++) {
int[] freqMap = new int[1 << NUM_BITS]; // at most this number of elements

public static void radixSort(int[] arr) {
int[] sorted = new int[arr.length];
radixSort(arr, sorted);
arr = sorted; // swap back lol
// count each element
for (int num : arr) {
freqMap[getSegmentMasked(num, i)]++;
}
// get prefix sum
for (int j = 1; j < freqMap.length; j++) {
freqMap[j] += freqMap[j - 1];
}
// place each number in its correct sorted position up until the given segment
for (int k = arr.length - 1; k >= 0; k--) {
int curr = arr[k];
int id = getSegmentMasked(curr, i);
sorted[freqMap[id] - 1] = curr;
freqMap[id]--;
}
// We do a swap so that our results above for this segment is
// saved and passed as input to the next segment.
// By doing this we no longer need to create a new array
// every time we shift to a new segment to sort.
// We can constantly reuse the array, allowing us to only use O(n) space.
int[] tmp = arr;
arr = sorted;
sorted = tmp;
}
sorted = arr;
}

/**
* Calls radix sort inplace on a given array.
*
* @param arr The array to be sorted.
*/
public static void radixSort(int[] arr) {
int[] sorted = new int[arr.length];
radixSort(arr, sorted);
arr = sorted; // swap back lol
}
}
39 changes: 39 additions & 0 deletions src/test/java/algorithms/sorting/radixSort/RadixSortTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package algorithms.sorting.radixSort;

import static org.junit.Assert.assertArrayEquals;

import java.util.Arrays;
import org.junit.Test;

public class RadixSortTest {
@Test
public void test_radixSort_shouldReturnSortedArray() {
int[] firstArray = new int[] {2, 3, 4, 1, 2, 5, 6, 7, 10, 15, 20, 13, 15, 1, 2,
15, 12, 20, 21, 120, 11, 5, 7, 85, 30};
int[] firstResult = Arrays.copyOf(firstArray, firstArray.length);
RadixSort.radixSort(firstResult);

int[] secondArray = new int[] {9, 1, 2, 8, 7, 3, 4, 6, 5, 5, 9, 8, 7, 6, 5, 4,
3, 2, 1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int[] secondResult = Arrays.copyOf(secondArray, secondArray.length);
RadixSort.radixSort(secondResult);

int[] thirdArray = new int[] {};
int[] thirdResult = Arrays.copyOf(thirdArray, thirdArray.length);
RadixSort.radixSort(thirdResult);

int[] fourthArray = new int[] {1};
int[] fourthResult = Arrays.copyOf(fourthArray, fourthArray.length);
RadixSort.radixSort(fourthResult);

Arrays.sort(firstArray);
Arrays.sort(secondArray);
Arrays.sort(thirdArray);
Arrays.sort(fourthArray);

assertArrayEquals(firstResult, firstArray);
assertArrayEquals(secondResult, secondArray);
assertArrayEquals(thirdResult, thirdArray);
assertArrayEquals(fourthResult, fourthArray);
}
}