跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C算法

数据结构:常见排序算法详解与代码实现

系统讲解了八种常见排序算法,包括直接插入、希尔、堆、冒泡、选择、Hoare 快排、双指针快排及归并排序。涵盖稳定与不稳定排序分类、各算法实现思路、复杂度分析及代码示例,并辅以五道相关 OJ 练习题,适合数据结构复习与面试准备。

锁机制发布于 2026/3/30更新于 2026/10/26118 浏览
数据结构:常见排序算法详解与代码实现

稳定排序与不稳定排序分类

稳定排序:冒泡排序、直接插入排序、归并排序 不稳定排序:快速排序、堆排序、选择排序、希尔排序

直接插入排序

文章配图

实现思路

从第一个元素开始,默认第一个元素是有序的,将其之后的元素与前面的进行依次比较,根据条件进行移动、插入。

单趟实现

文章配图

整体实现

文章配图

//直接插入排序
void Direct(int* arr, int size) {
    assert(arr);
    for (int i = 1; i < size; i++) {
        //待比元素(待比元素刚开始应该在待排元素的前一位)
        int end = i - 1;
        //待排元素
        int tmp = arr[i];
        while (end >= 0) {
            //如果待排的比比较的元素小,就后移
            if (arr[end] > tmp) {
                arr[end + 1] = arr[end];
                end--;
            } else break;
        }
        //插入
        arr[end + 1] = tmp;
    }
}
复杂度

时间复杂度最坏:O(N^2),空间复杂度:O(1)

希尔排序

文章配图

实现思路

希尔排序是在直接插入排序上的优化,从大概有序到整体有序,避免了最坏情况。将一个数组分组,保证每组间隔一致,将每组进行直接插入排序,再整体实现直接插入排序。

单趟实现(注意待排元素不越界)

文章配图

整体实现达到大概有序

文章配图

再进行一趟直接插入排序达到整体有序

文章配图

//希尔排序
void Shell(int* arr, int size) {
    assert(arr);
    //设置间隔
    int val = 3;
    for (int j = 0; j < val; j++) { //外出循环控制 end 的起始位置
        //单趟(内层循环控制 end、tmp 的位置)
        for (int i = j; i + val < size; i += val) {
            //待比元素
            int end = i;
            //待排元素
            int tmp = arr[end + val];
            while (end >= 0) {
                //如果待排元素小于待比元素就前移
                if (arr[end] > tmp) {
                    arr[end + val] = arr[end];
                    end -= val;
                } else break;
            }
            //循环结束代表满足插入条件
            arr[end + val] = tmp;
        }
    }
    Direct(arr, size);
}
代码优化

假如有一万个数据,那么间隔 val 就太短了不适合,我们可以让间隔为每次的二分之一,根据长度选择间隔。最后间隔会达到 1,同时也就不用去再接入直接插入排序的接口了。

文章配图

复杂度

优化之前时间复杂度最坏情况:O(N^2) 优化之后明显感觉更效率:外层 * 内层 O(logN) * O(N) 空间复杂度:O(1)

堆排序

文章配图

文章配图

实现思路

利用堆的向上调整、向下调整来存储数据,再利用多次出堆顶元素来完成排序。下面小编用大顶堆、从下标 1 开始存储来实现堆排序。

实现堆:

//建推 Heap Heapspace;

//初始化
void Perliminary(Heap* Heapspace) {
    Heapspace->max = MAX;
    Heapspace->size = 0;
    Heapspace->data = (int*)malloc(sizeof(int) * Heapspace->max);
    if (Heapspace->data == NULL) {
        printf("初始化失败\n");
        return;
    }
}

//交换函数
void Exchange(int* p1, int* p2) {
    int tmp = *p1;
    *p1 = *p2;
    *p2 = tmp;
}

//上浮调整
void Upward(int* data, int child) {
    int parent = child / 2;
    while (parent > 0) {
        if (data[child] > data[parent]) {
            Exchange(&data[child], &data[parent]);
            child = parent;
            parent = child / 2;
        } else break;
    }
}

