快速排序(Quick Sort)(C语言) 超详细解析!!!

生活的本质是什么呢? 无非就是你要什么就不给你什么. 而生活的智慧是什么呢? 是给你什么就用好什么. ---马斯克

索引

  • 一. 前言
  • 二. 快速排序的概念
  • 三. 快速排序的实现
    • 1. hoare
    • 2. 挖坑法
    • 3. 前后指针法
  • 总结


正文开始

一. 前言

接上文, 前面我们了解了插入排序, 与优化版本希尔排序, 那么本篇博客将详细介绍另外一种排序, 快速排序.

博客主页: 酷酷学!!!

感谢关注~

二. 快速排序的概念

快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,

其基本思想为:

任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。

三. 快速排序的实现

将区间按照基准值划分为左右两半部分的常见方式有三种版本

1. hoare

在这里插入图片描述
如图所示, 排序的思想为

第一步:
两个下标, 一个从后往前走, 一个从前往后走, 然后定义一个基准值key, 下表为keyi,这里要注意要保证end先走, 才能让最后相遇的位置小于keyi的位置, 因为end先走无非就两种情况, 第一种, end找到了比key小的值, 停下来, 最后一次begin相遇end,交换相遇结点与key结点, 第二种情况, end相遇begin, 此时end没有找到比keyi小的值, 然后最后一次与begin相遇, 此时begin的值为上一次交换后的值, 所以比keyi小.然后相遇与keyi交换位置, 此时6这个位置就排好了

在这里插入图片描述
第二步:

因为6这个位置排好了, 所以进行下一次的排序,以6划分为两个区域,然后再次递归调用函数, 如果最后的区间不存在或者只有一个值那么就结束函数.当left==right时此时区间只有一个值就不需要排序, 区间不存在也不需要排序.

void QuickSort(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[begin], &a[keyi]);keyi = begin;QuickSort(a, left, keyi - 1);QuickSort(a, keyi + 1, right);
}

我们来分析一下快速排序的时间复杂度, 目前来看, 每一层的时间复杂度为O(N), 高度为logn, 所以时间复杂度为O(NlogN), 但是这是在较好的情况下, 也就是相当于二叉树, 左右区间差不多大, 那么如果是有序的一系列数字, 那么这个排序就很不友好, 很容易栈溢出,递归层次很深, 就不是logn, 那么如何进行优化呢, 就需要我们修改它的key值, 不大不小为最好的情况, 为了避免有序情况下,效率退化, 可以选择 随机选key法 或者 三数取中法

在这里插入图片描述

这里我们来介绍一下三数取中法的优化.

int GetMidi(int* a, int left, int right)
{int midi = (right - left) / 2;if (a[left] < a[midi]){if (a[midi] < a[right]){return midi;}else if(a[left] < a[right]){return right;}else{return left;}}else//a[left] > a[midi]{if (a[left] < a[right]){return left;}else if (a[midi] > a[right]){return midi;}else{return right;}}
}

我们可以定义一个函数, 然后选取区间内最左边的值, 最右边的值, 中间的值,那个不大又不小的值作为key, 利用上述函数, 我们的代码可以改写为

void QuickSort(int* a, int left, int right)
{if (left >= right){return;}//三数取中int midi = GetMidi(a, left, right);Swap(&a[left], &a[midi]);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[begin], &a[keyi]);keyi = begin;QuickSort(a, left, keyi - 1);QuickSort(a, keyi + 1, right);
}

有了上述的优化, 我们的代码效率就算在有序的情况下也能大大的提升效率, 但是还有一个问题, 我们知道, 二叉树中递归调用, 最后一层递归调用的次数占据了总调用次数的一半, 我们可以把最后几层的递归调用优化掉, 如果区间为十个数那么我们选择其他的排序方法, 如果选堆排序, 十个数还需要建堆比较麻烦, 如果使用希尔排序, 那么就是多此一举, 数据量不大选择希尔排序属实有点浪费了, 那我们就在时间复杂度为O(N^2)的排序中选择一个相对来说效率比较高的插入排序.优化后的代码如下:

