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

news2024/11/19 8:51:39

零、说在前面

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

一、理论部分

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.coloradmin.cn/o/1867326.html

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

相关文章

文生视频模型Sora刷屏的背后的数据支持

前言&#xff1a;近日&#xff0c;OpenAI的首个文生视频模型Sora横空出世&#xff0c;引发了一波Sora热潮。与其相关的概念股连续多日涨停&#xff0c;多家媒体持续跟踪报道&#xff0c;央视也针对Sora进行了报道&#xff0c;称这是第一个真正意义上的视频生成大模型。 01 …

VisualRules组件功能介绍-计算表格(一)

一、本章内容 2、计算表格是什么 3、计算表格的比较优势 4、计算表格基本功能展示 5、计算表格基本操作 6、特别说明 二、计算表格是什么 计算表格作为VisualRules规则引擎的核心组件&#xff0c;提供了一种在内存中高效处理数据的方法。通过将外部数据导入计算表格&#x…

C++入门 list的模拟实现

目录 list的节点类 list的迭代器类 list的模拟实现 要模拟实现list&#xff0c;必须要熟悉list的底层结构以及其接口的含义&#xff0c;通过之前学习&#xff0c;这些内容已基本掌握&#xff0c;现在我们来模拟实现list。 参照带头双向循环链表的结构&#xff0c;我们可以建…

DVWA 靶场 File Upload 通关解析

前言 DVWA代表Damn Vulnerable Web Application&#xff0c;是一个用于学习和练习Web应用程序漏洞的开源漏洞应用程序。它被设计成一个易于安装和配置的漏洞应用程序&#xff0c;旨在帮助安全专业人员和爱好者了解和熟悉不同类型的Web应用程序漏洞。 DVWA提供了一系列的漏洞场…

1.x86游戏实战-认识CE

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 本次游戏没法给 内容参考于&#xff1a;微尘网络安全 链接&#xff1a;https://pan.baidu.com/s/1rEEJnt85npn7N38Ai0_F2Q?pwd6tw3 提取码&#xff1a;6tw3 复…

ARCGIS添加在线地图

地图服务地址&#xff1a;http://map.geoq.cn/ArcGIS/rest/services 具体方法&#xff1a; 结果展示&#xff1a;

vue2 + dataV 组件问题

在使用 dataV 过程中&#xff0c;遇见 svg 动画不加载问题。 一、理想状态下&#xff1a; 二、开发中遇到的 加载不出来问题。 解决方案 在查找官方资料中&#xff0c;提到使用 key 可以解决方案。 1 绑定 key 2 改变 key 值 注意&#xff1a;一定要在 $nextTick 里面执…

PLC梯形图(置位与复位)的使用方法

置位指令相当于我们把照明灯的开关按到开的状态&#xff0c;即便我们把手离开&#xff0c;开关也是通的&#xff0c;灯也是亮的。 想要关闭必须要把它按到关的状态&#xff0c;即使用复位指令。 复位指令相当于我们把照明灯的开关按到关的状态&#xff0c;把手离开&#xff0…

49-3 内网渗透 - MSI安 装策略提权

