每日算法 — 使用java实现插入排序:直接插入与折半插入优化

算法概述

插入排序(Insertion Sort)是一种直观且高效的稳定排序算法,其核心思想是将待排序元素逐个插入到已排序序列的正确位置。对于小规模数据或接近有序的数据,插入排序的性能甚至可以超越快速排序。本文将用Java实现基础插入排序,并引入折半查找优化定位插入点,将比较复杂度从O(n)降至O(log n)。

核心原理

假设数组前i个元素已经有序,第i+1个元素需要找到合适的位置插入。基础做法是从后向前线性扫描已排序区间;优化做法是使用二分查找在已排序区间中定位插入点,再通过批量移动元素完成插入。

完整Java实现

public class InsertionSort {

    /**
     * 基础插入排序:从后向前线性扫描找到插入位置
     * 时间复杂度:最坏O(n²),最好O(n),平均O(n²)
     * 空间复杂度:O(1)
     * 稳定性:稳定
     */
    public static void basicInsertionSort(int[] arr) {
        int n = arr.length;
        for (int i = 1; i < n; i++) {
            int key = arr[i];
            int j = i - 1;
            // 从后向前扫描,将大于key的元素后移
            while (j >= 0 && arr[j] > key) {
                arr[j + 1] = arr[j];
                j--;
            }
            arr[j + 1] = key;
        }
    }

    /**
     * 折半插入排序:使用二分查找定位插入点
     * 比较次数降至O(n log n),移动次数仍为O(n²)
     */
    public static void binaryInsertionSort(int[] arr) {
        int n = arr.length;
        for (int i = 1; i < n; i++) {
            int key = arr[i];
            // 在arr[0..i-1]中使用二分查找定位插入点
            int left = 0, right = i - 1;
            while (left <= right) {
                int mid = left + (right - left) / 2;
                if (arr[mid] > key) {
                    right = mid - 1;
                } else {
                    left = mid + 1;
                }
            }
            // left即为插入位置,将[left, i-1]的元素后移一位
            for (int j = i - 1; j >= left; j--) {
                arr[j + 1] = arr[j];
            }
            arr[left] = key;
        }
    }

    /**
     * 带哨兵的插入排序优化:减少边界判断
     */
    public static void sentinelInsertionSort(int[] arr) {
        int n = arr.length;
        // 找到最小元素放到arr[0]作为哨兵
        int minIdx = 0;
        for (int i = 1; i < n; i++) {
            if (arr[i] < arr[minIdx]) {
                minIdx = i;
            }
        }
        int temp = arr[0];
        arr[0] = arr[minIdx];
        arr[minIdx] = temp;

        for (int i = 2; i < n; i++) {
            int key = arr[i];
            int j = i - 1;
            // 有哨兵保护,无需j>=0判断
            while (arr[j] > key) {
                arr[j + 1] = arr[j];
                j--;
            }
            arr[j + 1] = key;
        }
    }

    public static void main(String[] args) {
        int[] arr1 = {64, 34, 25, 12, 22, 11, 90};
        int[] arr2 = arr1.clone();
        int[] arr3 = arr1.clone();

        basicInsertionSort(arr1);
        System.out.println("基础插入排序: " + java.util.Arrays.toString(arr1));

        binaryInsertionSort(arr2);
        System.out.println("折半插入排序: " + java.util.Arrays.toString(arr2));

        sentinelInsertionSort(arr3);
        System.out.println("哨兵插入排序: " + java.util.Arrays.toString(arr3));
    }
}

复杂度分析

版本 最好时间 平均时间 最坏时间 空间 比较次数 移动次数
基础插入 O(n) O(n²) O(n²) O(1) O(n²) O(n²)
折半插入 O(n) O(n²) O(n²) O(1) O(n log n) O(n²)
哨兵插入 O(n) O(n²) O(n²) O(1) O(n²) O(n²)

折半插入排序虽然比较次数减少,但元素移动次数不变,因此在元素较大的场景下优势有限;当比较操作代价高于移动操作时(如字符串排序),折半优化效果显著。

应用场景

  • 数据量较小(n < 50)时的首选排序算法
  • 数据基本有序时的最优选择
  • 作为快速排序/归并排序的递归终止条件(小规模切到插入排序)
  • 在线排序场景(数据流逐个到达)