【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

Flutter 三方库 wasm_ffi 深入鸿蒙端侧硬核 WebAssembly 虚拟机沙盒穿透适配全景:通过异步极速 FFI 中继管道打通底层高算力异构服务-适配鸿蒙 HarmonyOS ohos

Flutter 三方库 wasm_ffi 深入鸿蒙端侧硬核 WebAssembly 虚拟机沙盒穿透适配全景:通过异步极速 FFI 中继管道打通底层高算力异构服务-适配鸿蒙 HarmonyOS ohos

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 三方库 wasm_ffi 深入鸿蒙端侧硬核 WebAssembly 虚拟机沙盒穿透适配全景:通过异步极速 FFI 中继管道打通底层高算力异构服务并全面实现无损语言壁垒交互 前言 在 OpenHarmony 应用向高性能计算领域扩展的过程中,如何优雅地接入已有的 C/C++ 算法库(如加密引擎、重型图像处理、数学模拟)而又不失跨平台的便捷性?传统的 NAPI 虽然稳健,但在 Flutter 生态中,直接利用 WebAssembly (WASM) 配合 FFI(External Function Interface)的语义可以在一定程度上实现代码的高度复用。wasm_ffi 库为 Flutter 开发者提供了一套在 Dart 环境下调用 WASM

By Ne0inhk
三种适用于Web版IM(即时通讯)聊天信息的加密算法实现方案

三种适用于Web版IM(即时通讯)聊天信息的加密算法实现方案

文章目录 * **第一部分:引言与核心密码学概念** * **1.1 为什么IM需要端到端加密(E2EE)?** * **1.2 核心密码学概念与工具** * **第二部分:方案一:静态非对称加密(基础方案)** * **2.1 方案概述与流程** * **2.2 前端Vue实现(使用node-forge)** * **1. 安装依赖** * **2. 核心工具类 `crypto.js`** * **3. Vue组件中使用** * **2.3 后端Java实现(Spring Boot)** * **1. 实体类** * **2. Controller层** * **3. WebSocket配置** * **2.4 密钥管理、注册与登录集成** * **1. 用户注册/登录时生成密钥** * **2. 密钥设置页面** * **2.

By Ne0inhk
前端代码生成的大洗牌:当 GLM 4.7 与 MiniMax 挑战 Claude Opus,谁才是性价比之王?

前端代码生成的大洗牌:当 GLM 4.7 与 MiniMax 挑战 Claude Opus,谁才是性价比之王?

在 AI 辅助编程领域,长期以来似乎存在一条不成文的铁律:如果你想要最好的结果,就必须为最昂贵的模型买单(通常是 Anthropic 或 OpenAI 的旗舰模型)。然而,随着国产大模型如 GLM 4.7 和 MiniMax M2.1 的迭代,这一格局正在发生剧烈震荡。 最近,一场针对Claude Opus 4.5、Gemini 3 Pro、GLM 4.7 和 MiniMax M2.1 的前端 UI生成横向测评,打破了许多人的固有认知。在这场包含落地页、仪表盘、移动端应用等五个真实场景的较量中,不仅出现了令人咋舌的“滑铁卢”,更诞生了性价比极高的“新王”。 本文将深入拆解这场测试的细节,透过代码生成的表象,探讨大模型在工程化落地中的真实效能与成本逻辑。

By Ne0inhk
【Java Web学习 | 第14篇】JavaScript(8) -正则表达式

【Java Web学习 | 第14篇】JavaScript(8) -正则表达式

🌈个人主页: Hygge_Code🔥热门专栏:从0开始学习Java | Linux学习| 计算机网络💫个人格言: “既然选择了远方,便不顾风雨兼程” 文章目录 * JavaScript 正则表达式详解 * 什么是正则表达式🤔 * JavaScript 正则表达式的定义与使用🥝 * 1. 字面量语法 * 2. 常用匹配方法 * test() 方法🍋‍🟩 * exec() 方法🍋‍🟩 * 正则表达式的核心组成部分🐦‍🔥 * 1. 元字符 * 边界符 * 量词 * 字符类 * 2. 修饰符 * 简单示例🍂 JavaScript 正则表达式详解 正则表达式是处理字符串的强大工具,在 JavaScript 中被广泛应用于表单验证、文本处理和数据提取等场景。本文将从正则表达式的基本概念出发,详细介绍其语法规则和实际应用方法。 什么是正则表达式🤔 正则表达式是用于匹配字符串中字符组合的模式,在 JavaScript

By Ne0inhk