排序算法系列二:归并排序、快速排序

零、说在前面

        本文是一个系列, 入口请移步这里

一、理论部分

1.4:归并排序

1.4.1:算法解读:

        使用二分法和插入排序两种算法的思想来实现。流程分为“拆分”、“合并”两大部分,前者就是普通的二分思想,将一个数组拆成n个子组;后者是在每个子组内部用插入法排序,在子组间定义一个辅助数组和三个指针,用辅助数组搭配指针选数进行排序,再将两个子组合并;最终将所有子组合并成一个有序的数组。

1.4.2:时间复杂度

        因为用到了分治思想,因此时间复杂度除了与数据量有关,还与遍历次数(即对数据量二分次数 logN )有关,因此时间复杂度为 O(N logN)

        在最优与最坏情况,二分操作耗时不会节约、归并比较阶段操作耗时不会节约,因此在遍历的数据量不变,遍历的轮次不变的情况下,时间复杂度固定为O(N logN)

1.4.3:优缺点:

        算法耗时稳定,但需要额外的辅助空间,需付出空间复杂度的代价。适用于大数据量的排序

1.4.4:代码:
/*** date:    2024-06-23* author:  dark* description: 归并排序算法(由小到大)*/
public class Merge {/*** 定义原始数组长度*/public static int arrayLength;/*** 定义临时数组*/public static Integer[] tempArrays;/*** 定义临时变量*/public static int tempArrayPointer = 0;/*** 逻辑步骤:1:接受一个数组,对它进行两步操作,即 先拆分后归并。并开辟一个与原数组等长的临时数组和四个指针*         2:拆分:用数组的中间坐标作为拆分的边界,并将得到的两个子数组递归拆分,直至每个子组元素小于等于2为止,并保证组内元素有序。*         3:归并:从左向右,选择相邻的两个子组,令三个指针分别指向这两个子组的首元素坐标位置、以及左子组坐标对应临时坐标中的位置。*                对两个子组的元素两两比较,将其中较小的元素拷贝到临时数组中,并控制较小元素所在子组的指针和临时数组的指针右移一位。*                直至这两个子组的元素比对完毕,然后将这对子组进行拼接并将临时数组中的元素对位拷贝到新子组中。*                以此类推处理同级别的所有子组,并递归处理合并后的子组,直至将所有子组合并成一个有序数组。*          4:注意:为逻辑方便,整个流程的右子数组的终止位置设置在数组越界1位的位置 使用时需谨慎* @param arrays*/public void mergeSort(Integer[] arrays){arrayLength = arrays.length;/*** 定义左、右子组的首元素指针、右子组的尾部指针,并为临时数组指针赋初值。* 右指针的起始位置根据数组长度加1除2后确定,这样得到的右子组位置略偏右* 为了后续逻辑方便,特将 数组终止位置坐标 设置为 数组实际长度*/int startPointer = 0,  endPointer = arrayLength;tempArrays = new Integer[arrayLength];split(arrays, startPointer, endPointer);}/*** 拆分。当传入的子组元素数量小于等于2时,进行合并(原因是避免 当待拆分元素是3个时,给后续操作带来困难)*      否则递归调用本方法继续拆分。注意:应通过准确计算左右索引的位置控制拆分的准确* @param arrays* @param startPointer* @param endPointer*/public void split(Integer[] arrays, int startPointer, int endPointer){/*** 因为传入的 endPointer 的坐标处于数组越界1位的位置,* 因此作为右子数组的起始位置的  middlePointer 等于 起点与终点坐标之和的 二分之一*/int middlePointer = (startPointer + endPointer)/2 ;/**** 因此下面的逻辑判断我交给 if 判断来使用, 而非交给 while 来使用* 又因为startPointer 是从0开始计数,因此下面的逻辑判断的实际意义是:* 当数组中大于3个元素需要继续拆分,否则就可以归并了*/if(endPointer-startPointer > 2) {/*** 二分拆解原数组,通过计算得到两个子组各自的左右指针坐标*/split(arrays, startPointer, middlePointer);split(arrays, middlePointer, endPointer);}merge(arrays, startPointer, middlePointer, endPointer);}/*** 归并:    从左向右,选择相邻的两个子组,令三个指针分别指向这两个子组的首元素坐标位置、以及左子组坐标对应临时坐标中的位置。*          对两个子组的元素两两比较,将其中较小的元素拷贝到临时数组中,并控制较小元素所在子组的指针和临时数组的指针右移一位。*          直至这两个子组的元素比对完毕,然后将这对子组进行拼接并将临时数组中的元素对位拷贝到原数组中。*          以此类推处理同级别的所有子组,并递归处理合并后的子组,直至将所有子组合并成一个有序数组。* @param arrays* @param startPointer      左子数组起始位指针* @param middlePointer     右子数组起始位指针* @param endPointer        右子数组终止位指针(这个参数应始终保持处于越界1位的位置,这样便于判断 middlePointer 是否越界)*/public void merge(Integer[] arrays, int startPointer, int middlePointer, int endPointer){/*** 定义 右子数组的起始位副本,用来判断 startPointer 与 middlePointer 是否越界* 定义 左子数组的起始位副本,用来循环将副本数组的数据同步到原始数组中。* 临时数组指针,初始指向本次子数组首位*/int startPointerTmp = startPointer, middlePointerTmp = middlePointer;tempArrayPointer = startPointer;/*** 当 两个指针都没越界,则执行下面逻辑,两个都临界/越界,则比对结束跳出循环*/while(true){/*** 若左右指针都没越界,则通过比两个子数组元素的大小决定将谁放入临时数组* 若有一个指针临界或越界,则直接将另一个子数组的元素都放入临时数组即可*/if(startPointer < middlePointerTmp && middlePointer < endPointer){if(arrays[startPointer] < arrays[middlePointer]){tempArrays[tempArrayPointer++] = arrays[startPointer++];} else {tempArrays[tempArrayPointer++] = arrays[middlePointer++];}} else if (startPointer == middlePointerTmp && middlePointer < endPointer) {while (middlePointer < endPointer){tempArrays[tempArrayPointer++] = arrays[middlePointer++];}} else if (middlePointer == endPointer && startPointer < middlePointerTmp) {while (startPointer < middlePointerTmp){tempArrays[tempArrayPointer++] = arrays[startPointer++];}} else {for (int i = startPointerTmp; i < endPointer; i++) {arrays[i] = tempArrays[i];}break;}}}
}