靶场环境搭建: 这里还是用我们之前的windows2012虚拟机进行搭建 1)打开一些设置让靶场存在漏洞 打开组策略编辑器(gpedit.msc) 使用运行命令打开: 按下 Win + R 组合键来打开运行对话框。输入 gpedit.msc,然后按下 Enter 键。使用搜索打开: 点击任务栏上的搜索框(W…

Redis数据库(六):主从复制和缓存穿透及雪崩

目录 一、Redis主从复制 1.1 概念 1.2 主从复制的作用 1.3 实现一主二从 1.4 哨兵模式 1.4.1 哨兵的作用 1.4.2 哨兵模式的优缺点 二、Redis缓存穿透和雪崩 2.1 缓存穿透——查不到 2.1.1 缓存穿透解决办法 2.2 缓存击穿 - 量太大&#xff0c;缓存过期 2.2.1 缓存…

排序算法之java语言实现

零、说在前面 近期打算复习java的几种排序算法&#xff0c;我会将这些排序算法的实现代码、个人心得、时间复杂度分析&#xff0c;算法间的对比做成一个系列帖子&#xff0c;这里作为那些帖子的汇总而存在。 这个系列的框架会包含&#xff1a;概念、实现、时间空间复杂度…

鸿萌数据迁移业务案例:为医药客户成功迁移重要科研数据

天津鸿萌科贸发展有限公司对 Windows 及 Linux 系统下的各类型备份及数据迁移业务积累了丰富的业务经验&#xff0c;可提供针对性的解决方案。 医药科研数据迁移成功案例 2024年6月初&#xff0c;天津某医药厂家埃尔法 workstation2020 服务器硬盘老化&#xff0c;为保证服务…

记录一次即将出现的钓鱼新方式

钓鱼通常是内网渗透过程中的最为常见的入口方式&#xff0c;但是随着蓝队人员溯源反制思路开阔&#xff0c;入侵排查能力提升&#xff0c;钓鱼也越来越困难&#xff0c;这里就记一种不同寻常的钓鱼方式。 pip install 的执行流程&#xff1a; 先获取到远端的服务器地址 url 比…

嵌入式Linux系统编程 — 3.7 文件目录与处理

目录 1 文件目录 1.1 文件目录简介 1.2 目录存储形式 2 创建和删除目录 2.1 mkdir创建目录 2.2 rmdir删除空目录 3 opendir打开、 readdir读取以及closedir关闭目录 3.1 打开文件 opendir 3.2 读取目录 readdir 3.3 重置目录起点rewinddir 3.4 关闭目录 closedir 3…

《数字图像处理》实验报告五

一、实验任务与要求 实现一个自适应局部降噪滤波器&#xff1b;在一幅测试版图像中加入运动模糊和高斯噪声&#xff0c;产生一幅退化图像&#xff0c;采用 deconvwnr 函数实现逆滤波及维纳滤波。 二、实验报告 &#xff08;一&#xff09;实现一个自适应局部降噪滤波器 1、自…

入门JavaWeb之 JSP 语法、指令、内置对象和 JSTL 标签

导入 jar 包 搜索 jstl-api、standard pom.xml 导入 jar 包 <!-- Servlet 依赖 --><dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>4.0.1</version><scope>provid…

VMamba: Visual State Space Model论文笔记

文章目录 VMamba: Visual State Space Model摘要引言相关工作Preliminaries方法网络结构2D-Selective-Scan for Vision Data(SS2D) VMamba: Visual State Space Model 论文地址: https://arxiv.org/abs/2401.10166 代码地址: https://github.com/MzeroMiko/VMamba 摘要 卷积神…

使用matlab的大坑,复数向量转置!!!!!变量区“转置变量“功能(共轭转置)、矩阵转置(默认也是共轭转置)、点转置

近期用verilog去做FFT相关的项目&#xff0c;需要用到matlab进行仿真然后和verilog出来的结果来做对比&#xff0c;然后计算误差。近期使用matlab犯了一个错误&#xff0c;极大的拖慢了项目进展&#xff0c;给我人都整emo了&#xff0c;因为怎么做仿真结果都不对&#xff0c;还…

matlab量子纠缠态以及量子门操作下的量子态

前言 今天我们来聊聊题外话&#xff0c;量子纠缠&#xff0c;在目前物理分支中&#xff0c;要说最深&#xff0c;最能改变人类对宇宙影响的莫过于量子力学了&#xff0c;假如我们可以人为的对两个粒子施加纠缠态&#xff0c;那么我们将可以足不出户的完成对外界的操控 简介 …

计算机网络:408考研|湖科大教书匠|原理参考模型II|学习笔记

系列目录 计算机网络总纲领 计算机网络特殊考点 计算机网络原理参考模型I 计算机网络原理参考模型II 目录 系列目录更新日志前言应用层(Application Layer)一、应用层概述二、客户/服务器方式和对等方式三、动态主机配置协议(DHCP, Dynamic Host Configuration Protocol)四、域…