rokevin
移动
前端
语言
  • 基础

    • Linux
    • 实施
    • 版本构建
  • 应用

    • WEB服务器
    • 数据库
  • 资讯

    • 工具
    • 部署
开放平台
产品设计
  • 人工智能
  • 云计算
计算机
其它
GitHub
移动
前端
语言
  • 基础

    • Linux
    • 实施
    • 版本构建
  • 应用

    • WEB服务器
    • 数据库
  • 资讯

    • 工具
    • 部署
开放平台
产品设计
  • 人工智能
  • 云计算
计算机
其它
GitHub
  • 插入排序

  • 算法定义
  • 算法思路
  • 算法复杂度
  • 优点与缺点
  • 应用场景
  • 各版本优化说明
  • 代码

插入排序

算法定义

插入排序(Insertion Sort)是一种简单直观的排序算法,它的核心思想是将数组分为 “已排序” 和 “未排序” 两部分,每次从 “未排序” 部分取出一个元素,插入到 “已排序” 部分的合适位置,直到所有元素都被插入完毕。

这种排序方式类似于人们整理扑克牌的过程 —— 每次拿起一张牌,将其插入到手中已有序的牌组中的正确位置。

算法思路

插入排序的具体步骤如下:

  1. 初始时,将数组的第一个元素视为 “已排序” 部分(只有一个元素的数组天然有序)。
  2. 从第二个元素开始,依次取出 “未排序” 部分的元素,将其与 “已排序” 部分的元素从后向前比较。
  3. 如果 “已排序” 部分的元素大于当前待插入元素,则将该元素后移一位。
  4. 找到合适的插入位置后,将待插入元素放入该位置。
  5. 重复步骤 2-4,直到所有元素都被插入到 “已排序” 部分。

算法复杂度

  • 最好时间复杂度:O (n) - 当数组已经有序时,只需遍历一次,无需移动元素
  • 最坏时间复杂度:O (n²) - 当数组完全逆序时,每个元素都需要移动到最前面
  • 平均时间复杂度:O(n²)
  • 空间复杂度:O (1) - 只需一个临时变量存储待插入元素,属于原地排序

优点与缺点

  • 优点:
    • 实现简单,代码简洁易懂
    • 原地排序,空间复杂度低(O (1))
    • 稳定的排序算法(相等元素的相对位置保持不变)
    • 对几乎有序的数组或小规模数据效率很高
    • 适合增量排序(可以高效处理动态新增的数据)
  • 缺点:
    • 时间复杂度为 O (n²),不适合大规模数据排序
    • 元素移动次数较多(最坏情况下需要 O (n²) 次移动)
    • 对逆序数组处理效率低

应用场景

  • 小规模数据的排序(如长度小于 100 的数组)
  • 几乎有序的数组(此时效率接近 O (n))
  • 作为复杂排序算法的子过程(如快速排序处理小规模子数组时)
  • 数据流式输入的场景(可以边接收数据边排序)
  • 链表排序(插入操作无需移动元素,只需修改指针)

各版本优化说明

  1. 基本版:
    • 最直观的实现,依次将元素插入到已排序部分的正确位置
    • 逻辑清晰,但比较和移动次数较多
  2. 减少交换次数优化:
    • 基本版的改进版,将多次交换操作改为一次赋值
    • 通过先暂存待插入元素,再将大于它的元素后移,最后插入
    • 减少了一半的数据移动操作,性能有所提升
  3. 二分查找优化:
    • 用二分查找代替线性查找来确定插入位置,将比较次数从 O (n) 降至 O (log n)
    • 适合数据量较大的情况,但元素移动次数仍为 O (n)
    • 总体时间复杂度仍为 O (n²),但常数因子更小
  4. 希尔排序(分组插入排序):
    • 插入排序的重大改进,通过分组(按步长 gap)进行插入排序
    • 先让数组整体基本有序,最后再进行一次普通插入排序
    • 时间复杂度可提升至 O (n^1.3) 左右,适合中等规模数据

插入排序是一种非常实用的排序算法,尤其是在处理小规模数据或几乎有序的数据时表现出色。许多编程语言的标准库中,在处理小规模子数组时都会选择插入排序(如 Java 的Arrays.sort())。

代码

import java.util.Arrays;

public class InsertionSort {
    
    // 1. 基本版插入排序
    public static void insertionSortBasic(int[] arr) {
        if (arr == null || arr.length <= 1) {
            return;
        }
        
        int n = arr.length;
        // 从第二个元素开始遍历未排序部分
        for (int i = 1; i < n; i++) {
            int key = arr[i]; // 当前待插入元素
            int j = i - 1;    // 已排序部分的最后一个元素索引
            
            // 从后向前比较已排序部分,找到插入位置
            while (j >= 0 && arr[j] > key) {
                arr[j + 1] = arr[j]; // 元素后移
                j--;
            }
            arr[j + 1] = key; // 插入元素
        }
    }
    
    // 2. 优化版1:减少元素交换次数(使用赋值代替交换)
    public static void insertionSortOptimized1(int[] arr) {
        if (arr == null || arr.length <= 1) {
            return;
        }
        
        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;
        }
    }
    
    // 3. 优化版2:二分查找优化(减少比较次数)
    public static void insertionSortBinarySearch(int[] arr) {
        if (arr == null || arr.length <= 1) {
            return;
        }
        
        int n = arr.length;
        for (int i = 1; i < n; i++) {
            int key = arr[i];
            // 二分查找找到插入位置
            int left = 0;
            int right = i - 1;
            
            while (left <= right) {
                int mid = left + (right - left) / 2;
                if (key < arr[mid]) {
                    right = mid - 1;
                } else {
                    left = mid + 1;
                }
            }
            
            // 将插入位置后的元素后移
            for (int j = i - 1; j >= left; j--) {
                arr[j + 1] = arr[j];
            }
            arr[left] = key; // 插入元素
        }
    }
    
    // 4. 优化版3:希尔排序(分组插入排序)
    public static void shellSort(int[] arr) {
        if (arr == null || arr.length <= 1) {
            return;
        }
        
        int n = arr.length;
        // 初始步长设为数组长度的一半,逐渐减小
        for (int gap = n / 2; gap > 0; gap /= 2) {
            // 对每个子数组进行插入排序
            for (int i = gap; i < n; i++) {
                int key = arr[i];
                int j = i;
                
                // 对以gap为步长的子数组进行插入排序
                while (j >= gap && arr[j - gap] > key) {
                    arr[j] = arr[j - gap];
                    j -= gap;
                }
                arr[j] = key;
            }
        }
    }
    
    public static void main(String[] args) {
        int[] arr = {64, 34, 25, 12, 22, 11, 90, 5};
        int[] arr1 = Arrays.copyOf(arr, arr.length);
        int[] arr2 = Arrays.copyOf(arr, arr.length);
        int[] arr3 = Arrays.copyOf(arr, arr.length);
        int[] arr4 = Arrays.copyOf(arr, arr.length);
        
        insertionSortBasic(arr1);
        System.out.println("基本版: " + Arrays.toString(arr1));
        
        insertionSortOptimized1(arr2);
        System.out.println("减少交换优化: " + Arrays.toString(arr2));
        
        insertionSortBinarySearch(arr3);
        System.out.println("二分查找优化: " + Arrays.toString(arr3));
        
        shellSort(arr4);
        System.out.println("希尔排序(分组优化): " + Arrays.toString(arr4));
    }
}
最近更新:: 2025/9/8 23:27
Contributors: luokaiwen