1.5:快速排序

1.5.1:算法解读:

        在每轮循环中,以一个人为指定的元素作为数组的分界线,将当前数组中小于它的元素置于其左侧,大于的置于其右侧,形成两个子数组,再对两个子数组重复上述过程,直至无法细分为止,此时数组有序。

1.5.2:时间复杂度

        因为用到了分治思想,因此时间复杂度除了与数据量有关,还与遍历次数(即对数据量二分次数 logN )有关,因此时间复杂度为 O(N logN)

        在最优情况下,左右指针应该在数组中间附近汇合,这样每次都能将数组二分,其时间复杂度为 O(N logN),最坏情况下数据顺序倒排,每一轮的分界线都不能实现对数组的二分,算法会退化成类似选择排序的样子,时间复杂度为 O(N^{2})

1.5.3:优缺点:

        在最优与最差情况下的表现差距较大,是一个不稳定的排序算法。适用于大数据量的排序。

1.5.4:代码:
/*** date:    2024-06-25* author:  dark* description: 快速排序算法(由小到大)*/
public class Quick {/*** 初始数组长度*/public Integer arrayLength = 0;/*** 定义向左(即找到比分界线更小元素)的指针 和 向右(更大元素)的指针*/public Integer leftWardPointer, rightWardPointer;/*** 为便于后续数组内的数据交换,定义一个静态变量,作为接受外部数据的副本*/private static Integer[] arrayLocal;/*** 逻辑步骤:1: 接受一个数组*         2: 从数组中选择一个随机数(比如首元素)作为分界线,目的是将数组中小于分界线的元素置于其左侧,大于的置于其右侧。*            定义两个临时指针分别指向数组的首尾(为方便后续逻辑,这里的尾指针指向的是尾元素的后一位)*            令左向和右向临时指针相向移动,理想状态下左向指针扫描过的都是大于分界线的数据,右向指针扫描的都是小于分界线数据*            因此当两个指针遇到非理想数据时停止扫描,交换两个临时指针对应的数据*               当左右指针相遇,说明二者扫过的都是符合理想状态的数据,且相遇点应该就是分界线指针所在位置。*            将分界线指针交换到二者相遇点即可。*            特殊情况一、左向指针移到头部也没遇到一个符合的数据,说明分界线就是当前数组最小的元素,毋需交换分界线位置*            特殊情况二、左向指针移动了一格,但右向指针与左向重合也没一个符合的数据,说明所有数据都小于分界线,*                      将分界线交换到数组尾部即可。*         3: 在左右子数组中递归重复步骤2的过程,直至子数组中仅剩一个元素,然后将左右子数组与各自的分界线进行归并。* @param arrays*/public void quickSort(Integer[] arrays){arrayLength = arrays.length;arrayLocal = arrays;leftWardPointer = arrayLength;rightWardPointer = 0;exchangeLeftOrRight(arrayLocal, rightWardPointer, leftWardPointer);}/*** 以当前数组首元素为分界线,将以 leftWardPointer 为起点,以 rightWardPointer 为终点的数组拆分成左右两个子数组* @param arrays* @param leftWardPointer       位于数组尾部* @param rightWardPointer      位于数组首部*/public void exchangeLeftOrRight(Integer[] arrays, Integer rightWardPointer, Integer leftWardPointer){/*** 分界线的初始位置 为右向指针的初始位置。*/Integer boundaryPointer = rightWardPointer;Integer temp ;/*** 定义左右向指针的初始位置,以便递归使用*/Integer leftWardPointerTmp = leftWardPointer, rightWardPointerTmp = rightWardPointer;/*** 每次遍历至少得有两个及以上元素才行*/while(leftWardPointerTmp - rightWardPointerTmp >= 2){/*** 1:当左向指针 与 右向指针副本重合,说明到达了终点* 2:在左向指针移动中,若遇到小于分界线的元素,则记下当前指针位置并跳出循环,备用* 3:因为左向指针的初始值是 数组长度 ,因此可以先 ++ 再比对* 4:下面的 右向指针也是类似的逻辑*/while(--leftWardPointer > rightWardPointer){if(arrayLocal[leftWardPointer] <= arrayLocal[boundaryPointer]) {break;}}while(++rightWardPointer < leftWardPointer){if(arrayLocal[rightWardPointer] >= arrayLocal[boundaryPointer])break;}/*** 走到这里若左右指针尚未重合,说明可以交换二者指向的数据* 当左向指针位置小于等于右向指针,说明数组中数据分布符合分界线的要求* 此时将分界线交换到 左右向指针重合位置或左向指针(因此时右向跨越左向指针,说明右向指针走的有点儿多了,故而不选右指针的位置)的位置即可。* 即使左右向指针有一个没移动过也没关系,分析过程见“逻辑步骤”第二步的特殊情况分析*/if(leftWardPointer > rightWardPointer ){temp = arrayLocal[leftWardPointer];arrayLocal[leftWardPointer] = arrayLocal[rightWardPointer];arrayLocal[rightWardPointer] = temp;} else if (leftWardPointer <= rightWardPointer) {temp = arrayLocal[boundaryPointer];arrayLocal[boundaryPointer] = arrayLocal[leftWardPointer];arrayLocal[leftWardPointer] = temp;boundaryPointer = leftWardPointer;/*** 因为左向指针的特殊处理逻辑(从子数组长度+1开始算,因此下面第一行第三个参数不能传 boundaryPointer+1)*/exchangeLeftOrRight(arrayLocal, rightWardPointerTmp, boundaryPointer);exchangeLeftOrRight(arrayLocal, boundaryPointer+1, leftWardPointerTmp);break;}}}
}

