【C语言】排序算法——快速排序详解(含多种变式)!!!

【C语言】排序算法——快速排序详解(含多种变式)!!!

【C语言】排序算法——快速排序详解(含多种变式)!!!

前言

在上一期,我们学习了希尔排序以及插入排序,这些排序的算法都很高
那么,还有什么高效的排序算法呢?
今天给大家带来的是被加入C语言库里的排序算法——快速排序

(本期讲快速排序由初阶到高阶,还有一些拓展,方便大家理解)

一 、快速排序(初阶)

1. 视频演示

首先给大家看一段视频,让大家先看看快速排序是怎么运行的
(该视频仅仅是一次快速排序)

快速排序视频演示

2. 算法思想

(该视频仅仅是一次快速排序)

快速排序有一个 key 值,叫基准元素,可以理解为就是要排序数组的第一个数字
视频里可以看到,在第一次快速排序结束后,这个 key 值位置发生改变,其他元素位置也发生一定改变
最后在 key 的左边都是小于 key 的数,在 key 的右边都是大于 key 的数
这就是一次快速排序,此时 key 的顺序就被排好了,后续也不需要动
然后,将整个数组以 key 分成两个左右两个区间,并在这两个区间循环刚刚的步骤
直到区间不可再分(区间只剩一个元素),此时排序结束

一句话总结,快速排序就是不断将比 key 小的数放左边,把比 key 大的数放右边
最后完成排序

3. 实现思路

大体的排序逻辑我们讲完了,那现在该怎么实现呢?

(1)定key值

  • 第一步:定key值
快速排序有一个 key 值,叫基准元素,现在可以理解为就是第一个数字(后续还会再改进)
后续的排序就是围绕这个基准元素 key 来进行的

(2)大小交换

  • 第二步:大小交换
我们可以发现,视频中有两个小人,一个从左边往右走,一个从右往左走
先右小人一直向左走,直到遇到比 key 大的数停下
再左小人一直向右走,直到遇到比 key 小的数停下
当两小人都停下后,两小人所对应的值就互相交换,大的数换到右边,小的数换到左边

(3)循环

  • 第三步:循环
两小人继续一个左走,一个右走,满足条件时继续交换,然后重复
直到两小人相遇循环停止

(4)交换key

  • 第四步:交换key
此时将 key 与两小人相遇点的值进行交换
此时在 key 的左边都是小于 key 的数,在 key 的右边都是大于 key 的数
此时 key 就已经排好序,后续也不需要再动,第一轮快速排序结束

(5)分割区间

  • 第五步:分割区间
现在, key 就已经排好序,后续也不需要再动
此时将 key 左右的区间分割开来,分成左右两个区间
然后分别对这两个区间重复第一轮的排序:
定key值、大小交换、循环、交换key、分割区间

(6)结束

  • 第六步:结束
当每个区间分割成只剩下一个元素时,自然也不需要继续循环,就可以跳出循环
当所有的区间都只剩下一个元素时,都跳出循环时,排序也就完成了

4. 实现代码

刚刚给大家详细讲解了一下快速排序的实现思路
现在,我们开始实现代码

定义两个指针,也就是两个小人,排序完一次后开始递归key的左右区间
直到所有区间都跳出循环,排序结束

代码演示:(内有注释)

