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

数据结构:排序算法原理与实现

多种经典排序算法的原理与 C 语言实现。内容包括直接插入排序、希尔排序、直接选择排序、堆排序、冒泡排序、快速排序(Hoare、挖坑法、前后指针法及非递归实现)、归并排序以及计数排序。文章通过代码示例展示了各算法的核心逻辑,如建堆、分治策略、空间换时间等思想,适合学习数据结构与算法基础。

暗影行者发布于 2026/3/27更新于 2026/10/2689 浏览

1. 插入排序算法

1.1 直接插入排序

文章配图

文章配图

end 初始指向已排序部分的最后一个下标,end+1 初始为待排序数据。我们将 end+1 的数据暂存到 tmp 变量中。

文章配图

tmp 与 end 位置的数据比较,若 end 比 tmp 大,则将 end 的数据放到 end+1 位置上,并让 end--,在已排好序的数组中寻找合适位置;反之则将 tmp 数据放到 end+1 的位置。

// 直接插入排序
void InsertSort(int* arr, int num) {
    for (int i = 0; i < num - 1; i++) {
        int end = i;
        int tmp = arr[end + 1];
        while (end >= 0) {
            if (arr[end] > tmp)
                arr[end + 1] = arr[end];
            else
                break;
            end--;
        }
        arr[end + 1] = tmp;
    }
}

1.2 希尔排序

该算法属于插入排序的优化版本。

优化思路

首先明确,直接插入排序为何时间复杂度较高?

最坏情况下,若数组倒序,每次插入都需要调整到已排序部分的最前面。若能减少调整次数,效率将提升。

算法尝试

文章配图

若先将数组调整为只需少量调整的预排序状态,可解决逐个查找的问题。通过设置间隔变量 gap(如 num/3+1)进行大步长调整,之后逐渐减小 gap,直至 gap 为 1 时执行完整插入排序。

代码实现

基于插入排序修改:

for (int i = 0; i < num - gap; i++) {
    int end = i;
    int tmp = arr[end + gap];
    while (end >= 0) {
        if (arr[end] > tmp)
            arr[end + gap] = arr[end];
        else
            break;
        end -= gap;
    }
    arr[end + gap] = tmp;
}

外层循环控制 gap 变化:

void ShellSort(int* arr, int num) {
    int gap = num;
    while (gap > 1) {
        gap = gap / 3 + 1;
        for (int i = 0; i < num - gap; i++) {
            int end = i;
            int tmp = arr[end + gap];
            while (end >= 0) {
                if (arr[end] > tmp)
                    arr[end + gap] = arr[end];
                else
                    break;
                end -= gap;
            }
            arr[end + gap] = tmp;
        }
    }
}

先给 gap 赋值为数组长度,确保循环内部修改后 gap 为 1 时能执行一次。

2. 选择排序算法

2.1 直接选择排序

代码思路

文章配图

遍历数组查找最小值,与已排序末尾交换。

遍历效率较低,能否一次循环找最大和最小两个值?

算法尝试

文章配图

使用 begin 和 end 分别指向待排序数组的头尾,将找到的最小值和最大值与 begin 和 end 交换。中间为未排序部分,两边为已排序部分。用 maxi 和 mini 记录最大和最小值的下标。

代码实现

内层逻辑:

int mini = begin;
int maxi = end;
for (int i = begin; i <= end; i++) {
    if (arr[i] > arr[maxi]) maxi = i;
    if (arr[i] < arr[mini]) mini = i;
}
if (maxi == begin) maxi = mini;
Swap(&arr[begin], &arr[mini]);
Swap(&arr[end], &arr[maxi]);
begin++;
end--;

增加判断是为了避免当 maxi 位于 begin 位置时,第一次交换导致数据错位的问题。

完整函数:

void SelectSort(int* arr, int num) {
    int begin = 0;
    int end = num - 1;
    while (begin < end) {
        int mini = begin;
        int maxi = end;
        for (int i = begin; i <= end; i++) {
            if (arr[i] > arr[maxi]) maxi = i;
            if (arr[i] < arr[mini]) mini = i;
        }
        if (maxi == begin) maxi = mini;
        Swap(&arr[begin], &arr[mini]);
        Swap(&arr[end], &arr[maxi]);
        begin++;
        end--;
    }
}

2.2 堆排序

代码思路

基于堆结构,通过建堆、将堆顶换至末位、维护堆完成排序。

算法尝试

核心在于维护堆和建堆。

