八大算法排序@快速排序、递归版本一(C语言版本)

目录

  • 快速排序版本一
    • 概念
    • 算法思想
    • 快排步骤
    • 代码实现
    • 时间复杂度
    • 空间复杂度
    • 特性总结

快速排序版本一

概念

  快速排序(Quicksort)是一种高效的排序算法,它是由英国计算机科学家 Tony Hoare 在1960年提出的。快速排序是基于分治(Divide and Conquer)策略的算法,其基本思想是通过选择一个基准元素,将数组划分为两个子数组,使得左侧子数组的元素都小于基准元素,右侧子数组的元素都大于基准元素,然后对子数组进行递归排序。

快速排序的核心算法有多种实现方法,该文章介绍的版本是。借助双指针,递归的方式实现排序功能。




算法思想


注:为了方便理解,下文所说的指针,并不是概念上的指针,其实就是一个变量,只是为了更加形象的表达、讲解,所以用了指针这一形象的工具。

  原数组使用变量left 、 right 记录数组第一个和最后一个元素的下标。借助快排核心算法:
  在数组中选取一个元素,作为基准值。使用 “双指针” 分别从数组的左边和右边开始往中间挪动,要求
左边的 “指针” begin 从左到右寻找到比基准值大的数则停止,右边的 “指针” end 从右到左寻找到比基准值小的数则停止。 接着左右指针指向的数据互换。交换完成后,左边 ”指针” 继续往右边寻找,右边 “指针” 继续往左边寻找。
重复以上的动作,直到 begin“指针” 和 end “指针”相遇,交换begin 和 end所指的元素(自身交换),然后结束退出迭代过程。最后将一开始选定的基准值与左 “指针” begin指向的数据进行交换。
  最终的结果:最终与begin “指针” 所指的位置交换后的基准值,基准值左边的数都比基准值小,右边的数都比基准值大。假设数组排好升序的情况下,基准值所在的位置,便是升序下所在的位置。即根据以上的步骤,我们便完成数组中的一个元素(基准值)的排序。
  接着用变量div存储begin“指针”指向的下标,根据这个下标,将原数组一分为二,分别为[left , div-1] 、[div+1,right]。然后依次对这两个数组进行以上快排的核心算法,也就是递归的思想。通过每次递归一次完成一个“基准值”到达指定的位置。最终实现了整个数组的排序。

为了方便演示递归的流程,使用数组arr[] = { 6 , 2 , 3 , 9 , 4 } 进行升序过程的模拟实现。




原数组状态
一开始用变量left、right 存储待排序数组的第一个元素下标和最后一个元素的下标。div变量用来接收快排核心算法最终返回的begin存储的下标。
将数组的地址、待排序数组 left 和 right 传入核心算法函数,核心算法思路图解如下:

PartSort1

基准值一般选取待排序数组的最后一个元素的下标,如图选取元素4作为基准值
begin指向的下标0的元素6比4大,begin“指针”停止。
end指向的下标4的元素4没比基准值小,所以接着往左边找,直到end指向下标为2的元素3满足条件,end “指针” 停止。
接着begin 和 end 指向的两个元素交换。交换后重复迭代动作。
交换后的begin指针指向的元素从原来的6变成了3,不满足条件,所以接着往右边寻找,直到指向下标为2的元素6时才满足停止的条件,但是这时begin “指针” 和 end “指针” 相遇了,因此退出迭代,最终begin “指针” 指向的元素和基准值进行交换。
最终返回begin变量存储的下标值。


退出核心排序算法函数后,如下:

在这里插入图片描述
变量div接收核心排序算法函数返回的begin变量存储的下标值。这时,我们已经完成了对数组升序结果中,元素4的排序了。

接着将数组,以div所指的位置为分割点,划分为两个数组,分别为 [ left,div-1] 、[ div+1,right]。如下:

在这里插入图片描述

