排序算法代码

167 阅读5分钟

这一节,我们对这一章所学习的排序算法做一个总结。

平均时间复杂度 最坏时间复杂度 最好时间复杂度 空间复杂度 是否原地排序 是否稳定
1、选择排序 O(N^2) O(N^2) O(N^2) O(1)
2、堆排序 O(N \log N) O(N \log N) O(N \log N) O(1)
3、冒泡排序 O(N^2) O(N^2) O(N^2) O(1) 稳定
4、插入排序 O(N^2) O(N^2) O(N) O(1) 稳定
5、希尔排序 (暂不讨论) (暂不讨论) (暂不讨论) O(1)
6、归并排序 O(N \log N) O(N \log N) O(N \log N) O(N  + \log N) 稳定
7、快速排序 O(N \log N) O(N^2) O(N \log N) O(\log N)
8、桶排序 O(K + N) O(N^2) O(K + N) O(K + N) 稳定
9、计数排序 O(K + N) O(K + N) O(K + N) O(K + N) 稳定
10、基数排序 O(KN) O(KN) O(KN) O(K + N) 稳定

Partition 测试

package cn.leetcode.sorting;

import cn.leetcode.sorting.utils.SortingUtil;

import java.util.Arrays;

public class Partition {

    public static void main(String[] args) {
        int[] arr = new int[]{4, 5, 1, 6, 7, 3, 2};
        int pivot = arr[0];

        // [1, j - 1] < pivot
        // [j, i - 1] >= pivot
        int j = 1;
        for (int i = 1; i < arr.length; i++) {
            if (arr[i] < pivot) {
                SortingUtil.swap(arr, i, j);
                j++;
            }
            System.out.println(Arrays.toString(arr));
        }
        SortingUtil.swap(arr, 0, j - 1);
        System.out.println(Arrays.toString(arr));
    }
}

测试选择排序

Java 代码:

package cn.leetcode.sorting;

import cn.leetcode.sorting.utils.SortingUtil;

public class SelectionSort implements ISortingAlgorithm {

    @Override
    public void sort(int[] arr) {
        int len = arr.length;
        for (int i = 0; i < len - 1; i++) {
            // 在区间 [i, len - 1] 中找到最小元素的索引
            int minIndex = i;
            for (int j = i + 1; j < len; j++) {
                if (arr[j] < arr[minIndex]) {
                    minIndex = j;
                }
            }
            SortingUtil.swap(arr, i, minIndex);
        }
    }

    public static void main(String[] args) {
        int[] randomArray = SortingUtil.generateRandomArray(10000, 1, 10000);
        SortingUtil.testSortingAlgorithm(new SelectionSort(), randomArray);

        int[] randomArray2 = SortingUtil.generateRandomArray(20000, 1, 20000);
        SortingUtil.testSortingAlgorithm(new SelectionSort(), randomArray2);

        int[] randomArray3 = SortingUtil.generateRandomArray(40000, 1, 10000);
        SortingUtil.testSortingAlgorithm(new SelectionSort(), randomArray3);
    }
}

Java 代码:

package cn.leetcode.sorting;

public interface ISortingAlgorithm {
    void sort(int[] arr);
}

选择排序:

import java.util.Arrays;

public class HelloAlgorithms {

    /**
     * 时间复杂度
     * 空间复杂度
     *
     * @param args
     */
    public static void main(String[] args) {
        int[] arr = {3, 4, 5, 4, 2, 1};

        int len = arr.length;
        // 循环不变量:在未排定区间区间 [0, i) 中包含原始数组已经排序的 i 个最小元素
        // 初始时:[0, 0) 为空
        // 遍历中:[0, i) 我们总是把未排定区域中的最小元素放在索引 i 这个位置,在 i 逐渐增大的过程中保持这个性质,
        // 结束:[0, len - 2] ,最后一个元素一定最大,因此 [0, len - 1] 有序
        // 贪心算法:循环不变量保证了这道问题使用贪心算法是有效的


        for (int i = 0; i < len - 1; i++) {
            System.out.println(Arrays.toString(arr));

            // 在区间 [i, len - 1] 中找到最小元素的索引
            int minIndex = i;
            for (int j = i + 1; j < len; j++) {
                if (arr[j] < arr[minIndex]) {
                    minIndex = j;
                }
            }
            swap(arr, i, minIndex);
        }
        System.out.println(Arrays.toString(arr));
    }

    private static void swap(int[] arr, int index1, int index2) {
        int temp = arr[index1];
        arr[index1] = arr[index2];
        arr[index2] = temp;
    }
}

排序工具类:

package cn.leetcode.sorting.utils;

import cn.leetcode.sorting.ISortingAlgorithm;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.Duration;
import java.time.Instant;
import java.util.Arrays;
import java.util.Random;

public class SortingUtil {

    /**
     * 随机数种子
     */
    public static final Random RANDOM = new Random(System.currentTimeMillis());

    /**
     * 测试方法执行的次数
     */
    private static final int TEST_TIMES = 3;