建堆可使用向下调整算法,找到最后一个父节点:parent = (num - 2) / 2。 维护堆类似出栈,栈顶与栈尾交换,对栈顶使用向下调整算法。

递增序列应建大堆还是小堆?

建大堆,使最大数据优先放到数组末尾。

代码实现

向下调整算法:

// 向下调整算法
void AdjustDown(int* arr, int parent, int size) {
    int child = parent * 2 + 1;
    while (child < size) {
        // 大堆:找最大的子节点
        if (child + 1 < size && arr[child] < arr[child + 1])
            child++;
        if (arr[parent] < arr[child])
            Swap(&arr[parent], &arr[child]);
        else
            break;
        parent = child;
        child = parent * 2 + 1;
    }
}

堆排序算法:

// 堆排序
void HeapSort(int* arr, int num) {
    // 建堆
    for (int i = (num - 2) / 2; i >= 0; i--) {
        AdjustDown(arr, i, num);
    }
    // 堆排序
    for (int i = num - 1; i > 0; i--) {
        Swap(&arr[0], &arr[i]);
        AdjustDown(arr, 0, i);
    }
}

3. 交换排序

3.1 冒泡排序

代码思路

相邻元素比较,大的往后放,每轮确定一个最大值。

代码实现
// 冒泡排序
void BubbleSort(int* arr, int num) {
    for (int i = 0; i < num - 1; i++) {
        for (int j = 0; j < num - i - 1; j++) {
            if (arr[j] > arr[j + 1])
                Swap(&arr[j], &arr[j + 1]);
        }
    }
}

3.2 快速排序

代码思路

选取基准值 key,小于 key 的放左边,大于 key 的放右边。

Hoare 版本快排

left 从右往左找小于 key 的值,right 从左往右找大于 key 的值,交换后继续,直到 right < left。

// Hoare 版本
int _QuickSort(int* arr, int left, int right) {
    int keyi = left;
    left++;
    while (left <= right) {
        while (arr[right] >= arr[keyi] && left < right)
            right--;
        while (arr[left] <= arr[keyi] && left < right)
            left++;
        if (left <= right)
            Swap(&arr[left++], &arr[right--]);
    }
    Swap(&arr[keyi], &arr[right]);
    return right;
}

递归代码:

// 快速排序
void QuickSort(int* arr, int left, int right) {
    if (left >= right)
        return;
    int keyi = _QuickSort(arr, left, right);
    QuickSort(arr, left, keyi - 1);
    QuickSort(arr, keyi + 1, right);
}
挖坑法

文章配图

第一个数据放入坑中,left 位置产生坑,right 先走找小于 key 的值填入左边坑,随后 left 走找大于 key 的值填入右边坑,循环至 right < left,最后填入 key。

// 挖坑法
int _QuickSort_hole(int* arr, int left, int right) {
    int hole = left;
    int key = arr[hole];
    while (left < right) {
        while (arr[right] >= key && left < right)
            right--;
        arr[hole] = arr[right];
        hole = right;
        while (arr[left] <= key && left < right)
            left++;
        arr[hole] = arr[left];
        hole = left;
    }
    arr[hole] = key;
    return hole;
}
Lomuto 前后指针法

prev 存放已找到的比 key 小的数据,cur 寻找比 key 小的数据,找到则与 prev+1 交换。遍历完后 prev 与 key 交换。

// Lomuto 前后指针
int _QuickSort_lomuto(int* arr, int left, int right) {
    int keyi = left;
    int prev = left;
    int cur = left + 1;
    while (cur <= right) {
        if (arr[cur] < arr[keyi] && ++prev != cur)
            Swap(&arr[prev], &arr[cur]);
        cur++;
    }
    Swap(&arr[keyi], &arr[prev]);
    return prev;
}
快排的非递归实现方法

借助栈代替递归,保存左右边界参数。

// 非递归版本快速排序——借助栈
void QuickSortNoR(int* arr, int left, int right) {
    ST st;
    StackInit(&st);
    StackPush(&st, left);
    StackPush(&st, right);
    while (!STEmpty(&st)) {
        int end = StackTop(&st);
        StackPop(&st);
        int start = StackTop(&st);
        StackPop(&st);
        int keyi = _QuickSort_lomuto(arr, start, end);
        if (start < keyi - 1) {
            StackPush(&st, start);
            StackPush(&st, keyi - 1);
        }
        if (end > keyi + 1) {
            StackPush(&st, keyi + 1);
            StackPush(&st, end);
        }
    }
    StackDestory(&st);
}

4. 归并排序

代码思想

文章配图