现在问题从,对数组arr[] = { 6 , 2 , 3 , 9 , 4 }的升序排序,转变为对数组 { 3 , 2 } 和 { 9 , 6 } 的升序排序了。但是对于这两个待排序的数组,排序的思路和上述的思路一致,借助 “双指针” 的方法,一步步递归下去,最终由一个个排好序的个体,返回到原数组时,形成有序的整体。




对于分割出来的数组的排序流程如下:

在这里插入图片描述

传入数组arr的地址,left 和 div-1给核心算法函数:

递归左数组

begin所在的元素比基准值2大,故停止不动;
end所在的元素没比基准值小,故向左移动,当指向下标0时,与begin “指针” 相遇,begin 和 end 所指的元素交换,而后结束、退出迭代过程。
最终begin指向的元素和基准值进行交换,然后返回begin存储的下标值。

退出核心算法函数,如下:

在这里插入图片描述
又实现了对于 [ left,div-1] 数组的排序。
注意:这里还需递归一次,但是可以通过边界判断直接返回。




同样的思路,对 [ div+1,right ] 所处的数组进行排序:

在这里插入图片描述

传入数组arr的地址,div+1 和 riht 下标范围,如下:

在这里插入图片描述

退出核心算法后:

在这里插入图片描述

后续,主要为返回递归,销毁调用的栈帧,回到一开始调用的函数。结束排序。

最后,实现了整个数组的升序排序效果。以上是对于“双指针版本” 的快速排序的大体流程讲解。
具体步骤如下。




快排步骤

基本步骤:

1、选择基准元素: 从数组中选择一个元素作为基准元素。选择基准元素的方法可以有多种,常见的包括选择第一个元素、最后一个元素或随机选择一个元素。

2、划分数组: 将数组中的其他元素按照与基准元素的大小关系分为两个子数组。比基准元素小的放在左侧,比基准元素大的放在右侧。相等的元素可以放在任一侧。

3、递归排序: 对左右两个子数组进行递归排序。递归的终止条件是子数组的大小为1或0,因为一个元素或没有元素的数组是已经有序的。

4、合并结果: 将排好序的左右子数组合并成最终的有序数组。




代码实现