//入堆
void Enter(Heap* Heapspace, int data) {
    if (Heapspace->size == Heapspace->max) {
        int* pc = (int*)malloc(sizeof(int) * (Heapspace->max) * 2);
        if (pc == NULL) {
            printf("空间扩容失败\n");
            return;
        }
        Heapspace->max *= 2;
        Heapspace->data = pc;
    }
    Heapspace->size++;
    Heapspace->data[Heapspace->size] = data;
    Upward(Heapspace->data, Heapspace->size);
}

//打印堆元素
void Printf_Heap(Heap Heapspace, int size) {
    printf("堆元素:");
    for (int i = 1; i <= size; i++) {
        printf("%d ", Heapspace.data[i]);
    }
    printf("\n");
}

//下沉调整
void Subsidence(Heap* Heapspace) {
    int parent = 1;
    int child = 2 * parent;
    Exchange(&Heapspace->data[1], &Heapspace->data[Heapspace->size]);
    if (Heapspace->size > 1) {
        Heapspace->size--;
    }
    while (parent > 0 && child <= Heapspace->size) {
        if (child <= Heapspace->size && Heapspace->data[child] < Heapspace->data[child + 1]) {
            child++;
        }
        if (child <= Heapspace->size && Heapspace->data[parent] < Heapspace->data[child]) {
            Exchange(&Heapspace->data[parent], &Heapspace->data[child]);
            parent = child;
            child = 2 * parent;
        } else break;
    }
}

堆排序:利用堆的下沉调整,每次将堆尾元素与堆顶元素交换,然后调整堆以保持大顶堆的性质,多次调整达到排序效果。

for (int j = 0; j < size; j++) {
    Subsidence(&Heapspace);
}

文章配图

复杂度

时间复杂度为 O(N logN),空间复杂度为 O(1)

冒泡排序

文章配图

实现思路

从第一个元素开始,与之后的所有元素进行比较,较大则交换位置,直至排完所有元素。

单趟实现:

文章配图

整体实现:

文章配图

//冒泡排序
void Bubbles(int* arr, int size) {
    assert(arr);
    for (int i = 1; i < size; i++) {
        for (int end = 0; end < size - 1; end++) {
            if (arr[end] > arr[end + 1]) {
                Exchange(&arr[end], &arr[end + 1]);
            }
        }
    }
}
代码优化

如果经过单趟,没有任何排序过程,说明整体都是有序的,可以直接结束循环。

文章配图

复杂度

时间复杂度:O(N^2),空间复杂度:O(1)

选择排序

文章配图

实现思路

开始整个数组都是无序的,找整个无序组中的最小值,与有序部分的末尾进行交换,一直重复。

单趟实现:

文章配图

整体实现:

文章配图

//选择排序
void Select(int* arr, int size) {
    assert(arr);
    int end = 0;
    int tmp = 0;
    for (int i = 0; i < size - 1; i++) {
        tmp = end;
        for (int j = end; j < size; j++) {
            if (arr[j] < arr[tmp]) {
                tmp = j;
            }
        }
        Exchange(&arr[tmp], &arr[end]);
        end++;
    }
}
复杂度

时间复杂度:O(N^2),空间复杂度:O(1)

Hoare 排序

文章配图

实现思路

通过两个指针的移动,左边找大,右边找小,目的是让大的去到右边,小的去到左边,二者相遇再与 key 位置交换。记得 key 在哪边,对面的指针先动。

单趟实现:左边找大,右边找小,相遇则交换 key 的位置元素。

文章配图

整体实现:此时需要更新 key 的位置到二者相遇的位置,然后就将数组分为了两组,分别递归。

文章配图

文章配图

记录的原因以及递归的过程:如果不记录 left、right,那么递归右区间时,right 会随着左递归不断变化,导致无法递归右区间。