递归将数组拆分至单个数据,回档时合并有序序列。

算法实现
void _MergeSort(int* arr, int left, int right, int* tmp) {
    if (left >= right)
        return;
    int mid = (left + right) / 2;
    _MergeSort(arr, left, mid, tmp);
    _MergeSort(arr, mid + 1, right, tmp);
    // 合并两个有序的序列
    int begin1 = left, end1 = mid;
    int begin2 = mid + 1, end2 = right;
    int i = begin1;
    while (begin1 <= end1 && begin2 <= end2) {
        if (arr[begin1] < arr[begin2])
            tmp[i++] = arr[begin1++];
        else
            tmp[i++] = arr[begin2++];
    }
    while (begin1 <= end1)
        tmp[i++] = arr[begin1++];
    while (begin2 <= end2)
        tmp[i++] = arr[begin2++];
    memmove(arr + left, tmp + left, (right - left + 1) * sizeof(int));
}

// 归并排序
void MergeSort(int* arr, int num) {
    int* tmp = (int*)malloc(num * sizeof(int));
    if (tmp == NULL) {
        perror("MergeSort::malloc");
        exit(1);
    }
    _MergeSort(arr, 0, num - 1, tmp);
    free(tmp);
}

5. 非比较排序

5.1 计数排序

算法思想

文章配图

文章配图

通过数组记录每个数据的个数,再根据计数表还原数据。

算法实现
// 计数排序
void CountSort(int* arr, int num) {
    int min = arr[0];
    int max = arr[0];
    // 获取当前数据的偏移量
    for (int i = 0; i < num; i++) {
        if (arr[i] < min)
            min = arr[i];
        if (arr[i] > max)
            max = arr[i];
    }
    int range = max - min + 1;
    int* count = (int*)calloc(range, sizeof(int));
    if (count == NULL) {
        perror("CountSort::malloc");
        exit(1);
    }
    // 将数据放到计数表中
    for (int i = 0; i < num; i++) {
        count[arr[i] - min]++;
    }
    // 将数据放到原数组当中
    int index = 0;
    for (int i = 0; i < range; i++) {
        while (count[i]--)
            arr[index++] = i + min;
    }
    free(count);
}

目录

  1. 1. 插入排序算法
  2. 1.1 直接插入排序
  3. 1.2 希尔排序
  4. 优化思路
  5. 算法尝试
  6. 代码实现
  7. 2. 选择排序算法
  8. 2.1 直接选择排序
  9. 代码思路
  10. 算法尝试
  11. 代码实现
  12. 2.2 堆排序
  13. 代码思路
  14. 算法尝试
  15. 代码实现
  16. 3. 交换排序
  17. 3.1 冒泡排序
  18. 代码思路
  19. 代码实现
  20. 3.2 快速排序
  21. 代码思路
  22. Hoare 版本快排
  23. 挖坑法
  24. Lomuto 前后指针法
  25. 快排的非递归实现方法
  26. 4. 归并排序
  27. 代码思想
  28. 算法实现
  29. 5. 非比较排序
  30. 5.1 计数排序
  31. 算法思想
  32. 算法实现

更多推荐文章

查看全部
  • Python 2026 发展局势:AI 时代的通用基础设施语言
  • VS Code + WSL 环境下 GitHub 访问与 Copilot 卡顿问题排查指南
  • STM32 无线烧录方法:WCH LinkW 与 ESP32-C3 方案
  • 网络安全入门与进阶学习路线指南
  • 程序员为何要尽早掌握基础知识与设计模式
  • AI 大模型技术原理、训练优化及应用场景详解
  • 机器人领域顶级会议盘点与具身智能学习路线
  • Linux 下 C++ 线程池设计与实现
  • 自然语言处理在医疗健康领域的应用与实战
  • MATLAB 与 C++ 跨语言协作:动态链接库 DLL 开发指南
  • LLaMA Factory 多模态微调实践
  • OpenClaw 完整部署指南:用户创建、安装配置及 Nginx 反向代理
  • OpenClaw 框架深度解析:AI Agent 的安全挑战与演进路径
  • Stable Diffusion aki v4下载
  • 普通程序员学习大模型(LLM)的学习路线与知识体系
  • Python 面试核心考点解析:__slots__、属性访问与导入机制
  • Ubuntu/Debian 下 libwebkit2gtk-4.1-0 的 APT 安装指南
  • 科学机器学习中的物理信息神经网络:现状与展望
  • ListView 条目无法点击问题的解决方案
  • OpenClaw 入门指南:AI Agent 开发新范式

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如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