void QuickSort(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, 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[begin], &a[keyi]);keyi = begin;QuickSort(a, left, keyi - 1);QuickSort(a, keyi + 1, right);}}

此优化在release版本下较为突出.

2. 挖坑法

前面单趟排序的方法由hoare发明之后又有另外的一种排序方法, 挖坑法, 它的思路比hoare排序的思想更容易理解

在这里插入图片描述

//挖坑法
int PartSort3(int* a, int left, int right)
{//三数取中int midi = GetMidi(a, left, right);Swap(&a[left], &a[midi]);int keyi = left;int begin = left, end = right;int tmp = a[keyi];while (begin < end){while (begin < end && a[end] >= a[keyi]){end--;}a[keyi] = a[end];keyi = end;while (begin < end && a[begin] <= a[end]){begin++;}a[keyi] = a[begin];keyi = begin;}a[keyi] = tmp;return keyi;
}void QuickSort(int* a, int left, int right)
{if (left >= right){return;}if (right - left + 1 < 10){InsertSort(a + left, right - left + 1);}int keyi = PartSort3(a,left,right);QuickSort(a, left, keyi - 1);QuickSort(a, keyi + 1, right);
}

我们可以直接把单趟排序的算法拿出来封装成一个函数, 如图所示挖坑法特别好理解, 先让keyi的值存放在一个临时变量中, 挖一个坑,先让对面的先走, 如果遇到比end小的就把它拿出来填入坑中, 然后begin在开始走, 遇到大的填入坑中, 这样依次轮换,相遇时将keyi的值填入坑中.

3. 前后指针法

在这里插入图片描述

如图所示, 初始时, prev指针指向序列开头, cur指针指向prev指针的后一个位置, 然后判断cur指针指向的数据是否小于key, 则prev指针后移一位, 并于cur指针指向的内容交换, 然后cur++,cur指针指向的数据仍然小于key,步骤相同, 如果cur指针指向的数据大于key, 则继续++,最后如果cur指针已经越界, 这时我们将prev指向的内容与key进行交换.

可以看到也就是一个不断轮换的过程, 将大的数依次往后翻转, 小的数往前挪动, 这种方法代码写起来简洁, 当然我们也可以优化一下, 让cur和prev相等时就没必要进行交换

//前后指针版
int PartSort2(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[cur], &a[prev]);}cur++;}Swap(&a[prev], &a[keyi]);return prev;
}void QuickSort(int* a, int left, int right)
{if (left >= right){return;}if (right - left + 1 < 10){InsertSort(a + left, right - left + 1);}int keyi = PartSort2(a,left,right);QuickSort(a, left, keyi - 1);QuickSort(a, keyi + 1, right);
}

以上是递归版本的实现, 那么我们知道, 递归层次太深会容易栈溢出, 那么能不能采用非递归的实现呢, 答案肯定是可以的, 我们可以使用栈这个数据结果, 然后将区间入栈中, 栈不为空循环对区间进行排序, 一般linux系统下函数栈帧的空间的大小为8M, 而堆空间大小为2G, 所以这个空间是非常大的, 而且栈也是动态开辟的空间, 在堆区申请, 所以几乎不需要考虑空间不够, 我们可以将区间压栈, 先压入右区间, 在压入左区间, 然后排序的时候先排完左区间再排右区间, 这是一种深度优先算法(DFS), 当然也可以创建队列, 那么走的就是一层一层的排序, 每一层的左右区间都排序, 是一种广度优先算法(BFS).
代码实现:

这里可以创建一个结构体变量存放区间, 也可以直接压入两次, 分别把左值和右值压入

//非递归版
#include"stack.h"
void QuickSortNonR(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 = PartSort1(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);}}Destroy(&st);
}

总结

快速排序是一种常用的排序算法,其时间复杂度为O(nlogn)。它的基本思想是通过一趟排序将待排序序列分割成独立的两部分,其中一部分的所有元素都比另一部分的所有元素小,然后再对这两部分分别进行排序,最终得到一个有序序列。


期待支持, 感谢关注~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/23339.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

HQL面试题练习 —— 向用户推荐好友喜欢的音乐