voidQuickSort1(int* a,int left,int right){if(left >= right)//判断是否继续,当区间只有一个数时跳出循环{return;}int key = left;//确定key的值,为第一个元素int L = left;int R = right;//先将左右的下标记录下来,以免后面丢失while(left < right)//当左右小人相遇时就停止循环{while(left < right && a[right]>= a[key])//右小人向左走,直到找到比key小的值{ right--;}while(left < right && a[left]<= a[key])//左小人向右走,直到找到比key大的值{ left++;}Swap(&a[left],&a[right]);//交换大的值和小的值}Swap(&a[right],&a[key]);//最后交换key和相遇点对应的值 key = right;//key的下标也要改变QuickSort1(a, L, key -1);//递归key的左区间QuickSort1(a, key +1, R);//递归key的右区间}

二 、快速排序(中阶)

1. 存在的问题

在刚刚快速排序的初阶代码中
我们的key值是固定的,始终都是数组的第一项
但这样也会出现一些小问题

若这个数组是完全有序的,会出现什么情况呢?
首先,右小人先向左走,因为数组是有序的,找不到比第一个数key小的值
所以会一直走直到找到key,且左右小人相遇 ( 左小人起始位置为key )
然后分割区间,key单独一个区间,右边所有数一个区间,然后循环右区间
最后当排序结束时,发现每一次循环都只排好一个数,时间复杂度为 O(N ^ 2)

所以,当数组的顺序有序或几乎有序时,key值就容易取到数组的极值,算法就很慢

2. 优化(三数取中)

那怎么来优化呢?

这里我们可以用三数取中的方法来解决取到极值
意思就是取数组的开头、中间、结尾三个数,数的大小在中间的就定为key
可以一定程度上避免取到极值点

代码演示:(内有注释)

//三数取中,返回三个数的中间值intFindKey(int* a,int left,int right)//形参为数组以及左右两下标{int mid =(left + right)/2;//中间数的下标if(a[left]> a[right]){if(a[right]> a[mid]){return right;}elseif(a[mid]> a[left]){return left;}else{return mid;}}else{if(a[left]> a[mid]){return left;}elseif(a[mid]> a[right]){return right;}else{return mid;}}}

3. 实现代码(中阶)

我们可以封装成一个函数来使用:
代码演示:(内有注释)

voidQuickSort1(int* a,int left,int right){if(left >= right)//判断是否继续,当区间只有一个数时跳出循环{return;}int L = left;int R = right;//先将左右的下标记录下来,以免后面丢失int key =FindKey(a, left, right);Swap(&a[key],&a[left]); key = left;while(left < right)//当左右小人相遇时就停止循环{while(left < right && a[right]>= a[key])//右小人向左走,直到找到比key小的值{ right--;}while(left < right && a[left]<= a[key])//左小人向右走,直到找到比key大的值{ left++;}Swap(&a[left],&a[right]);//交换大的值和小的值}Swap(&a[right],&a[key]);//最后交换key和相遇点对应的值 key = right;//key的下标也要改变QuickSort1(a, L, key -1);//递归key的左区间QuickSort1(a, key +1, R);//递归key的右区间}

三 、快速排序(高阶)

1. 仍存在的问题

由于我们的快速排序是由递归实现的,每递归一次就多一半的区间,最后的区间数是 2^n ( n为递归次数 )
而在第倒数1、2个递归时,区间内只有几个数,这时候用之前的办法就效率不高,且还会多两倍的区间
所以,我们可以当区间内个数少于一定是时,采用其他排序,这样就可以减少大量的递归,提高效率

2. 优化(小区间优化)

当区间个数小于10时,就采用推排序,可以有效的提高效率

3. 实现代码(高阶)

(包含堆排)

(1)三数取中函数

代码演示:(内有注释)

//三数取中,返回三个数的中间值intFindKey(int* a,int left,int right){int mid =(left + right)/2;//中间数的下标if(a[left]> a[right]){if(a[right]> a[mid]){return right;}elseif(a[mid]> a[left]){return left;}else{return mid;}}else{if(a[left]> a[mid]){return left;}elseif(a[mid]> a[right]){return right;}else{return mid;}}}

(2)主要的快速排序代码

代码演示:(内有注释)

// 快速排序递归实现// 快速排序hoare版本intPartSort1(int* a,int left,int right){int key =FindKey(a, left, right);//找到中间下标并给keySwap(&a[key],&a[left]); key = left;//将key位置换到首项while(left < right)//当左右小人相遇时就停止循环{while(left < right && a[right]>= a[key])//右小人向左走,直到找到比key小的值{ right--;}while(left < right && a[left]<= a[key])//左小人向右走,直到找到比key大的值{ left++;}Swap(&a[left],&a[right]);//交换大的值和小的值}Swap(&a[right],&a[key]);//最后交换key和相遇点对应的值return right;//返回最后key的下标,方便分割}

(3)堆排序

代码演示:(内有注释)

// 插入排序voidInsertSort(int* a,int n){// [0, n-1]for(int i =0; i < n -1; i++){// [0, n-2]是最后一组// [0,end]有序 end+1位置的值插入[0,end],保持有序int end = i;int tmp = a[end +1];while(end >=0){if(tmp < a[end]){ a[end +1]= a[end];--end;}else{break;}} a[end +1]= tmp;}}// 将交换的元素向下调整voidAdJustDown(int* a,int parent,int size){// 先假设左孩子小int child =2* parent +1;while(child <= size -1){if(child +1<= size -1&& a[child +1]> a[child]){ child++;}if(a[child]> a[parent]){Swap(&a[child],&a[parent]); parent = child; child =2* parent +1;}else{break;}}}//堆排序voidHeapSort(int* a,int sz){int i;for(i =(sz -1-1)/2; i >=0; i--){AdJustDown(a, i, sz);}for(i = sz -1; i >0; i--){Swap(&a[0],&a[i]);AdJustDown(a,0, i);}}

(4)快速排序的框架

代码演示:(内有注释)

voidQuickSort1(int* a,int left,int right){if(left >= right)//判断是否继续,当区间只有一个数时跳出循环{return;}int g = right - left +1;if(g <10){HeapSort(a + left, g);//堆排序}else{int key =PartSort1(a, left, right);//接收返回值QuickSort1(a, left, key -1);//递归key的左区间QuickSort1(a, key +1, right);//递归key的右区间}}

四、快速排序(非递归)

1. 问题

在我们进行递归的快速排序时,若数据量过大,就会递归很多次,可能会出现栈溢出

为了避免出现这种情况,我们可以采用非递归的方式解决

2.实现思路

我们可以使用一个栈来实现
将区间的左、右范围分别存在栈中,当取出一个区间后,就存下这个区间的两个分割区间
(前提是区间存在)
当栈为空且不能再存入时,排序就好了

3.实现代码

注意:由于小编在之前的博客中详解了栈的实现,为了避免重复,小编在这里没有讲解栈的实现代码

若有感兴趣的可以去看看
链接:
【数据结构】栈——超详解!!!(包含栈的实现)

(1)栈的实现(Stack.h)

用于存放用来放函数的声明和一些库函数的头文件

#pragmaonce#define_CRT_SECURE_NO_WARNINGS1#include<stdio.h>#include<stdlib.h>#include<stdbool.h>#include<assert.h>#include<sperror.h>//重定义,方便修改类型typedefint STDataType;//定义栈typedefstructStack{ STDataType* a;//数组指针int size;//总元素int capacity;//容量 }Stack;// 初始化栈 voidStackInit(Stack* ps);// 入栈 voidStackPush(Stack* ps, STDataType data);// 出栈 voidStackPop(Stack* ps);// 获取栈顶元素  STDataType StackTop(Stack* ps);// 获取栈中有效元素个数 intStackSize(Stack* ps);// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0 intStackEmpty(Stack* ps);// 销毁栈voidStackDestroy(Stack* ps);

(2)栈的实现(Stack.c)

用于用来放函数的定义(栈的主体)

#include"Stack.h"// 初始化栈 voidStackInit(Stack* ps){//断言空指针assert(ps); ps->a =NULL; ps->capacity =0; ps->size =0;//全部初始化置 0 / NULL}// 入栈 voidStackPush(Stack* ps, STDataType data){assert(ps);//断言空指针if(ps->size == ps->capacity)//当size=capacity时就表示空间不足,此时需要增容,故进入if语句{//先设置新变量,增容后再赋值int newcapacity = ps->capacity ==0?4:2* ps->capacity;//设置一个三目操作符判断原空间是否为 0//当原空间为0时给空间开辟 4 字节;当原空间不为0时给空间增容 2倍 STDataType* tmp =(STDataType*)realloc(ps->a, newcapacity *sizeof(STDataType));//由于是在原空间的基础上给空间增容,故我们这里使用 realloc函数 增容//增容大小为上面的 newcapacity *(类型大小)if(tmp ==NULL)//加一个 if语句 防止增容失败{perror("realloc");return;}//没有问题后就赋值 ps->a = tmp; ps->capacity = newcapacity;} ps->a[ps->size]= data; ps->size++;}// 出栈 voidStackPop(Stack* ps){assert(ps && ps->size >0);//断言空指针//断言顺序表不能为空 ps->size--;//将元素个数进行 -1 就行//这样也不会影响到后面的 增、删、查、改}// 获取栈顶元素  STDataType StackTop(Stack* ps){assert(ps && ps->size >0);//断言空指针//断言顺序表不能为空return ps->a[ps->size -1];}// 获取栈中有效元素个数 intStackSize(Stack* ps){assert(ps);//断言空指针return ps->size;}// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0 intStackEmpty(Stack* ps){assert(ps);//断言空指针return ps->size ==0;}// 销毁栈 voidStackDestroy(Stack* ps){assert(ps);//断言空指针free(ps->a);//释放内存 ps->a =NULL; ps->capacity =0; ps->size =0;//全部初始化置 0 / NULL}

(3)快速排序主体

(下面重复的函数上面都有)

代码演示:(内有注释)

// 快速排序 非递归实现voidQuickSortNonR(int* a,int left,int right){//定义 Stack S;StackInit(&S);//首次添加数据StackPush(&S, right);StackPush(&S, left);//当栈不为空时循环while(!StackEmpty(&S)){int L =StackTop(&S);StackPop(&S);int R =StackTop(&S);StackPop(&S);//取出值并从栈删除if(L >= R){return;}//小区间优化int g = R - L +1;if(g <10){//堆排序HeapSort(a + L, g);}else{int key =PartSort1(a, L, R);//没越界就插入新数据if(R - key -1>1){StackPush(&S, R);StackPush(&S, key +1);}if(key -1- L >1){StackPush(&S, key -1);StackPush(&S, L);}}}//最后销毁StackDestroy(&S);}

结语

OK,本期的排序算法详解到这里就结束了

若内容对大家有所帮助,可以收藏慢慢看,感谢大家支持

本文有若有不足之处,希望各位兄弟们能给出宝贵的意见。谢谢大家!!!

新人,本期制作不易希望各位兄弟们能动动小手,三连走一走!!!

支持一下(三连必回QwQ)

Read more

【Copilot配置】—— Copilot 设置全解析 + 最佳实践|解锁 AI 辅助开发效率天花板

【Copilot配置】—— Copilot 设置全解析 + 最佳实践|解锁 AI 辅助开发效率天花板

Copilot 设置全解析 + 最佳实践|解锁 AI 辅助开发效率天花板 使用 GitHub Copilot 有一段时间的开发者,大概率会有一个共同感受:默认设置能满足基础需求,但想要让它真正贴合自己的开发习惯、兼顾效率与安全,就必须深入挖掘其设置面板的细节。很多人忽略了 Copilot 丰富的可配置项,导致未能充分发挥它的辅助价值,甚至遇到安全隐患或体验卡顿等问题。 今天这篇博客,将完全贴合实际开发场景,先系统梳理 Copilot 核心设置模块的功能说明,再给出经过实测的最佳实践,无论是新手还是资深开发者,都能跟着配置出最适合自己的 Copilot 环境(本文基于 Copilot 最新版本,包含实验性功能的合理使用建议)。 一、先理清:Copilot 设置面板的核心模块划分 Copilot 的设置面板看似繁杂,但本质可分为 5 大核心模块,所有配置都围绕“效率、安全、个性化”三个核心目标展开,先记住这个划分,后续配置时不会混乱:

By Ne0inhk

【AIGC行业前沿】2026年2月AIGC行业模型发布以及主要前沿资讯

目录 1. 阿里Qoder发布Qwen-Coder-Qoder 2. Kimi与南大发布SimpleSeg赋能模型像素感知 3. 字节研究团队发布ConceptMoE提升AI推理 4. 阶跃星辰发布并开源模型Step 3.5 Flash 5. 智谱发布并开源OCR模型GLM-OCR 6. xAI正式发布Grok Imagine 1.0视频模型 7. 优必选开源具身智能大模型Thinker 8. 通义千问发布开源编程模型Qwen3-Coder-Next 9. OpenAI宣布GPT-5.2系列模型提速40% 10. OpenBMB发布多模态模型MiniCPM-o 4.5 11. ACE Studio与StepFun联合发布开源音乐模型ACE-Step 1.5 12. Ai2发布轻量级开源编码模型SERA-14B 13. 上海AI实验室推出万亿参数多模态科学推理模型Intern-S1-Pro 14. Mistral AI开源40亿参数实时语音模型Voxtral Mini 4B Realtime 2602 15. 快手可灵发布可灵3.0 1

By Ne0inhk

AI编程工具深度对比:Cursor、Copilot、Trae与Claude Code,2025年开发者该如何选择?

2025年,AI编程助手已从新奇技术演变为生产力核心,但面对众多选择,开发者如何才能找到最适合自己的智能编程伙伴? 一、四大AI编程工具的核心定位与市场格局 2025年的AI编程工具市场已经形成了明显的分层格局。根据最新的开发者使用数据,这些工具不再仅仅是代码补全助手,而是朝着专业化、场景化方向发展。

By Ne0inhk

Copilot登录总失败?这7种情况你必须马上检查

第一章:Copilot登录失败的常见现象与影响 GitHub Copilot 作为广受欢迎的AI编程助手,在实际使用过程中,部分开发者频繁遭遇登录失败的问题。这一问题不仅影响编码效率,还可能导致开发流程中断,尤其在团队协作或紧急修复场景下尤为显著。 典型登录失败现象 * 输入凭据后提示“Authentication failed”但账号密码正确 * VS Code 中 Copilot 图标持续显示加载状态,无法完成初始化 * 浏览器重定向至 GitHub 授权页面时卡顿或返回空白页 * 终端输出错误日志:Copilot service is unreachable 对开发工作流的影响 影响维度具体表现编码效率失去代码补全与建议功能,手动编写耗时增加调试体验无法快速生成测试用例或错误解释团队协同新成员因无法启用 Copilot 导致上手速度下降 基础诊断命令 在 VS Code 终端中执行以下命令可获取当前认证状态: # 查看 Copilot 扩展日志 code --log debug # 检查已安装扩展及版本 code --list-extensions

By Ne0inhk