文章配图

//Hoare 排序
void Hoare(int* arr, int left, int right, int size) {
    if (left >= right) {
        return;
    }
    int begin = left;
    int end = right;
    int key = left;
    while (left < right) {
        while (arr[right] >= arr[key] && left < right) {
            right--;
        }
        while (arr[left] <= arr[key] && left < right) {
            left++;
        }
        Exchange(&arr[left], &arr[right]);
    }
    Exchange(&arr[key], &arr[left]);
    key = left;
    Hoare(arr, begin, key - 1, size);
    Hoare(arr, key + 1, end, size);
}
复杂度

时间复杂度:O(N logN),空间复杂度:O(1)

快排(双指针)

文章配图

实现思路

单趟:开始时有两个指针 prev 与 cur,分别找大找小,随后进行交换。注意交换完之后,cur 需要移动,否则一直进不了循环。

文章配图

整体实现:现在我们划分了基准值,分别递归左、右区间,注意记录部分值,原因和 Hoare 排序一样。

文章配图

//快排(双指针)
void Snap_shot(int* arr, int size, int prev) {
    assert(arr);
    int cur = prev + 1;
    if (cur >= size) {
        return;
    }
    int begin = prev;
    int pivot = prev;
    while (cur < size) {
        while (arr[cur] >= arr[pivot] && cur < size) {
            cur++;
        }
        while (arr[prev] <= arr[pivot] && prev < cur && cur < size) {
            prev++;
        }
        if (cur < size) {
            Exchange(&arr[cur], &arr[prev]);
            cur++;
        }
    }
    Exchange(&arr[prev], &arr[pivot]);
    pivot = prev;
    Snap_shot(arr, pivot, begin);
    Snap_shot(arr, size, pivot + 1);
}
复杂度

时间复杂度:O(N logN),空间复杂度:O(1)

归并排序

文章配图

实现思路

首先咱们看到这是将一个数组每次递归二分,这里通过划分区间并不难,难的是合并时如何排序?建议一个临时数组,通过每次函数返回的基准值进行划分区间,当划分到左右区间只有一个数据时开始合并,合并时就需要去排序了。

递归分组:左区间不断随着 Middle 分半,右区间会随着左区间传的参数改变 left、right 来产生,右边同理。

文章配图

if (left >= right) {
    return;
}
int Middle = (left + right) / 2;
Sort(arr, newnode, left, Middle);
Sort(arr, newnode, Middle + 1, right);

归并过程:此时我们划分了左右区间,分别是...然后我们对这个区间进行排序,排序的结果先放在临时数组。

文章配图

文章配图

我们可以用 memcpy 一键拷贝,需要注意它的三个参数:目的地起始位置、源起始位置、字节。

文章配图

主函数:

//归并排序
void Merge(int* arr, int size) {
    assert(arr);
    int* newnode = (int*)malloc(sizeof(int) * size);
    if (newnode == NULL) {
        printf("空间开辟失败\n");
        return;
    }
    Sort(arr, newnode, 0, size - 1);
}

子函数(递归、合并函数):

//归并子函数
void Sort(int* arr, int* newnode, int left, int right) {
    if (left >= right) {
        return;
    }
    int Middle = (left + right) / 2;
    Sort(arr, newnode, left, Middle);
    Sort(arr, newnode, Middle + 1, right);
    int begin1 = left;
    int end1 = Middle;
    int begin2 = Middle + 1;
    int end2 = right;
    int tmp = left;
    while (begin1 <= end1 && begin2 <= end2) {
        if (arr[begin1] < arr[begin2]) {
            newnode[tmp] = arr[begin1];
            begin1++;
            tmp++;
        } else {
            newnode[tmp] = arr[begin2];
            begin2++;
            tmp++;
        }
    }
    while (begin1 <= end1) {
        newnode[tmp] = arr[begin1];
        begin1++;
        tmp++;
    }
    while (begin2 <= end2) {
        newnode[tmp] = arr[begin2];
        begin2++;
        tmp++;
    }
    memcpy(arr + left, newnode + left, sizeof(int) * (right - left + 1));
}
复杂度