目录 1 题目2 建表语句3 题解 题目来源&#xff1a;腾讯。 1 题目 现有三张表分别为&#xff1a; 用户关注表 t_follow(user_id,follower_id)记录用户ID及其关注的人ID&#xff0c;请给用户1 推荐他关注的用户喜欢的音乐名称 ------------------------ | user_id | follower…

六月可以闭眼入的宠物空气净化器:希喂、安德迈、霍尼韦尔真实PK

俗话说得好&#xff0c;猫咪一年到头都在掉毛&#xff0c;仿佛它们是四季常在的"蒲公英"&#xff0c;随时随地都在播撒毛发。猫毛不仅遍布它们自己的身体&#xff0c;还可能飘到你的床铺、沙发、衣物上……面对这样的状况&#xff0c;既要应对无处不在的猫毛&#xf…

基于卷积神经网络(CNN)的垃圾分类模型研究

摘要&#xff1a; 随着城市化进程的加快&#xff0c;垃圾问题日益严重。传统的垃圾分类方法存在效率低下、准确率不高等问题。本文提出了一种基于卷积神经网络&#xff08;CNN&#xff09;的垃圾分类模型&#xff0c;该模型能够自动识别并分类不同类型的垃圾。实验表明&#xf…

Kruskal算法求最小生成树

#include <stdio.h> #include <stdlib.h> #include <string.h> #include <assert.h> #define MAX 100 #define NO INT_MAX//NO表示没有边&#xff0c;相当于INFtypedef struct Graph {int arcnum;int vexnum;char vextex[MAX][20];int martrix[MAX][MA…

什么无线领夹麦克风音质最好?领夹麦克风品牌排行榜前十名推荐

​在当今的数字化浪潮中&#xff0c;个人声音的传播和记录变得尤为重要。无论是会议中心、教室讲台还是户外探险&#xff0c;无线领夹麦克风以其卓越的便携性和连接稳定性&#xff0c;成为了人们沟通和表达的首选工具。面对市场上琳琅满目的无线麦克风选择&#xff0c;为了帮助…

Doris insert into 插入语句执行成功,且select查询成功,返回结果不报错,但查不到该插入数据

问题&#xff1a;Doris insert into 正常执行成功&#xff0c;select 查询也执行成功&#xff0c;但查不到该写入数据 原因&#xff1a;由于有其他 insert commit 事务待提交且该任务处于锁的状态&#xff0c;导致不断在回滚&#xff0c;进而造成其他的insert into 语句也执行成…

26 - 超过5名学生的课(高频 SQL 50 题基础版)

26 - 超过5名学生的课 select class fromCourses group byclass havingcount(*)>5;

Seed-TTS语音编辑有多强?对比实测结果让你惊叹!

GLM-4-9B 开源系列模型 前言 就在最近&#xff0c;ByteDance的研究人员最近推出了一系列名为Seed-TTS的大规模自回归文本转语音(TTS)模型,能够合成几乎与人类语音无法区分的高质量语音。那么Seed-TTS的表现究竟有多强呢?让我们一起来感受下Seed-TTS带来的惊喜吧! 介绍Seed-TTS…

组装服务器重装linux系统【idrac集成戴尔远程控制卡】

&#x1f341;博主简介&#xff1a; &#x1f3c5;云计算领域优质创作者 &#x1f3c5;2022年CSDN新星计划python赛道第一名 &#x1f3c5;2022年CSDN原力计划优质作者 &#x1f3c5;阿里云ACE认证高级工程师 &#x1f3c5;阿里云开发者社区专…

数据结构 | 超详细讲解七大排序(C语言实现,含动图,多方法!)

目录 ​编辑 排序的概念 常见排序算法 ​编辑 1.冒泡排序 &#x1f379;图解 &#x1f973;代码实现 &#x1f914;时间复杂度 2.插入排序 &#x1f379;图解 &#x1f334;深度剖析 &#x1f34e;代码思路 &#x1f973;代码实现 &#x1f914;时间复杂度 3.希尔…

2024 年适用于 Linux 的 5 个微软 Word 替代品