    /**
     * 小数点后保留几位有效数字
     */
    private static final int SCALE = 8;

    /**
     * 将毫秒转化为秒的分母常量
     */
    private static final BigDecimal DIVISOR = new BigDecimal("1000");

    /**
     * 打印数组
     *
     * @param nums 待打印数组
     */
    public static void printArray(int[] nums) {
        int len = nums.length;
        System.out.print("[");
        for (int i = 0; i < len; i++) {
            System.out.print(nums[i]);
            if (i == len - 1) {
                System.out.println("]");
                break;
            }
            System.out.print(", ");
        }
        // 使用 Java,可以用下面这一行代码代替
        // System.out.println(Arrays.toString(nums));
    }

    /**
     * 交换数组中两个索引位置的元素
     *
     * @param nums   数组
     * @param index1 索引 1
     * @param index2 索引 2
     */
    public static void swap(int[] nums, int index1, int index2) {
        // 严格意义上说,这里需要对 index1 和 index2 的有效性做校验
        int temp = nums[index1];
        nums[index1] = nums[index2];
        nums[index2] = temp;
    }

    /**
     * 复制数组:根据已经有的数组得到一个新的数组(用于比较排序算法的效率)
     *
     * @param nums 待复制数组
     * @return 与 nums 相等的数值
     */
    public static int[] copyFromArray(int[] nums) {
        int len = nums.length;
        int[] newArr = new int[len];
        // Java 可以使用下面一行代码
        // System.arraycopy(nums, 0, newArr, 0, len);
        for (int i = 0; i < len; i++) {
            newArr[i] = nums[i];
        }
        return newArr;
    }

    /**
     * 判断两个数组是否相等,通过逐个比对的方式比较两个数组是否相等
     *
     * @param arr1 待比较的数组 1
     * @param arr2 待比较的数组 2
     */
    public static void judgeArrayEquals(int[] arr1, int[] arr2) {
        int len1 = arr1.length;
        int len2 = arr2.length;
        if (len1 != len2) {
            throw new RuntimeException("两个数组长度不相等,请检查!");
        }
        for (int i = 0; i < len1; i++) {
            if (arr1[i] != arr2[i]) {
                throw new RuntimeException("您编写的排序算法错误!");
            }
        }
    }

    /**
     * 生成随机整型数组
     *
     * @param len 生成随机整型数组的长度
     * @param min 生成随机整型中元素的最小值(可以取到)
     * @param max 生成随机整型中元素的最大值(可以取到)
     * @return 一个随机数组
     */
    public static int[] generateRandomArray(int len, int min, int max) {
        // 参数校验
        assert len > 0;
        if (min > max) {
            int temp = max;
            max = min;
            min = temp;
        }

        int[] randomArray = new int[len];
        for (int i = 0; i < len; i++) {
            // nextInt(n) 生成 [0, n) 的随机整数
            randomArray[i] = min + RANDOM.nextInt(max - min + 1);
        }
        return randomArray;
    }


    /**
     * 测试排序算法正确性、计算排序算法执行时间
     *
     * @param sortingAlgorithm 我们自己编写的排序算法实现对象
     */
    public static void testSortingAlgorithm(ISortingAlgorithm sortingAlgorithm, int[] arr) {
        // 多运行几次,避免我们编写的算法恰好"蒙混过关"
        for (int i = 0; i < TEST_TIMES; i++) {
            System.out.print(String.format("生成第 %d 个数组,", i + 1));
            // 根据一定的策略生成测试用例数组
            // 生成测试用例数组的拷贝
            int[] randomArrayCopy = SortingUtil.copyFromArray(arr);

            // 将计时逻辑封装到一个函数中,更好的做法是使用动态代理或者过滤器
            timingSortingAlgorithm(sortingAlgorithm, arr);

            // 使用系统库函数对 randomArrayCopy 进行排序
            Arrays.sort(randomArrayCopy);

            // 逐个比较两个排序以后的数组元素,以验证我们编写的排序算法的正确性
            SortingUtil.judgeArrayEquals(arr, randomArrayCopy);
        }
        System.out.println("您编写的排序算法正确!\n");
    }

    /**
     * 统计排序算法耗时
     *
     * @param sortingAlgorithm 排序算法,传入我们自己编写的排序算法实现对象
     * @param nums             待排序数组
     */
    private static void timingSortingAlgorithm(ISortingAlgorithm sortingAlgorithm, int[] nums) {
        // 使用我们的算法对 nums 进行排序
        Instant startTime = Instant.now();
        sortingAlgorithm.sort(nums);
        Instant endTime = Instant.now();

        // 以毫秒为单位
        long millis = Duration.between(startTime, endTime).toMillis();
        // 向上取整
        BigDecimal spendBigDecimal = new BigDecimal(String.valueOf(millis)).divide(DIVISOR, SCALE, RoundingMode.CEILING);
        System.out.print(String.format("耗时 %s 秒。\n", spendBigDecimal.toString()));
    }

}