二、对比

2.4:归并与快速

        二者都运用了分治思想,都适用于大量数据情况下的排序,前者在最优最差情况下表现稳定,后者表现不够稳定,在最差情况下会退化,使时间复杂度变为 O(N^{2})

2.5:快速与希尔

        二者都运用了分治思想,都适用于大量数据情况下的排序,二者都是边比较边交换排序,后者在每轮循环中都会在小范围内使数据有序,因此在最差情况下的表现会优于快速排序。

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

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

相关文章

电商平台数据功能封装API需要注意些什么?如何调用封装后的API?

一、引言 随着电商行业的蓬勃发展&#xff0c;电商平台的数据功能愈发复杂多样&#xff0c;如何高效、安全地管理和使用这些数据成为了电商平台开发者面临的重要问题。API&#xff08;Application Programming Interface&#xff09;作为不同软件之间进行通信的桥梁&#xff0…

预警与校准并行:可燃气体报警器在矿区井下甲烷泄露防控中的应用

在矿区井下作业中&#xff0c;甲烷泄露是一个严重威胁工人生命安全和矿区生产安全的隐患。因此&#xff0c;及时、准确地预警甲烷泄露并采取相应的处理措施显得尤为重要。 可燃气体报警器作为一种有效的监测工具&#xff0c;在预防甲烷泄露事故中发挥着不可替代的作用。 在这…