对于那些最近由于隐私问题或其他原因而转向 Linux 的用户来说&#xff0c;可能很难替换他们最喜欢的、不在 Linux 操作系统上运行的应用程序。 寻找流行程序的合适替代品可能会成为一项挑战&#xff0c;而且并不是每个人都准备好花费大量时间来尝试弄清楚什么可以与他们在 Win…

读书笔记|《把自己变成稀缺资产》:我们都拥有100分的欲望,却只有1分的耐心。

哈喽&#xff0c;你好啊&#xff0c;我是雷工&#xff01; 最近在读一本书《把自己变成稀缺资产》&#xff0c;其中一章讲到耐心的重要性&#xff0c;很有共鸣。 当今社会&#xff0c;生活节奏越来越快&#xff0c;我们都在急于求成的追求结果&#xff0c;对过程越来越缺乏耐…

2024050402-重学 Java 设计模式《实战责任链模式》

重学 Java 设计模式&#xff1a;实战责任链模式「模拟618电商大促期间&#xff0c;项目上线流程多级负责人审批场景」 一、前言 场地和场景的重要性 射击&#x1f3f9;需要去靶场学习、滑雪&#x1f3c2;需要去雪场体验、开车&#x1f697;需要能上路实践&#xff0c;而编程…

Scanpy(4)用与数据整合和批次处理

Scanpy包,用与数据整合和批次处理,包含批次效应的BBKNN算法和用于对比的ingest基础算法比较,及其原理简介。 1. 依赖: (1)数据集(全部需要挂VPN): PBMC:pbmc3k_processed()(需要下载);pbmc68k_reduced()(scanpy自带)Pancreas(需要下载)(2)Python包:Scanp…

【Python】把xmind转换为指定格式txt文本

人工智能训练通常需要使用文本格式&#xff0c;xmind作为一种常规格式不好进行解析&#xff0c;那如何把xmind转换为txt格式呢&#xff1f; 软件信息 python python -v Python 3.9.13 (tags/v3.9.13:6de2ca5, May 17 2022, 16:36:42) [MSC v.1929 64 bit (AMD64)] on win32…

Python 包安装及常用命令【python 入门】

背景&#xff1a; 近期看到一个项目&#xff0c;做微信只能机器人&#xff0c;服务是使用python搭建的&#xff0c;于是拷贝下来自己打算跑一跑&#xff0c;部署一下&#xff0c;可是自己又没有python的经验&#xff0c;于是各种查资料学习&#xff0c;跟着敲一敲&#xff0c;顺…

Go 1.19.4 切片与子切片-Day 05

1. 切片 1.1 介绍 切片在Go中是一个引用类型&#xff0c;它包含三个组成部分&#xff1a;指向底层数组的指针&#xff08;pointer&#xff09;、切片的长度&#xff08;length&#xff09;以及切片的容量&#xff08;capacity&#xff09;&#xff0c;这些信息共同构成了切片的…

JavaWeb_SpringBootWeb案例

环境搭建&#xff1a; 开发规范 接口风格-Restful&#xff1a; 统一响应结果-Result&#xff1a; 开发流程&#xff1a; 第一步应该根据需求定义表结构和定义接口文档 注意&#xff1a; 本文代码从上往下一直添加功能&#xff0c;后面的模块下的代码包括前面的模块&#xff0c…

Xmind Pro 2024 专业版激活码(附下载链接)

说到思维导图&#xff0c;就不能不提 Xmind。这是一款优秀的思维导图工具&#xff0c;拥有着丰富的导图模板&#xff0c;漂亮的界面和配色&#xff0c;以及各种各样的创意工具。 新架构速度更快 采用全新 Snowdancer 引擎&#xff0c;一种堪称「黑科技」的先进图形渲染技术。…

翘首以盼的抗锯齿

Antialiasing 实际的图形学中是怎么实现反走样的呢&#xff1f; 我们不希望实际产出的图形有锯齿效果&#xff0c;那怎么办呢&#xff1f; 从采样的理论开始谈起吧 Simpling theory 照片也是一种采样&#xff0c;把景象打散成像素放到屏幕上的过程&#xff1a; 还可以在不…