时间复杂度:O(N logN),空间复杂度:O(N)

排序 OJ 题(1)

文章配图

快排采用找基准值,划分区间进行递归排序,为分治思想。

排序 OJ(2)

文章配图

注意是从后往前比较。 假设此时排序完前 7 个元素:15、23、38、54、60、72、96 现在将 45 插入其中 45 VS 96 继续比较直到比到 38, 45>38,停止比较,中间有 4 个元素加上 38 一共比较了 5 次 此题何为从后往前:例如第一个 54 已经为有序,现在插入 38 38<54,有序组变为 38、54,继续下一个元素,意思是前 7 个正常排,45 采用从后往前排

排序 OJ(3)

文章配图

归并需要额外开辟一个数组作为临时数组。

排序 OJ(4)

文章配图

稳定排序的有:冒泡、直接插入、归并 时间复杂度稳定在 O(N^2) 的只有直接插入排序

排序 OJ(5)

文章配图

两个指针分别找大找小再交换、最后找大的指针指向的元素与基准值 65 交换,得到 A

目录

  1. 稳定排序与不稳定排序分类
  2. 直接插入排序
  3. 实现思路
  4. 复杂度
  5. 希尔排序
  6. 实现思路
  7. 代码优化
  8. 复杂度
  9. 堆排序
  10. 实现思路
  11. 复杂度
  12. 冒泡排序
  13. 实现思路
  14. 代码优化
  15. 复杂度
  16. 选择排序
  17. 实现思路
  18. 复杂度
  19. Hoare 排序
  20. 实现思路
  21. 复杂度
  22. 快排(双指针)
  23. 实现思路
  24. 复杂度
  25. 归并排序
  26. 实现思路
  27. 复杂度
  28. 排序 OJ 题(1)
  29. 排序 OJ(2)
  30. 排序 OJ(3)
  31. 排序 OJ(4)
  32. 排序 OJ(5)

更多推荐文章

查看全部
  • ChatGLM3-6B 本地与云端部署及 API 调用实战指南
  • Hunyuan-MT-7B-WEBUI 无需代码的网页翻译方案
  • Java 25 虚拟线程核心优化与使用
  • 前端模块化开发:从面条代码到结构化代码的蜕变
  • 用Q-learning做无人机三维避障路径规划
  • 基于 Trae 与 CMake 构建 AI 辅助的 Qt 开发环境
  • Go 微服务并发调优实战:Goroutine、Channel 与 Context
  • OpenClaw 配置飞书机器人教程(本地部署)
  • OpenClaw 框架 30+ 真实场景实战拆解
  • FPGA 验证:全方位解析 AXI Verification IP (AXI VIP)
  • 基于 DeepSeek 与 Cursor 构建智能代码审查工具实战
  • 前端SSE(Server-Sent Events)实现详解:从原理到前端AI对话应用
  • Python 环境完整卸载与重装指南(Windows/macOS)
  • Ace-Translate 本地离线 AI 翻译工具使用指南
  • 前端微前端架构:大型项目应用与风险分析
  • JVS-APS是什么?算法驱动+低代码融合,重塑智能排产新范式!
  • AWS Kiro 账号池管理系统 | 将 Amazon Q Developer API 转换为 OpenAI 兼容格式 | 支持多账号池、OIDC 自动认证、令牌自动刷新、Web 管理控制台 | Go
  • 基于 Amazon SageMaker 的 AIGC 应用部署与 Web 集成实践
  • QClaw 上手指南:本地 AI 代理工具深度体验与配置
  • 基于Q-learning算法的机器人迷宫路径规划研究

相关免费在线工具

  • 加密/解密文本

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

  • Gemini 图片去水印

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

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online