STM32HAL库 -- RS485 开发板通信(速记版)

在本章中&#xff0c; 我们将使用 STM32F429的串口 2 来实现两块开发板之间的 485 通信(半双工)。 RS485 简介 485&#xff08;一般称作 RS485/EIA-485&#xff09;隶属于 OSI 模型物理层&#xff0c;是串行通讯的一种。电气特性规定为 2 线&#xff0c;半双工&#xff0c;多…

CVPR 2024最佳论文分享:文本到图像生成的丰富人类反馈

CVPR&#xff08;Conference on Computer Vision and Pattern Recognition&#xff09;是计算机视觉领域最有影响力的会议之一&#xff0c;主要方向包括图像和视频处理、目标检测与识别、三维视觉等。近期&#xff0c;CVPR 2024公布了最佳论文。共有10篇论文获奖&#xff0c;其…

如何实现智慧农田的精准灌溉

如何实现智慧农田的精准灌溉 智慧农田的精准灌溉是现代农业技术发展的重要组成部分&#xff0c;它集成了物联网、大数据分析、人工智能以及现代水利技术&#xff0c;旨在通过实时监测土壤湿度、气象条件及作物生长状况&#xff0c;实现水资源的高效利用和作物产量、品质的双重…

解决2021版IDEA新建没有Server问题

2024-06-27可用 我是这样解决的&#xff0c;仅供参考 IDEA软件是2021.1.1版 导入Tomcat的Servlet包&#xff0c;就解决了&#xff0c;详见下图操作 1. 打开项目结构 2. 选Libraries 3. 找到Tomcat安装路径 比如我安装在了“C:\soft”目录&#xff0c;就去这个目录找 然后记得…

基于Cardinal的AWD攻防平台搭建与使用以及基于docker的题目环境部署

关于 CTF 靶场的搭建与完善勇师傅前面已经总结过了&#xff0c;参考&#xff1a; CTF靶场搭建及Web赛题制作与终端docker环境部署_ctfoj搭建-CSDN博客 基于H1ve一分钟搭好CTF靶场-CSDN博客 Nginx首页修改及使用Nginx实现端口转发_nginx 修改欢迎首页-CSDN博客 关于H1ve导…

cPanel的SSL证书续订方法

在现代互联网环境中&#xff0c;SSL证书对于保障网站的安全和数据加密至关重要。下面我们将介绍如何在cPanel中续订SSL证书&#xff0c;并适时结合Hostease服务器的优势&#xff0c;帮助您更好地理解和操作。 生成私钥和CSR&#xff08;证书签名请求&#xff09; 难易程度&am…

Unity中模拟抛物线(非Unity物理)

Unity中模拟抛物线非Unity物理 介绍剖析问题以及所需公式重力加速度公式&#xff1a;h 1/2*g*t*t(h 1/2 * g * t ^ 2)速度公式&#xff1a;Vt V初 a * t 主要代码总结 介绍 用Unity物理系统去做的抛物线想要控制速度或者想要细微的控制一些情况是非常困难的。所以想要脱离U…

国产CPU兆芯发展分析

国产信创CPU-兆芯CPU CPU&#xff1a;信创根基&#xff0c;国之重器 国产CPU已形成自主架构、x86、ARM三大阵营。自主架构以龙芯、申威的LoongArch、SW-64为代表&#xff1b;ARM阵营由鲲鹏、飞腾领军&#xff0c;依托ARM授权开发处理器&#xff1b;x86阵营则以海光、兆芯等品牌…

基于halcon的眼在手外(Eye-to-Hand)标定

