算法导论 8-3思考题 之变长数据项的排序

题目

给定一个整数数组,其中不同的整数所包含的数字的位数可能不同,但该数组中,所有整数中包含的总数字为n。设计一个算法,使其可以在O(n)时间内对该数组进行排序。

算法思路

首先,我们可以用计数排序按数字的位数进行排序。然后,用基数排序来对每组数位相同的数字进行排序。

每个数字的数位在1到n之间,令i为数字的位数, mi 为位数为i的数字的个数。由于一共n位数,So,我们有如下公式:

i=1nimi=n

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,所以算法的时间复杂度为 θ(n) 。countingSort这个子程序为的时间复杂度为O(n)。接着下面的for循环和radixSort方法的目的是排序具有相同位数的数字,那么它也会遍历数组中每一位元素的所有数位,所以它的时间复杂度为O(n)。

所以,算法的总时间复杂度为O(n)。

你可能感兴趣的:(算法导论)