Skip to content

排序

更新: 10/24/2025 字数: 0 字 时长: 0 分钟

1.稳定性

如果在排序过程中,两个相等的元素在排序前后的相对位置不变,则称该排序算法是稳定的(Stable Sort)。否则,该排序算法是不稳定的(Unstable Sort)。

比如数组:[5, 3₁, 2, 3₂, 1]3₁3₂是两个值相同但不同的元素),在排序后:

  • 如果变为:[1, 2, 3₁, 3₂, 5],则说明排序算法是稳定的。
  • 如果变为:[1, 2, 3₂, 3₁, 5],则说明排序算法是不稳定的。

对自定义对象进行排序时,如果排序的关键字相同,且希望保持它们在原始数组中的相对位置不变,则应选择稳定的排序算法。

2.原地算法

原地算法(In-place Algorithm)是指在排序过程中只使用常数级别的额外空间(即空间复杂度为 O(1)),而不依赖于额外的数据结构来存储数据。换句话说,原地算法通过在原始数据结构上进行修改来实现排序,而不是创建一个新的数据结构来存储排序后的结果。

3.封装公共父类

在所有排序算法中,均有一些公共的逻辑,比如:交换、比较大小等操作,可以将这些公共逻辑封装到一个抽象类中,还可以进行一些指标统计,所有排序算法均继承自该抽象类,并实现sort()方法,如下:

java
import java.text.DecimalFormat;

/**
 * 所有排序算法的基类
 * 实现 `Comparable<Sort>` 接口,可以用于比较不同排序算法的性能
 *
 * @param <E> 元素类型,要求实现 Comparable 接口
 * @author yolk
 * @since 2025/10/6 15:47
 */
public abstract class Sort<E extends Comparable<E>> implements Comparable<Sort<E>> {

    // 待排序的数组
    protected E[] array;

    // 统计比较次数
    protected int cmpCount;

    // 统计交换次数
    protected int swapCount;

    // 当比较次数和交换次数过大时,缩短显示
    private static final DecimalFormat fmt = new DecimalFormat("#.00");

    // 耗时(毫秒数)
    private long time;

    /**
     * 对外提供的排序接口
     */
    public void sort(E[] arr) {
        if (arr == null || arr.length < 2) {
            return;
        }

        // 赋值给成员变量,子类可以直接使用
        this.array = arr;

        long begin = System.currentTimeMillis();
        sort();
        long end = System.currentTimeMillis();

        // 记录排序耗时
        this.time = end - begin;
    }

    /**
     * 具体的排序算法由子类实现
     */
    protected abstract void sort();

    /**
     * 基于索引比较 array[i1] 和 array[i2] 的大小
     *
     * @param i1
     * @param i2
     * @return =0: array[i1] == array[i2]
     * <0: array[i1] < array[i2]
     * >0: array[i1] > array[i2]
     */
    protected int compare(int i1, int i2) {
        // 比较次数加 1
        cmpCount++;

        return array[i1].compareTo(array[i2]);
    }

    /**
     * 直接比较元素 v1 和 v2 的大小
     *
     * @param v1
     * @param v2
     * @return =0: v1 == v2
     * <0: v1 < v2
     * >0: v1 > v2
     */
    protected int compare(E v1, E v2) {
        // 比较次数加 1
        cmpCount++;

        return v1.compareTo(v2);
    }

    /**
     * 交换 array[i1] 和 array[i2] 的值
     *
     * @param i1
     * @param i2
     */
    protected void swap(int i1, int i2) {
        // 如果索引 i1 和 i2 相等,则不交换
        if (i1 == i2) {
            return;
        }
        // 交换次数加 1
        swapCount++;

        // 交换 array[i1] 和 array[i2]
        E tmp = array[i1];
        array[i1] = array[i2];
        array[i2] = tmp;
    }

    /**
     * 重写 toString 方法,打印排序效率统计信息
     */
    @Override
    public String toString() {
        String countStr = "数据规模:" + numberString(array.length);
        String timeStr = "耗时:" + (time / 1000.0) + "s(" + time + "ms)";
        String compareCountStr = "比较:" + numberString(cmpCount);
        String swapCountStr = "交换:" + numberString(swapCount);
        return "【" + getClass().getSimpleName() + "】\n"
                + countStr + "\t "
                + timeStr + " \t"
                + compareCountStr + "\t "
                + swapCountStr + "\n"
                + "------------------------------------------------------------------";

    }

