1. 引言
在本教程中,我们将了解 Radix Sort,分析其性能,并了解其实现。
在这里,我们重点介绍使用 Radix Sort 对整数进行排序,但它不仅限于数字。我们也可以使用它来对其他类型进行排序,例如 String。
为了简单起见,我们将重点介绍数字以基数(基数)10 表示的十进制系统。
2. 算法概述
基数排序是一种排序算法,它根据数字的位置对数字进行排序。基本上,它使用数字中数字的位值。与大多数其他排序算法(例如合并排序、插入排序、冒泡排序)不同,它不会比较数字。
基数排序使用稳定的排序算法作为子程序对数字进行排序。我们在这里使用了计数排序的变体作为子例程,它使用基数对每个位置的数字进行排序。计数排序是一种稳定的排序算法,在实践中效果很好。
基数排序的工作原理是将数字从最低有效数字 (LSD) 排序到最高有效数字 (MSD)。我们还可以实现基数排序来处理来自 MSD 的数字。
3. 一个简单的例子
让我们通过一个示例看看它是如何工作的。让我们考虑以下数组:
3.1. 迭代 1
我们将通过处理来自 LSD 的数字并移向 MSD 来对这个数组进行排序。
因此,让我们从一个位置的数字开始:
在第一次迭代之后,数组现在如下所示:
请注意,数字已根据某个位置的数字进行排序。
3.2. 迭代 2
让我们继续看十位数字:
现在数组如下所示:
我们看到数字 7 占据了数组中的第一个位置,因为它在十位没有任何数字。我们也可以将其视为在十位有一个 0。
3.3. 迭代 3
让我们继续讨论百位位置的数字:
完成此迭代后,数组如下所示:
算法到此为止,所有元素都已排序。
4. 实施
现在让我们看一下实现。
void sort(int[] numbers) {
int maximumNumber = findMaximumNumberIn(numbers);
int numberOfDigits = calculateNumberOfDigitsIn(maximumNumber);
int placeValue = 1;
while (numberOfDigits-- > 0) {
applyCountingSortOn(numbers, placeValue);
placeValue *= 10;
}
}
该算法的工作原理是找出数组中的最大数字,然后计算其长度。此步骤有助于我们确保对每个位值执行子例程。
例如,在数组 [7, 37, 68, 123, 134, 221, 387, 468, 769] 中,最大数字为 769,长度为 3。
因此,我们在每个位置的数字上迭代并应用子程序三次:
void applyCountingSortOn(int[] numbers, int placeValue) {
int range = 10 // decimal system, numbers from 0-9
// ...
// calculate the frequency of digits
for (int i = 0; i < length; i++) {
int digit = (numbers[i] / placeValue) % range;
frequency[digit]++;
}
for (int i = 1; i < range; i++) {
frequency[i] += frequency[i - 1];
}
for (int i = length - 1; i >= 0; i--) {
int digit = (numbers[i] / placeValue) % range;
sortedValues[frequency[digit] - 1] = numbers[i];
frequency[digit]--;
}
System.arraycopy(result, 0, numbers, 0, length);
}
在子例程中,我们使用基数(范围)来计算每个数字的出现次数并增加其频率。因此,从 0 到 9 范围内的每个 bin 都会根据数字的频率获得一些值。然后,我们使用频率来定位数组中的每个元素。这也有助于我们最小化对数组进行排序所需的空间。
现在让我们测试一下我们的方法:
@Test
void givenUnsortedArray_whenRadixSort_thenArraySorted() {
int[] numbers = {387, 468, 134, 123, 68, 221, 769, 37, 7};
RadixSort.sort(numbers);
int[] numbersSorted = {7, 37, 68, 123, 134, 221, 387, 468, 769};
assertArrayEquals(numbersSorted, numbers);
}
5. 基数排序与计数排序
在子程序中,频率阵列的长度为 10 (0-9)。在计数排序的情况下,我们不使用范围。频率数组的长度将是数组中的最大数字 + 1。因此,我们不会将它们划分为箱,而 Radix Sort 使用箱进行排序。
当数组的长度不比数组中的最大值小多少时,计数排序非常有效,而基数排序允许数组中的较大值。
6. 复杂性
Radix Sort 的性能取决于选择对数字进行排序的稳定排序算法。
在这里,我们使用基数排序对以 b 为基数的 n 个数字数组进行排序。在我们的例子中,基数是 10。我们应用了计数排序 d 次,其中 d 代表位数。因此,基数排序的时间复杂度变为 O(d * (n + b))。
空间复杂度为 O(n + b),因为我们在这里使用了 Counting Sort 的变体作为子程序。
7. 结论
在本文中,我们描述了 Radix 排序算法,并说明了如何实现它。