八大算法排序@快速排序、递归版本一(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.mzph.cn/news/601630.shtml

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

相关文章

C++中string的库函数

山再高&#xff0c;往上攀&#xff0c;总能登顶&#xff01; 路再长&#xff0c;走下去&#xff0c;定能到达&#xff01; &#x1f3a5;烟雨长虹&#xff0c;孤鹜齐飞的个人主页 &#x1f525;个人专栏c 期待小伙伴们的支持与关注&#xff01;&#xff01;&#xff01; 目录 前…

Pandas DataFrame中将True/False映射到1/0

在本文中&#xff0c;我们将看到如何在Pandas DataFrame中将True/False映射到1/0。True/False到1/0的转换在执行计算时至关重要&#xff0c;并且可以轻松分析数据。 1. replace方法 在这个例子中&#xff0c;我们使用Pandas replace()方法将True/False映射到1/0。在这里&…

【Spark精讲】记一个SparkSQL引擎层面的优化:SortMergeJoinExec

SparkSQL的Join执行流程 如下图所示&#xff0c;在分析不同类型的Join具体执行之前&#xff0c;先介绍Join执行的基本框架&#xff0c;框架中的一些概念和定义是在不同的SQL场景中使用的。 在Spark SQL中Join的实现都基于一个基本的流程&#xff0c;根据角色的不同&#xff0…

信息论与编码期末复习——计算题+基础汇总(二)

个人名片&#xff1a; &#x1f981;作者简介&#xff1a;一名喜欢分享和记录学习的在校大学生 &#x1f42f;个人主页&#xff1a;妄北y &#x1f427;个人QQ&#xff1a;2061314755 &#x1f43b;个人邮箱&#xff1a;2061314755qq.com &#x1f989;个人WeChat&#xff1a;V…

【计算机毕业设计】SSM网上外卖订餐管理系统

项目介绍 该项目为前后台项目&#xff0c;分为普通用户与管理员两种角色&#xff0c;前台普通用户登录&#xff0c;后台管理员登录&#xff1b; 普通用户主要功能包括&#xff1a; 登录注册&#xff0c;查看商品&#xff0c;提交订单&#xff0c;然后留言&#xff0c;查看购物…

DS|静态查找

题目一&#xff1a;DS静态查找 -- 顺序查找 题目描述&#xff1a; 给出一个队列和要查找的数值&#xff0c;找出数值在队列中的位置&#xff0c;队列位置从1开始 要求使用带哨兵的顺序查找算法 输入要求&#xff1a; 第一行输入n&#xff0c;表示队列有n个数据 第二行输入…

CAN通信的基本原理与实现方法

一. CAN协议概念 1.1 CAN 协议简介 CAN 是控制器局域网络 (Controller Area Network) 的简称&#xff0c;它是由研发和生产汽车电子产品著称的德国 BOSCH 公司开发的&#xff0c;并最终成为国际标准(ISO11519以及ISO11898),是国际上应用最广泛的现场总线之一。差异点如下&…

手拉手springboot3整合mybatis-plus多数据源

环境介绍 技术栈 springbootmybatis-plusmysql 软件 版本 mysql 8 IDEA IntelliJ IDEA 2022.2.1 JDK 17 Spring Boot 3.1.7 dynamic-datasource 3.6.1 mybatis-plus 3.5.3.2 加入依赖 <dependency><groupId>com.baomidou</groupId><arti…

苹果macOS 14.3开发者预览版Beta 2发布 修复API会意外失败的问题

1 月 4 日消息&#xff0c;苹果向 Mac 电脑用户推送了 macOS 14.3 开发者预览版 Beta 2 更新&#xff08;内部版本号&#xff1a;23D5043d&#xff09;&#xff0c;本次更新距离上次发布隔了 22 天。 macOS Sonoma 14.3 Beta 2 主要以修复 BUG、提高安全性为主。根据苹果官方更…

如何做一个炫酷的Github个人简介(3DContribution)

文章目录 前言3D-Contrib第一步第二步第三步第四步第五步第六步 前言 最近放假了&#xff0c;毕设目前也不太想做&#xff0c;先搞一点小玩意玩玩&#xff0c;让自己的github看起来好看点。也顺便学学这个action是怎么个事。 3D-Contrib 先给大家看一下效果 我的个人主页&am…

面试算法96:字符串交织

题目 输入3个字符串s1、s2和s3&#xff0c;请判断字符串s3能不能由字符串s1和s2交织而成&#xff0c;即字符串s3的所有字符都是字符串s1或s2中的字符&#xff0c;字符串s1和s2中的字符都将出现在字符串s3中且相对位置不变。例如&#xff0c;字符串"aadbbcbcac"可以由…

trino-435:dynamic catalog数据库存储代码实现

一、dynamic catalog数据库存储源码分析 dynamic catalog的实现主要涉及到两个类&#xff1a;CoordinatorDynamicCatalogManager、WorkerDynamicCatalogManager&#xff0c;这两个类的详细信息如下&#xff1a; 这两个类主要提供了对catalog的增删改查的方法。trino-435源码中…

​三子棋(c语言)

前言&#xff1a; 三子棋是一种民间传统游戏&#xff0c;又叫九宫棋、圈圈叉叉棋、一条龙、井字棋等。游戏规则是双方对战&#xff0c;双方依次在9宫格棋盘上摆放棋子&#xff0c;率先将自己的三个棋子走成一条线就视为胜利。但因棋盘太小&#xff0c;三子棋在很多时候会出现和…

推荐一款Apache开源的文档内容解析工具

shigen坚持更新文章的博客写手&#xff0c;擅长Java、python、vue、shell等编程语言和各种应用程序、脚本的开发。记录成长&#xff0c;分享认知&#xff0c;留住感动。 hello&#xff0c;伙伴们&#xff0c;在闲暇的时候逛了一下掘金&#xff0c;发现了这样的一篇文章&#xf…

机器人相关知识

机器人学&#xff08;Robotics) 一些基础概念 位姿 位姿位置姿态 位姿的表示 刚体 刚性物体是一组粒子的集合&#xff0c;其中任意两个粒子之间的距离保持固定&#xff0c;不受物体运动或施加在物体上的力的影响。 “完全不可变形”的物体就是刚体。 刚体位置 刚性连杆 …

【网络技术】【Kali Linux】Wireshark嗅探(八)动态主机配置协议(DHCP)

一、实验目的 本次实验使用 Wireshark &#xff08;“网鲨”&#xff09;流量分析工具进行网络流量嗅探&#xff0c;旨在初步了解动态主机配置协议&#xff08;DHCP协议&#xff09;的工作原理。 二、DHCP协议概述 动态主机配置协议&#xff08; D ynamic H ost C onfigurat…

工智能基础知识总结--导出SVM要优化的问题

导出SVM要优化的问题 对于上图中这样一个二分类线性可分问题,期望找到一个分类超平面将正负类分开,SVM就是一个用来寻找这样的分类超平面的算法。 定义正负类的标签分别为1、-1,分类超平面的表达式为 f ( x ) = w T x + b f(x)=w^Tx+b

使用代理IP保护爬虫访问隐私数据的方法探讨

目录 前言 1. 获取代理IP列表 2. 随机选择代理IP 3. 使用代理IP发送请求 4. 处理代理IP异常 总结 前言 保护爬虫访问隐私数据是一个重要的安全问题。为了保障用户的隐私&#xff0c;很多网站会采取限制措施&#xff0c;如封禁IP或限制访问频率。为了绕过这些限制&#x…

宏电股份5G RedCap终端产品助力深圳极速先锋城市建设

12月26日&#xff0c;“全城全网&#xff0c;先锋物联”深圳移动5G-A RedCap助力深圳极速先锋城市创新发布会举行&#xff0c;宏电股份携一系列5G RedCap终端产品应邀参与创新发布会&#xff0c;来自全国5G生态圈的各界嘉宾、专家学者济济一堂&#xff0c;共探信息化数字化创新…

L1-078:吉老师的回归

题目描述 曾经在天梯赛大杀四方的吉老师决定回归天梯赛赛场啦&#xff01; 为了简化题目&#xff0c;我们不妨假设天梯赛的每道题目可以用一个不超过 500 的、只包括可打印符号的字符串描述出来&#xff0c;如&#xff1a;Problem A: Print "Hello world!"。 众所周知…