跳到主要内容
极客日志极客日志
首页博客AI提示词GitHub精选代理工具
搜索
|注册
博客列表
Javajava算法

经典排序算法详解:插入、希尔、选择、冒泡、堆与快速排序

深入解析插入、希尔、选择、冒泡、堆、快速、归及计数排序的核心逻辑与实现。对比各算法的时间复杂度、空间复杂度及稳定性,提供完整的 Java 代码示例与基准测试方法,助力掌握数据结构核心知识。

灰度发布发布于 2026/3/23更新于 2026/4/252 浏览
经典排序算法详解:插入、希尔、选择、冒泡、堆与快速排序

经典排序算法详解

一、插入排序

1.1 直接插入排序

直接插入排序的基本思想是:将第一个元素视为有序序列,从第二个元素开始,依次将其插入到前面已排序的序列中。如果当前元素比前一个元素小,则前一个元素后移,直到找到合适位置。

public static void insertSort(int[] array) {
    for (int i = 1; i < array.length; i++) {
        int tmp = array[i];
        int j = i - 1;
        // 向前查找并移动元素
        for (; j >= 0; j--) {
            if (tmp < array[j]) {
                array[j + 1] = array[j];
            } else {
                break;
            }
        }
        // 插入到正确位置
        array[j + 1] = tmp;
    }
}
  • 时间复杂度:最坏 O(N²),最好 O(N)
  • 空间复杂度:O(1)
  • 稳定性:稳定

1.2 希尔排序

希尔排序(缩小增量法)是对插入排序的优化。它先将整个待排序记录分割成若干子序列,分别进行直接插入排序,随着增量逐渐减小,子序列包含的元素越来越多,当增量为 1 时,整个文件恰被分成一组,算法便终止。

public static void shellSort(int[] array) {
    int gap = array.length;
    while (gap > 1) {
        gap /= 2;
        shell(array, gap);
    }
}

private static void shell(int[] arr, int gap) {
    for (int i = gap; i < arr.length; i++) {
        int tmp = arr[i];
        int j = i - gap;
        for (; j >= 0; j -= gap) {
            if (tmp < arr[j]) {
                arr[j + gap] = arr[j];
            } else {
                break;
            }
        }
        arr[j + gap] = tmp;
    }
}
  • 时间复杂度:O(N^1.25) 左右
  • 空间复杂度:O(1)

二、选择排序

2.1 单向选择排序

每次从未排序部分选出最小值,放到已排序部分的末尾。

public static void selectSort2(int[] array) {
    for (int i = 0; i < array.length; i++) {
        int minIndex = i;
        for (int j = i + 1; j < array.length; j++) {
            if (array[j] < array[minIndex]) {
                minIndex = j;
            }
        }
        swap(array, minIndex, i);
    }
}

2.2 双向选择排序

同时从两端寻找最大值和最小值,分别交换到两端,减少比较次数。

public static void selectSort(int[] array) {
    int left = 0;
    int right = array.length - 1;
    while (left < right) {
        int maxIndex = left;
        int minIndex = left;
        for (int i = left + 1; i <= right; i++) {
            if (array[i] < array[minIndex]) minIndex = i;
            if (array[i] > array[maxIndex]) maxIndex = i;
        }
        swap(array, left, minIndex);
        // 处理 maxIndex 已被交换的情况
        if (maxIndex == left) maxIndex = minIndex;
        swap(array, right, maxIndex);
        left++;
        right--;
    }
}
  • 时间复杂度:O(N²)
  • 空间复杂度:O(1)

2.3 堆排序

利用大根堆的性质,将堆顶元素与末尾元素交换,再调整堆,重复直至有序。

public static void createHeap(int[] array) {
    for (int parent = (array.length - 1 - 1) / 2; parent >= 0; parent--) {
        siftDown(array, parent, array.length);
    }
}

private static void siftDown(int[] array, int parent, int size) {
    int child = 2 * parent + 1;
    while (child < size) {
        if (child + 1 < size && array[child] < array[child + 1]) {
            child = child + 1;
        }
        if (array[child] > array[parent]) {
            swap(array, child, parent);
            parent = child;
            child = 2 * parent + 1;
        } else {
            break;
        }
    }
}

public static void heapSort(int[] array) {
    createHeap(array);
    int end = array.length - 1;
    while (end > 0) {
        swap(array, 0, end);
        siftDown(array, 0, end);
        end--;
    }
}
  • 时间复杂度:O(N*logN)
  • 空间复杂度:O(1)

三、交换排序

3.1 冒泡排序

通过相邻元素比较交换,将最大元素逐步'浮'到末尾。若一趟无交换则提前结束。