void Swap(int* p1, int* p2)
{int tmp = *p1;*p1 = *p2;*p2 = tmp;
}// 三数取中法,排除 key 取到最大 和 最小的数值
// 如数组: 0 1 2 3 4 5 6 7 8 9 时  中间值为4,随后将 4 和 9 互换,再进行快排,即可将最坏的情况变为最优情况
int GetMidIndex(int* a, int begin, int end)
{int mid = (begin + end) / 2;if (a[begin] < a[mid]){if (a[mid] < a[end]){return mid;}else if (a[begin] > a[end]){return begin;}else{return end;}}else  //a[begin] > a[mid]{if (a[mid] > a[end]){return mid;}else if (a[begin] < a[end]){return begin;}else{return end;}}
}// 时间复杂度:O(N)
// [begin,end]    1、左右指针法
int PartSort1(int* a, int begin, int end)
{int midIndex = GetMidIndex(a, begin, end);Swap(&a[midIndex], &a[end]);  // 让最坏的情况不再会出现int keyindex = end;while (begin < end){// 注意:此处 要给个‘=’,若不然当左右第一找到的数都是a[keyindex] 会一直死循环while (begin < end && a[begin] <= a[keyindex]){begin++;}while (begin < end && a[end] >= a[keyindex]){end--;}Swap(&a[begin], &a[end]);}Swap(&a[begin], &a[keyindex]);return begin;
}/*极端情况下,快速排序最坏的情况下,每次选到的key都是最大的或者最小的  (有序或者接近有序)时间复杂度:O(N^2)最好情况下:时间复杂度:(类似于将N个数分散到二叉树中,高度为logN,每一层时间复杂度为 N )所以时间复杂度为:O( N*logN )空间复杂度:O(logN)
*/
// 快速排序 单趟排序
void QuickSort(int* a, int left, int right)
{assert(a);if (left >= right)	return;if ((right - left + 1) > 10){int div = PartSort1(a, left, right);//PrintArr(a + left, right - left + 1);//printf("[%d,%d]%d[%d,%d]\r\n", left, div - 1, div, div + 1, right);QuickSort(a, left, div - 1);QuickSort(a, div + 1, right);}else{// 小于等于10以内的数组,不再使用递归排序// 小区间使用插入排序去排,不再使用快速排序的递归排// 减少整体的递归次数InsertSort(a + left, right - left + 1);}
}




以上是关于快速排序,核心算法版本一“双指针”的演示流程和代码。可以结合这图形和代码自己演示一遍,更有利于理解。难点主要是递归的思想,需要注意是:
1、递归过程中核心思想是相同的,
2、注意递归返回的判定条件,防止出现栈溢出问题。



上述代码还有一点没讲到:
  三树取中法。主要是防止,每次的基准值取到的是数组中最大/最小的元素。如升序排序时,基准值每次取到的都是最大值的话,左 “指针” begin就得一直从左找到右,begin和end相遇了才退出;若基准值每次取到的都是最小值的话,那么右 “指针” 又得一直从右找到左,知道end和begin相遇才退出。这样就会大大的拉低程序的效率。
至此,我们发现如果原数组一开始便是有序的,比如 arr = { 2 , 3 , 4 , 6 , 9 },如果没有使用三树取中法的话,那么将出现最坏的情况,快排效率为O(N^2);
但是如果使用了三树取中法,取左右两边和中间的三个数中的既不是最大也不是最小的数,作为基准值。如{ 2 , 3 , 4 , 6 , 9 }这种情况,将 4 和 9 互换,4作为基准值,那么原先最坏的情况,将变成最好的情况,时间复杂度为O(N^logN)。


  除此之外,对于数组较小的排序,可以采用其他的排序方法,进行排序。因为快速排序得使用到递归,在性能上会造成栈帧上的开销。因此对于较小的数组排序时可以借助其他排序,上述代码使用的是插入排序。




时间复杂度

O( N*logN )

计算如下:
在这里插入图片描述
快排有点类似于将数组放到一个二叉树上,
在第一层选取1个元素排序、
在第二层选取2个元素排序、
在第三层选取4个元素排序、

在第n蹭选取2^(n-1)个元素排序。

每一层,都需要遍历一遍待排序的数组,大体上归纳统计,每一层时间复杂度为O(N)。
而整个“二叉树” 的高度为 logN。
所以时间复杂度为:O(N*logN)。
以上画的是理想下,每次都是均匀划分的。




空间复杂度

O(logN)

递归需要调取栈帧,递归的深度是logN,每次递归使用的空间是常数量O(1)。
所以空间上开销为:1*logN。
即空间复杂度为:O(logN)。




特性总结

1、快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序
2、时间复杂度:O(N*logN)
3、 空间复杂度:O(logN)
4、稳定性:不稳定

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

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

相关文章

修改对象的行为和值(代理)

文章目录 前言一、复制一个对象二、代理对象重点来了 总结 前言 最近遇到一个需求,需要在某个位置,统一处理对象的一些属性值&#xff1a; 方案有两种: 直接复制一份,将属性覆盖后,返回一个新对象搞一个代理类,代理这个对象,修改对象的原有行为和值,从而达到修改属性值的目的…

【GoLang入门教程】Go语言几种标准库介绍(四)

编程语言的未来&#xff1f; 文章目录 编程语言的未来&#xff1f;前言几种库fmt库 (格式化操作)关键函数&#xff1a;示例 Go库标准库第三方库示例 html库(HTML 转义及模板系统)主要功能&#xff1a;示例 总结专栏集锦写在最后 前言 上一篇&#xff0c;我们介绍了debug、enco…

关于“Python”的核心知识点整理大全65

目录 20.2.19 设置 SECRET_KEY 20.2.20 将项目从 Heroku 删除 注意 20.3 小结 附录 A 安装Python A.1.1 确定已安装的版本 A.1.2 在 Linux 系统中安装 Python 3 A.2 在 OS X 系统中安装 Python A.2.1 确定已安装的版本 A.2.2 使用 Homebrew 来安装 Python 3 注意 …

基于商品列表的拖拽排序后端实现

目录 一&#xff1a;实现思路 二&#xff1a;实现步骤 二&#xff1a;实现代码 三&#xff1a;注意点 一&#xff1a;实现思路 后台实现拖拽排序通常需要与前端进行配合&#xff0c;对商品的列表拖拽排序&#xff0c;前端需要告诉后端拖拽的元素和拖动的位置。 这里我们假…

Matlab三维绘图

绘制三维图plot3 t0:pi/50:10*pi; xsin(t); ycos(t); zt; plot3(x,y,z); 产生栅格数据点meshgrid 这个接口在绘制三维图像里面相当重要&#xff0c;很多时候要将向量变成矩阵才能绘制三维图。 x0:0.5:5; y0:1:10; [X,Y]meshgrid(x,y); plot(X,Y,o); x和y是向量&#xff0c;…

RocketMQ单机部署完整学习笔记

文章目录 前言一、RocketMQ是什么&#xff1f;二、使用步骤1.安装MQ1.安装JDK2.安装mq3.MQ配置(核心) 2.搭建可视化dashboard1.下载源码2.修改配置3.启动 3.整合java1.生产者2.消费者3.启动生产者4.启动消费者5.dashboard添加消费组 三、总结全部的配置 前言 本文是基于4.X版本…

LeetCode 2221. 数组的三角和

文章目录 1. 题目 2. 解题 1. 题目 给你一个下标从 0 开始的整数数组 nums &#xff0c;其中 nums[i] 是 0 到 9 之间&#xff08;两者都包含&#xff09;的一个数字。 nums 的 三角和 是执行以下操作以后最后剩下元素的值&#xff1a; num…

I.MX6ULL开发笔记(二)——硬件外设操作

0x01 点亮第一个RGB灯 在文章http://t.csdnimg.cn/EGWt9中有介绍Linux下文件目录&#xff0c;那么在Linux系统下&#xff0c;RGB灯也是一个设备&#xff0c;所以我们需要到/sys目录下去操作这个设备。 之后&#xff0c;我们进入到class目录&#xff0c;这里挂载着开发板上的外…

Mongodb删除操作中字符序对结果的影响

本文还是要从删除操作的语法说起。 db.collection.deleteMany(<filter>,{writeConcern: <document>,collation: <document>,hint: <document|string>} ) 删除语法中&#xff0c;可以指定数据写入策略&#xff0c;字符序和使用的索引字段。 字符序&a…

MySQL第四战:视图以及常见面试题(上)

目录 目录&#xff1a; 一.视图 1.介绍什么是视图 2.视图的语法 语法讲解 实例操作 二.MySQL面试题 1.SQL脚本 2.面试题实战 三.思维导图 目录&#xff1a; 随着数字化时代的飞速发展&#xff0c;数据库技术&#xff0c;特别是MySQL&#xff0c;已经成为IT领域中不可…

Mybatis简易搭建并查询数据库表内所有数据

搭建步骤 1.在mysql中创建user表&#xff0c;添加数据2.创建maven模块&#xff0c;导入坐标3.在资源文件夹中导入需要用到的logback.xml4.编写MyBatis 核心配置文件->替换连接信息 解决硬编码问题5.编写 SQL映射文件->统一管理sgl语句&#xff0c;解决硬编码问题6.编码定…

HackTheBox - Medium - Linux - Ambassador

Ambassador Ambassador 是一台中等难度的 Linux 机器&#xff0c;用于解决硬编码的明文凭据留在旧版本代码中的问题。首先&#xff0c;“Grafana”CVE &#xff08;“CVE-2021-43798”&#xff09; 用于读取目标上的任意文件。在研究了服务的常见配置方式后&#xff0c;将在其…