前言 上个月写了一个《基于halcon的眼在手上&#xff08;Eye-in-Hand&#xff09;标定》的文章&#xff0c;通过官方的示例代码进行了简单的叙述&#xff0c;想学习的小伙伴可以点击链接进行学习。之前博主认为眼在手上&#xff08;Eye-in-Hand&#xff09;的案例更多&#xff…

leetcode209:长度最小的子数组

题目链接&#xff1a;209. 长度最小的子数组 - 力扣&#xff08;LeetCode&#xff09; class Solution { public:int minSubArrayLen(int target, vector<int>& nums) {int n nums.size();if(n 0) return 0;int end 0, start 0, sum 0, ans 0x3f3f3f3f;while…

OpenAI将终止对中国提供服务,国内模型接棒

说起来&#xff0c;OpenAI自始至终就没有对中国提供过服务&#xff0c;OpenAI官方支持的国家和地区&#xff1a;https://platform.openai.com/docs/supported-countries 列表里面没有“Chinese”的选项&#xff0c;那为什么又要明令禁止呢&#xff0c;国类IT高手们&#xff0…

day001 环境的配置与工具的安装

VMware的软件包&#xff1a;https://pan.xunlei.com/s/VNs1KShnlZalTSJtejXXzchlA1?pwdudy5# 其他的软件可以在电脑管家中下载&#xff0c;注意不要安装到c盘&#xff0c;否则影响开机速度。 虚拟机工具&#xff1a;VMware17.5.1 1&#xff0c; 2&#xff0c; 3&#xff0c…

欢乐钓鱼大师攻略:卡鱼骨、典藏鱼、藏宝图怎么钓?

《欢乐钓鱼大师》是一款以钓鱼为核心玩法的休闲模拟手游&#xff0c;通过逼真的画面和丰富的钓鱼体验吸引了大量玩家。本文将为你详细介绍这款游戏的亮点、常见问题以及一些实用的游戏技巧&#xff0c;帮助你在《欢乐钓鱼大师》中获得更愉快的游戏体验。 辅助工具 1. 辅助工具…

内网安全【5】隧道搭建

1.内网穿透工具 Ngrok Frp Spp Nps EW(停更) 一共是这五个 优点&#xff1a;穿透加密数据&#xff0c;中间平台&#xff0c;防追踪&#xff0c;解决网络问题 Sunny-Ngrok内网转发内网穿透 - 国内内网映射服务器 https://github.com/esrrhs/spp https://github.com/fatedie…

计算机网络之数据通信原理(中)

上节内容传送口&#xff1a;数据通信原理基础 1.数据传输方式 1.1并行传输 并行传输: 字符编码的各个比特同时传输 特点&#xff1a; 一个比特时间内可传输一个字符&#xff0c;传输速度快&#xff0c;每个比特传输要求一个单独的信道支持&#xff0c;通信成本高&#xf…

探索网络爬虫技术:原理、实践与挑战

一、引言 在数字化时代&#xff0c;信息如同潮水般汹涌而来。过去&#xff0c;我们可能依赖书籍、报纸或电视来获取信息&#xff0c;但这些渠道的信息量有限&#xff0c;而且筛选过的信息未必能满足我们的需求。如今&#xff0c;互联网为我们提供了海量的信息&#xff0c;但同…

【Sklearn驯化-聚类指标】搞懂机器学习中聚类算法评估指标,轮廓系数、戴维森堡丁指数

【Sklearn驯化-聚类指标】搞懂机器学习中聚类算法评估指标&#xff0c;轮廓系数、戴维森堡丁指数 本次修炼方法请往下查看 &#x1f308; 欢迎莅临我的个人主页 &#x1f448;这里是我工作、学习、实践 IT领域、真诚分享 踩坑集合&#xff0c;智慧小天地&#xff01; &#…

快来看,错过了今天就要设置为vip文章了---云原生重塑架构:AutoMQ 基于云构建十倍降本的 Kafka

Apache Kafka在数据流处理系统中的核心地位 Apache Kafka已成为大数据和流计算领域的行业事实标准&#xff0c;其重要性在数据流处理系统中不言而喻。随着数字化转型的加速&#xff0c;越来越多的企业选择基于Kafka来构建其在线业务消息传递系统&#xff0c;使其成为数字化转型…