###题目
给定一个整数数组,其中不同的整数所包含的数字的位数可能不同,但该数组中,所有整数中包含的总数字为n。设计一个算法,使其可以在O(n)时间内对该数组进行排序。
###算法思路
首先,我们可以用计数排序按数字的位数进行排序。然后,用基数排序来对每组数位相同的数字进行排序。
每个数字的数位在1到n之间,令i为数字的位数,为位数为i的数字的个数。由于一共n位数,So,我们有如下公式:
###Java代码实现
package hanxl.insist.eightchapter;
import java.util.Arrays;
public class Exercise_8_3 {
public static void main(String[] args) {
int[] a = { 133, 2, 433, 124, 3432434, 2322, 2345, 1, 231, 12, 45, 84, 21, 3457, 132356, 12, 5, 67773, 233 };
System.out.println(Arrays.toString(Exercise_8_3.sort(a)));
}
public static int[] sort(int[] a) {
int maximumDigit = getMaximumDigit(a); // θ(n)
int[] count = new int[maximumDigit];
int[] countingSort = countingSort(a, count); // θ(n)
int lo = 0;
int hi = 0;
for (int i = 0; i < count.length; i++) {
int nums = count[i];
if (nums > 1) {
lo = hi;
hi += nums;
radixSort(countingSort, lo, hi, i + 1);
}
}
return countingSort;
}
/**
* 基数排序
*
* @param a
* 待排序的数组
* @param d
* 数组中元素的位数
*/
public static void radixSort(int[] a, int lo, int hi, int d) {
int[] countArray = new int[10]; // 记录每个基数的数量
int[][] kindArray = new int[10][hi]; // 把数组a中的元素按照基数归类,一维的含义是0-9个数字,二维的含义是含有一维基数的a中的元素
int n = 1;
while (d-- > 0) {
int k = lo;
for (int i = lo; i < hi; i++) {
int radix = (a[i] / n) % 10; // n的作用在这体现出来,每一轮while循环,n就会扩大10倍
kindArray[radix][countArray[radix]] = a[i];
countArray[radix]++;
}
// 将每个基数排完序后的结果重新放入a数组中
for (int i = 0; i < kindArray.length; i++) {
for (int j = 0; j < countArray[i]; j++) {
a[k] = kindArray[i][j];
k++;
}
countArray[i] = 0; // 为了其它的基数计算,计数数组必须清0
}
n *= 10;
}
}
/**
* 按照数字的位数进行计数排序
*
* @param a
* @param count
* @param nums
* @return
*/
private static int[] countingSort(int[] a, int[] count) {
int[] sortedArr = new int[a.length];
for (int i = 0; i < a.length; i++) { // θ(n) 和getMaximumDigit方法同理
int currentDigit = getNumDigit(a[i]) - 1; // 由于数组下标从0开始,所以下标为0的代表1位数字,下标为1的代表2位数字......
count[currentDigit]++;
}
int[] nums = count.clone(); // 为了保留count数组中的元素不被破坏 θ(count.length) count.length <= n
for (int i = 1; i < nums.length; i++) //θ(count.length)
nums[i] += nums[i - 1];
for (int i = a.length - 1; i >= 0; i--) { // θ(n) 和getMaximumDigit方法同理
int currentDigit = getNumDigit(a[i]) - 1; // 由于数组下标从0开始,所以下标为0的代表1位数字,下标为1的代表2位数字......
sortedArr[nums[currentDigit] - 1] = a[i];
nums[currentDigit]--;
}
return sortedArr;
}
/**
* 获取给定数组中所有元素中的最大位数
*/
public static int getMaximumDigit(int[] a) {
int largest = getNumDigit(a[0]);
for (int i = 1; i < a.length; i++) {
int currentDigit = getNumDigit(a[i]);
if (currentDigit > largest)
largest = currentDigit;
}
return largest;
}
/**
* 获取整数的位数
*/
private static int getNumDigit(int num) {
int digit = 0;
while (num > 0) {
digit++;
num /= 10;
}
return digit;
}
}
###算法时间复杂度分析
getMaximumDigit方法虽然for循环中嵌套着一个while循环,但是这个子程序的目的是求出数组中所有元素中的最大位数。也就是说它会遍历每个数字的每一位,由于总位数为n,所以算法的时间复杂度为。countingSort这个子程序为的时间复杂度为O(n)。接着下面的for循环和radixSort方法的目的是排序具有相同位数的数字,那么它也会遍历数组中每一位元素的所有数位,所以它的时间复杂度为O(n)。
所以,算法的总时间复杂度为O(n)。