    /**
     * 当比较次数和交换次数过大时,缩短显示
     * 当数字小于 1w 时,直接显示数字
     * 当数字小于 1亿 时,显示为 xx.xx 万
     * 否则显示为 xx.xx 亿
     */
    private String numberString(int number) {
        if (number < 10000) return "" + number;

        if (number < 100000000) return fmt.format(number / 10000.0) + "万";
        return fmt.format(number / 100000000.0) + "亿";
    }

    /**
     * 重写 compareTo 方法,比较两个排序算法的效率
     * 先比较耗时,耗时少的排在前面
     * 如果耗时相同,则比较比较次数,比较次数少的排在前面
     * 如果比较次数也相同,则比较交换次数,交换次数少的排在前面
     *
     * @param o 另一个排序算法对象
     * @return <0:当前算法效率更高
     * >0:另一个算法效率更高
     * =0: 效率相同
     */
    @Override
    public int compareTo(Sort o) {
        int result = Math.toIntExact(this.time - o.time);
        if (result != 0) {
            return result;
        }
        result = this.cmpCount - o.cmpCount;
        if (result != 0) {
            return result;
        }

        return this.swapCount - o.swapCount;
    }
}

3.种类

4.对比

名称最好最坏平均额外空间复杂度In-place稳定性
冒泡排序(Bubble Sort)O(n)O(n²)O(n²)O(1)
选择排序(Selection Sort)O(n²)O(n²)O(n²)O(1)
插入排序(Insertion Sort)O(n)O(n²)O(n²)O(1)
归并排序(Merge Sort)O(nlogn)O(nlogn)O(nlogn)O(n)
快速排序(Quick Sort)O(nlogn)O(n²)O(nlogn)O(logn)
希尔排序(Shell Sort)O(nlogn)O(n4/3) ~ O(n²)取决于步长序列O(1)
堆排序(Heap Sort)O(nlogn)O(nlogn)O(nlogn)O(1)
基数排序(Radix Sort)O(d * (n+k))O(d * (n+k))O(d * (n+k))O(n+k)
桶排序(Bucket Sort)O(n+k)O(n+k)O(n+m)O(n+m)
计数排序(Counting Sort)O(n+k)O(n+k)O(n+k)O(n+k)

注意

  • 以上表格是基于数组类型的数据结构进行排序的一般性总结,并且每种算法实现方式也有所不同,所以此分析仅供参考
  • 冒泡、选择、插入、归并、快速、希尔、堆排序是(Comparison Sorting)

4.1.性能对比测试代码

java
import sort.compare.bubble.BubbleSort3;
import sort.compare.heap.HeapSort;
import sort.compare.insertion.InsertionSort3;
import sort.compare.merge.MergeSort;
import sort.compare.quick.QuickSort;
import sort.compare.selection.SelectionSort;
import sort.compare.shell.ShellSort;
import sort.counting.CountingSort2;
import sort.radix.RadixSort;
import utils.Integers;

import java.util.Arrays;
import java.util.List;

/**
 * 比较每种排序的性能
 *
 * @author yolk
 * @since 2025/10/6 14:30
 */
public class Test {

    public static void main(String[] args) {
        Integer[] arr = Integers.random(10000, 1, 10_0000);
        List<Sort<Integer>> sorts = Arrays.asList(
                // new BubbleSort1<>(),
                // new BubbleSort2<>(),
                new BubbleSort3<>(),
                new SelectionSort<>(),
                new HeapSort<>(),
                // new InsertionSort1<>(),
                // new InsertionSort2<>(),
                new InsertionSort3<>(),
                new MergeSort<>(),
                new QuickSort<>(),
                new ShellSort<>(),
                // new CountingSort1(),
                new CountingSort2(),
                new RadixSort()
        );

        testSorts(arr, sorts);
    }

    public static void testSorts(Integer[] array, List<Sort<Integer>> sorts) {
        for (Sort<Integer> sort : sorts) {
            // 复制一份数组,避免排序算法之间相互影响
            Integer[] arr = Integers.copy(array);

            sort.sort(arr);
        }

        // 使用 Sort 类实现的 Comparable 接口,进行排序,以比较效率
        sorts.sort(null);
        System.out.println("各种排序效率从高到低:");
        for (Sort<Integer> sort : sorts) {
            System.out.println(sort);
        }
    }
}

Released under the MIT License.

本站访客数 人次 本站总访问量