【数据结构】八大排序之快速排序:分而治之的艺术

【数据结构】八大排序之快速排序:分而治之的艺术
在这里插入图片描述

文章目录

快速排序

快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为:任取待排序元素序列中 的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右 子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。

1.hoare版本

在这里插入图片描述

简单来说就是选某个元素为基准值(这里先默认第一个),然后把比基准值小的都放到基准值的左边,比基准值大的都放到基准值的右边

以下图为例

在这里插入图片描述

先以6为基准

然后左边找大,右边找小,之后互换

进行这么一趟后,6左边就都比它小,右边都比它大

然后以6为分界线,再分成两个区间,类似于二叉树

在这里插入图片描述
voidQuickSort(int* a,int left,int right){if(left >= right)return;//区间不存在时直接返回int keyi=left;int begin=left,end=right;while(begin < end){// 从右向左找小于基准的值while(begin < end && a[end]>= a[keyi]){ end--;}// 从左向右找大于基准的值while(begin < end && a[begin]<= a[keyi]){ begin++;}Swap(&a[begin],&a[end]);}// 将基准放到正确位置Swap(&a[keyi],&a[end]);// 递归排序子数组QuickSort(a, left, end -1);QuickSort(a, end +1, right);}}

算法优化

虽然基本快速排序已经很快,但在某些情况下性能会下降,特别是当输入数组已经有序或接近有序时。以下是两种常见的优化策略:

三数取中法

当数组已经有序或接近有序时,选择第一个元素作为基准会导致分区极度不平衡,从而使算法退化为 O(n²) 的时间复杂度。三数取中法通过选择左端、中间和右端三个元素的中值作为基准,可以有效避免这种最坏情况。

三数取中代码如下

intGetMidi(int* a,int left,int right)//左边作key 右边先走{int midi =(left + right)/2;if(a[left]< a[midi]){if(a[midi]< a[right]){//说明midi是中间值return midi;}//走到这说明midi最大,所以剩下两个数中大的就是中间值elseif(a[left]>a[right]){return left;}else//剩下最后一种情况 不必多说{return right;}}else//走到这说明 a[left]>a[midi]{if(a[midi]> a[right]){return midi;}//到这说明midi最小 所以找剩下两个小的elseif(a[left]< a[right]){return left;}else{return right;}}
小区间优化

区间比较小时,递归代价比较大,所以要进行小区间优化

对于递归来说

最后一层递归次数占总体的50%,倒数第二层25% ,所以进行小区间优化可以减少递归次数

在这里插入图片描述
//区间比较小时。进行小区间优化,不再递归分割排序。减少递归次数if((right - left +1)<10){InsertSort(a + left, right - left +1);}

完整代码如下

#include<stdio.h>voidSwap(int* a,int* b){int tmp =*a;*a =*b;*b = tmp;}voidInsertSort(int* a,int n){for(int i =1; i < n; i++){int key = a[i];int j = i -1;while(j >=0&& a[j]> key){ a[j +1]= a[j]; j--;} a[j +1]= key;}}//面试手撕快排 不用写小区间优化和三数取中 后续讲一下思路即可 不然会觉得你写的慢//避免有序情况下效率降低//1.随机数//2。三数取中intGetMidi(int* a,int left,int right)//左边作key 右边先走{int midi =(left + right)/2;if(a[left]< a[midi]){if(a[midi]< a[right]){//说明midi是中间值return midi;}//走到这说明midi最大,所以剩下两个数中大的就是中间值elseif(a[left]>a[right]){return left;}else//剩下最后一种情况 不必多说{return right;}}else//走到这说明 a[left]>a[midi]{if(a[midi]> a[right]){return midi;}//到这说明midi最小 所以找剩下两个小的elseif(a[left]< a[right]){return left;}else{return right;}}}voidQuickSort(int* a,int left,int right){if(left >= right)return;//小区间优化,不再递归分割排序。减少递归次数if((right - left +1)<10){InsertSort(a + left, right - left +1);}else{//三数取中int midi =GetMidi(a, left, right);Swap(&a[left],&a[midi]);int keyi = left;int begin = left +1;// 从基准下一个开始int end = right;while(begin <= end){// 从右向左找小于基准的值while(begin <= end && a[end]>= a[keyi]) end--;// 从左向右找大于基准的值while(begin <= end && a[begin]<= a[keyi]) begin++;// 交换找到的不符合元素if(begin < end)Swap(&a[begin],&a[end]);}// 将基准放到正确位置Swap(&a[keyi],&a[end]);// 递归排序子数组QuickSort(a, left, end -1);QuickSort(a, end +1, right);}}intmain(){int arr[3]={2,1,3};QuickSort(arr,0,2);for(int i =0; i <3; i++){printf("%d ", arr[i]);// 输出:1 2 3}return0;}

为什么要右边先走呢

分析如下图

在这里插入图片描述

算法分析

时间复杂度

O(n log n):一共有logn层递归 每一层都是所有子数组加起来都是n

空间复杂度

  • 最佳情况:O(log n) - 递归调用的栈空间
  • 最坏情况:O(n) - 当分区极度不平衡时

2.前后指针法

前后指针法是快速排序的另一种常见实现方式,通过两个指针prev和cur来遍历数组,将小于基准值的元素移动到前面。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

排序过程

  • 定义两个指针prev和cur,prev指向left,cur指向prev+1。
  • cur指针向右移动,如果a[cur]小于基准值,则prev++并交换a[prev]和a[cur]。
  • 最后将基准值交换到prev位置。

排序一趟的代码如下

int prev = left;int cur = prev+1;while(cur<=right){if(a[cur]<a[keyi]&&++prev!=cur)//cur比基准小就交换Swap(&a[prev],&a[cur])//cur不管交换还是不交换,都要继续走 cur++}Swap(&a[prev],&a[keyi]);return prev;

完整代码

intPartSort2(int* a,int left,int right){// 三数取中int midi =GetMidi(a, left, right);Swap(&a[left],&a[midi]);int keyi = left;int prev = left;int cur = prev+1;while(cur <= right){if(a[cur]< a[keyi]&&++prev != cur)Swap(&a[prev],&a[cur]); cur++;}Swap(&a[prev],&a[keyi]);return prev;}voidQuickSort(int* a,int left,int right){if(left >= right)return;int keyi =PartSort1(a, left, right);// [left, keyi-1] keyi [keyi+1, right]QuickSort(a, left, keyi -1);QuickSort(a, keyi +1, right);}

3.非递归(栈模拟)

递归实现虽然简洁,但在处理大规模数据时可能导致栈溢出。非递归实现通过显式栈来模拟递归过程,避免递归带来的栈开销。

实现思路

  1. 初始化一个栈,将初始左右下标入栈(先右后左)。
  2. 循环取出栈顶的左右下标,进行分区操作。
  3. 将分区后的左右子数组下标入栈,继续处理直到栈为空。
在这里插入图片描述
voidQuickSortNonR(int* a,int left,int right){ ST st;STInit(&st);STPush(&st, right);//先入右 再入左STPush(&st, left);while(!STEmpty(&st)){int begin =STTop(&st);STPop(&st);int end =STTop(&st);STPop(&st);int keyi =PartSort2(a, begin, end);// [begin, keyi-1] keyi [keyi+1, end]if(keyi +1< end){STPush(&st, end);STPush(&st, keyi+1);}if(begin < keyi-1){STPush(&st, keyi-1);STPush(&st, begin);}}STDestroy(&st);}

总结

快速排序是一种高效且应用广泛的排序算法,通过分治策略将问题分解为子问题处理。其性能取决于基准值的选择是否合理,因此在实际应用中常采用三数取中等优化策略来避免最坏情况的发生。

无论是递归还是非递归实现,快速排序都能在平均情况下达到O(n log n)的时间复杂度,使其成为处理大规模数据的理想选择之一

Read more

N46Whisper:革命性AI日语字幕制作方案

N46Whisper:革命性AI日语字幕制作方案 【免费下载链接】N46WhisperWhisper based Japanese subtitle generator 项目地址: https://gitcode.com/gh_mirrors/n4/N46Whisper N46Whisper是一款基于云端AI技术的日语语音转字幕工具,让字幕制作变得前所未有的高效智能。这款基于Whisper模型的创新应用,专为日语视频字幕制作而生,彻底改变了传统手动打字的繁琐流程。 🚀 极速启动:零配置云端体验 一键开启云端工作环境 无需安装任何软件,只需在浏览器中打开N46Whisper.ipynb文件,即可立即开始使用。云端处理能力让传统本地软件望尘莫及。 智能模型选择策略 * 标准模式:平衡精度与速度,适合日常制作 * 轻量模式:快速处理,满足即时需求 * 高精度模式:专业级识别,适合正式发布内容 💡 核心技术创新亮点 AI语音识别引擎 采用业界领先的Whisper技术,日语语音识别准确率突破95%。无论是综艺节目的快节奏对话,还是访谈内容的专业术语,都能精准捕捉。

By Ne0inhk
AIGC - Raphael AI:全球首个无限制免费 AI 图片生成器

AIGC - Raphael AI:全球首个无限制免费 AI 图片生成器

文章目录 * 引言 * 一、Raphael AI 是什么? * 二、核心引擎:Flux.1-Dev 与 Flux Kontext * 1. Flux.1-Dev:极速与精细的结合 * 2. Flux Kontext:精确的语义理解 * 三、主要功能一览 * 1. 零成本创作 * 2. 多风格引擎 * 3. 高级文本理解 * 4. 极速生成 * 5. 隐私保护 * 四、实测体验与使用方式 * 五、与其他 AI 绘图平台的对比 * 六、未来发展与生态计划 * 七、总结:AI 创意的平权时代 引言 在生成式 AI 技术飞速发展的时代,图像生成的门槛正在被彻底打破。

By Ne0inhk

Claude Code的完美平替:OpenCode + GitHub Copilot

引言:Claude 虽好,但你真的能用上吗? 在当前席卷全球的“Vibe Coding”浪潮中,Anthropic 推出的 Claude 系列模型 + 终端工具 Claude Code,凭借极强的逻辑推理能力,成为了开发者眼中的“白月光”。但现实是残酷的:对于中国开发者而言,账号随时被封、海外信用卡支付遭拒、API 额度受限以及复杂的网络环境,构成了一道难以逾越的门槛。 虽然最近国产编程模型不断发力,Claude Code + GLM-4.7的表现非常出色,但面对复杂问题,Claude系列模型依然完胜。难道我们只能眼馋Claude全家桶的编程体验吗? 作为一名追求极致生产力的开发者,我发现了一个绝佳的完美替代方案:OpenCode + GitHub Copilot。这个组合不仅能让你享受如 GLM-4.7 一样的性价比,还能更方便的使用 Claude 的顶级模型。 Claude Code 的开源免费平替:OpenCode 想要复刻

By Ne0inhk
idea更改git用户

idea更改git用户

步骤一: 在File>Settings中忘记密码: 注意:更换用户后,请选择In KeePass,否则会再次让你输入密码。 这一步操作之后,当你再次拉取或提交代码时,会弹出重新输入git用户的弹窗: 至此,idea层面配置的git用户就切换成功了,但此时提交代码,你会发现提交记录中的提交人和依旧没有改变。因为idea是利用git与远程仓库进行交互,而git本身是一个独立应用。所以我们在步骤二需要对git层面的用户进行更改。 步骤二(可选):在idea终端设置全局用户名 首先,分别输入以下命令查看git当前用户名及邮箱: git config user.name git config user.email 之后,修改项目的git目标用户名及邮箱: # 修改当前项目的git目标用户名及邮箱: git config user.name "newName"git config user.email "newEmail"# 修改全局git用户名和邮箱,

By Ne0inhk