public static void bubbleSort(int[] array) {
    for (int i = 0; i < array.length - 1; i++) {
        boolean flag = false;
        for (int j = 0; j < array.length - 1 - i; j++) {
            if (array[j] > array[j + 1]) {
                swap(array, j, j + 1);
                flag = true;
            }
        }
        if (!flag) break;
    }
}
  • 时间复杂度:最好 O(N),最坏 O(N²)
  • 空间复杂度:O(1)
  • 稳定性:稳定

3.2 快速排序

3.2.1 Hoare 版本

选取基准值,左右指针交替扫描,相遇时交换基准值。

public static void quickSort(int[] array) {
    quick(array, 0, array.length - 1);
}

private static void quick(int[] array, int left, int right) {
    if (left >= right) return;
    // 小区间使用插入排序优化
    if (right - left + 1 <= 10) {
        insertSort(array, left, right);
        return;
    }
    int index = middleNum(array, left, right);
    swap(array, left, index);
    int pos = partitionHoare(array, left, right);
    quick(array, left, pos - 1);
    quick(array, pos + 1, right);
}

private static int partitionHoare(int[] array, int left, int right) {
    int record = left;
    int tmp = array[left];
    while (left < right) {
        while (left < right && array[right] >= tmp) right--;
        while (left < right && array[left] <= tmp) left++;
        swap(array, left, right);
    }
    swap(array, record, left);
    return left;
}

// 辅助方法:三数取中
private static int middleNum(int[] array, int left, int right) {
    int mid = (left + right) / 2;
    if (array[left] > array[right]) {
        if (array[right] > array[mid]) return right;
        else if (array[left] < array[mid]) return left;
        else return mid;
    } else {
        if (array[left] > array[mid]) return left;
        else if (array[right] < array[mid]) return right;
        else return mid;
    }
}

// 小区间插入排序优化
private static void insertSort(int[] array, int left, int right) {
    for (int i = left + 1; i <= right; i++) {
        int tmp = array[i];
        int j = i - 1;
        for (; j >= left; j--) {
            if (array[j] > tmp) {
                array[j + 1] = array[j];
            } else {
                break;
            }
        }
        array[j + 1] = tmp;
    }
}
3.2.2 挖坑法

先挖掉基准值坑,左右指针填坑,最后回填基准值。

private static int partitionPit(int[] array, int left, int right) {
    int record = array[left];
    while (left < right) {
        while (left < right && array[right] >= record) right--;
        array[left] = array[right];
        while (left < right && array[left] <= record) left++;
        array[right] = array[left];
    }
    array[left] = record;
    return left;
}
3.2.3 前后指针法

cur 找小于基准值的数,prev 记录边界,遇到小于值则交换。

private static int partitionPointer(int[] array, int left, int right) {
    int prev = left;
    int cur = left + 1;
    while (cur <= right) {
        if (array[cur] < array[left] && array[++prev] != array[cur]) {
            swap(array, cur, prev);
        }
        cur++;
    }
    swap(array, left, prev);
    return prev;
}
3.2.4 非递归快速排序

使用栈模拟递归过程,避免栈溢出。

public static void quickNor(int[] array) {
    quickSortNor(array, 0, array.length - 1);
}

private static void quickSortNor(int[] array, int left, int right) {
    Stack<Integer> stack = new Stack<>();
    int pivot = partitionHoare(array, left, right);
    if (pivot > left + 1) {
        stack.push(left);
        stack.push(pivot - 1);
    }
    if (pivot + 1 < right) {
        stack.push(pivot + 1);
        stack.push(right);
    }
    while (!stack.isEmpty()) {
        right = stack.pop();
        left = stack.pop();
        pivot = partitionHoare(array, left, right);
        if (pivot > left + 1) {
            stack.push(left);
            stack.push(pivot - 1);
        }
        if (pivot + 1 < right) {
            stack.push(pivot + 1);
            stack.push(right);
        }
    }
}

四、归并排序

4.1 递归归并排序

分治策略,先递归拆分,再合并有序子数组。

public static void mergeSort(int[] array) {
    mergeSortM(array, 0, array.length - 1);
}

private static void mergeSortM(int[] array, int left, int right) {
    if (left >= right) return;
    int mid = (left + right) / 2;
    mergeSortM(array, left, mid);
    mergeSortM(array, mid + 1, right);
    merge(array, left, mid, right);
}

private static void merge(int[] array, int left, int mid, int right) {
    int[] tmpArr = new int[right - left + 1];
    int k = 0;
    int s1 = left;
    int s2 = mid + 1;
    while (s1 <= mid && s2 <= right) {
        if (array[s1] <= array[s2]) {
            tmpArr[k++] = array[s1++];
        } else {
            tmpArr[k++] = array[s2++];
        }
    }
    while (s1 <= mid) tmpArr[k++] = array[s1++];
    while (s2 <= right) tmpArr[k++] = array[s2++];
    for (int i = 0; i < tmpArr.length; i++) {
        array[i + left] = tmpArr[i];
    }
}
  • 时间复杂度:O(N*logN)
  • 空间复杂度:O(logN)
  • 稳定性:稳定

4.2 非递归归并排序

自底向上,按步长 gap 合并。

public static void mergeNor(int[] array) {
    int gap = 1;
    while (gap < array.length) {
        for (int i = 0; i < array.length; i += gap * 2) {
            int left = i;
            int mid = left + gap - 1;
            int right = mid + gap;
            if (mid >= array.length) mid = array.length - 1;
            if (right >= array.length) right = array.length - 1;
            merge(array, left, mid, right);
        }
        gap *= 2;
    }
}

五、计数排序

适用于整数范围有限的场景,无需比较,基于计数。

private static void sortCount(int[] array) {
    int maxVal = array[0];
    int minVal = array[0];
    for (int i = 0; i < array.length; i++) {
        if (array[i] > maxVal) maxVal = array[i];
        if (array[i] < minVal) minVal = array[i];
    }
    int len = maxVal - minVal + 1;
    int[] count = new int[len];
    for (int i = 0; i < array.length; i++) {
        count[array[i] - minVal]++;
    }
    int index = 0;
    for (int i = 0; i < count.length; i++) {
        while (count[i] > 0) {
            array[index] = i + minVal;
            index++;
            count[i]--;
        }
    }
}

六、性能测试参考

提供不同数据分布下的排序耗时测试。

public static void order(int[] arr) {
    for (int i = 0; i < arr.length; i++) arr[i] = i;
}

public static void reverse(int[] arr) {
    for (int i = 0; i < arr.length; i++) arr[i] = arr.length - i;
}

public static void disorder(int[] arr) {
    Random random = new Random();
    for (int i = 0; i < arr.length; i++) arr[i] = random.nextInt(100);
}

public static void testSort(int[] arr, String name, Consumer<int[]> sorter) {
    int[] tmpArray = Arrays.copyOf(arr, arr.length);
    long startTime = System.currentTimeMillis();
    sorter.accept(tmpArray);
    long endTime = System.currentTimeMillis();
    System.out.println(name + " 时间:" + (endTime - startTime));
}

注:以上代码假设存在 swap 工具方法及必要的 import 语句。

private static void swap(int[] array, int i, int j) {
    int tmp = array[i];
    array[i] = array[j];
    array[j] = tmp;
}

目录

  1. 经典排序算法详解
  2. 一、插入排序
  3. 1.1 直接插入排序
  4. 1.2 希尔排序
  5. 二、选择排序
  6. 2.1 单向选择排序
  7. 2.2 双向选择排序
  8. 2.3 堆排序
  9. 三、交换排序
  10. 3.1 冒泡排序
  11. 3.2 快速排序
  12. 3.2.1 Hoare 版本
  13. 3.2.2 挖坑法
  14. 3.2.3 前后指针法
  15. 3.2.4 非递归快速排序
  16. 四、归并排序
  17. 4.1 递归归并排序
  18. 4.2 非递归归并排序
  19. 五、计数排序
  20. 六、性能测试参考
  • 💰 8折买阿里云服务器限时8折了解详情
  • 💰 8折买阿里云服务器限时8折购买
  • 🦞 5分钟部署阿里云小龙虾了解详情
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog

更多推荐文章

查看全部
  • 微信小程序自定义 tabBar 实现方案
  • Isaac Lab 机器人强化学习实战:配置架构、添加流程与调参技巧
  • C++ 函数指针与回调函数深度解析
  • C++ 双指针算法实战:有效三角形个数与和为 S 的两个数字
  • Isaac Lab 机器人强化学习实战:配置架构、添加流程与调参技巧
  • C++ set 与 multiset 容器详解
  • MCP 模型上下文协议:原理、架构与应用场景解析
  • 互联网大厂 Java 与 Android 开发核心面试题整理
  • AI 时代技术民主化:文科生为何成为最大受益者
  • Oracle 迁移至 MySQL 的关键差异与注意事项
  • 低小慢无人机目标识别与跟踪技术
  • Spring Security 认证授权机制与实战配置
  • 双指针算法专题:三角形个数与多数之和问题
  • GitHub Copilot 权限设置与合规管理指南
  • Python 爬虫抓取小说并保存为 TXT 文件教程
  • Python 异步编程与协程详解
  • C++ 容器详解:std::list 与 std::forward_list 对比分析
  • AMD 显卡 llama.cpp Vulkan 后端兼容与优化指南
  • 纯 Java 手写 TopoJSON 生成器零依赖实战
  • YUXIANGROS 框架下智能仓储机器人系统搭建

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online