【算法】---归并排序(递归非递归实现)
参考
左程云算法
算法导论
前言
本篇介绍
- 归并排序
- 分治法
前置知识
- 了解递归, 了解数组。
引入
归并排序
归并排序最早是由公认的现代计算机之父John von Neumann发明的, 这是一种典型的分治思想
应用。
我们先介绍分治思想
分治思想
分治思想的想法基于递归, 许多算法可以通过自身调用, 来降低问题的规模, 又获得与原问题类似的子问题。可见, 分治法本质是递归思想的一个分支,分治法还要额外进行处理, 先进行递归地求解子问题, 然后合并这些子问题的解求出原问题的解。
通过一个简单的例子, 来讲解分治思想。
给定一个int类型的数组, 求解该数组的最大值。
你可能已经很熟悉了, 线性遍历即可
。
public static int getMaxValue1(int[] arr) {int n = arr.length;//max,初始默认为系统最小值。int max = Integer.MIN_VALUE;//无序数组, 直接遍历for(int i=0;i<n;i++) {max = Math.max(max, arr[i]);}return max;}
分治思想如何运用呢? 原数组区间范围在[0, arr.length - 1]
,求解原数组的大小可以被递归解决吗?
很有可能的想法, 直观上, 我们求解原数组的最大值就是求解在原数组序列中最大值。只需要等分序列即可, 将原数组序列的最大值,分解成左右子序列的最大值问题, 从递归上,对子序列可以同样这样处理,直到不需要递归继续降解规模了(递归模式结束, 处理基线条件,以防止死递归了)。
问题在于, 规模确实减小了,但左序列的最大值不一定是整个数组的最大值。 直觉上, 将左右序列的最大值进行比较,决定合并两个序列的最大值。
为此, 我们可以写出分治法的求解子问题的写法。
public static int getMaxValue(int[] arr) {if(arr==null||arr.length==0) {return Integer.MIN_VALUE;//处理值为null,传参为空数组的情况。}return process(arr, 0, arr.length-1);}public static int process(int[] arr, int l, int r) {if(l>r) {return Integer.MIN_VALUE;}if(l==r) {return arr[l];//直接返回值}//递归条件, 降低规模//分治的过程int mid = (l+r)/2;int lmax = process(arr, l, mid);int rmax = process(arr, mid+1, r);//合并的过程。return Math.max(lmax, rmax);}public static void main(String[] args) {int[] arr = {3,5,1,7,8,9,11,2};//对比两种方法, 观察结果是否一致。 相互验证!System.out.println("最大值:"+getMaxValue1(arr));System.out.println("最大值:"+ getMaxValue(arr));}
/*** output:* 最大值:11* 最大值:11*/
总结
对于每层递归:
分治法有三个步骤
- 分解原问题为若干子问题(不一定像上述例子二等分), 子问题是规模减小的原问题。
- 递归地处理这些子问题, 核心是什么时候继续递归分解, 什么时候直接求解。—写递归时必须想明白, 否则StackOverflow等着你。
- 合并处理子问题的解进而求出原问题的解。
能不能降低规模, 处理好递归,以及合并这个操作具体怎么写。写好分治法的难点。
归并排序
归并排序也是一种分治思想的体现。
基本思想就是,左边有序,右边有序。然后调整为整体有序。
- 分割: 将数组序列二等分, 利用递归不断分解。
- 合并: 合并两个有序序列,保持原来的元素的相对顺序不变。
结合代码和下面图片
public static void mergeSort(int[] arr) {//无效值null, 数组元素不为2,不需要排序。if(arr==null || arr.length<2) {return ;}//开始process(arr,0, arr.length-1);}public static void process(int[] arr, int l, int r) {//基线条件处理if(l>=r) {return ;}//选中分割下标, 直接取中值即可int mid = (l+r)/2;//左边递归调用process(arr,l, mid);//右边递归调用, 注意参数process(arr,mid+1,r);//合并操作merge(arr,l,mid,r);}public static void merge(int[] arr,int l,int m, int r) {int i = 0;int a = l;int b = m+1;//拷贝一个临时数组int[] help = new int[r-l+1];//比大小的过程while(a<=m && b<=r ) {help[i++]=arr[a]<=arr[b]?arr[a++]:arr[b++];}//处理剩余的序列while(a<=m) {help[i++] = arr[a++];}while(b<=r) {help[i++] = arr[b++];}//将数据拷贝回原序列。for(i=l;i<=r;i++) {arr[i] = help[i-l];}}//测试用例。public static void main(String[] args) {int[] arr= {4,1,6,23,8,9,11,0,2,3,4,4,4,4,10};System.out.println("排序前:"+Arrays.toString(arr));mergeSort(arr);System.out.println("排序后:"+Arrays.toString(arr));}/*** output:* 排序前:[4, 1, 6, 23, 8, 9, 11, 0, 2, 3, 4, 4, 4, 4, 10]* 排序后:[0, 1, 2, 3, 4, 4, 4, 4, 4, 6, 8, 9, 10, 11, 23]*/
开始有一个主方法,mergeSort
,只需要传递一个int数组即可。
process
这一函数是不断递归地分解问题。merge
函数是服务当前的process
函数。
比如,[4,8]
,[5,7]
给定两个子序列, 通过分离双指针和临时数组help
进行比较,原理是拷贝完较小的数组,然后拷贝完剩余的数组。
先将两个序列的比较结果拷贝进help数组,help=[4,5,7]
,此时还剩下一个元素8
,因为没有数可比了,序列[5,7]
的指针已经走到了尽头。只需要挨个检查将剩下的元素依次拷贝进help
数组即可。
以上就是对这行代码的解释:
//比大小的过程while(a<=m && b<=r ) {help[i++]=arr[a]<=arr[b]?arr[a++]:arr[b++];}//处理剩余的序列while(a<=m) {help[i++] = arr[a++];}while(b<=r) {help[i++] = arr[b++];}
最后, 将临时数组help
存储的有序序列依次拷贝回原数组的对应序列即可。注意这里是原数组进行修改。
//将数据拷贝回原序列。for(i=l;i<=r;i++) {arr[i] = help[i-l];}
递归版的复杂度
时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn), 系统压栈高度为logn
,merge函数时间 O ( n ) O(n) O(n), 乘起来就是 O ( n l o g n ) O(nlogn) O(nlogn)。
空间复杂度: O ( n ) O(n) O(n), 借助了一个临时数组help
.
非递归实现
public static void mergeSort(int[] arr) {int n = arr.length;//step分组数, step为1,说明左右区间各有一个数(除非区间已经越界, 则相应调整)//先两两分组, 再以4个为一组, 8个为一组...直到单次分组已经超过数组总的元素个数就终止。for (int l, m, r, step = 1; step < n; step <<= 1) {l = 0;//后面就是讨论区间while (l < n) {//确定区间的中间下标m = l + step - 1;//判断右边界是否存在if (m + 1 >= n) {//无右侧, 不用后续merge了。break;//不存在说明单层的归并排序结束。}//求右边界r = Math.min(l + (step << 1) - 1, n - 1);//确定好了,l,m,r的值,进行合并merge(arr, l, m, r);//内层while循环进行调整l = r + 1;}}}public static void merge(int[] arr,int l, int m,int r) {int a = l;int b = m+1;int[] help = new int[r-l+1];int i = 0;while(a<=m && b<=r) {help[i++] = arr[a]<=arr[b]?arr[a++]:arr[b++];}while(a<=m) {help[i++] = arr[a++];}while(b<=r) {help[i++] = arr[b++];}for(i=l;i<=r;i++) {arr[i] = help[i-l];}}//测试用例。public static void main(String[] args) {int[] arr= {4,1,6,23,8,9,11,0,2,3,4,4,4,4,10};System.out.println("排序前:"+Arrays.toString(arr));mergeSort(arr);System.out.println("排序后:"+Arrays.toString(arr));}/*** output:* 排序前:[4, 1, 6, 23, 8, 9, 11, 0, 2, 3, 4, 4, 4, 4, 10]* 排序后:[0, 1, 2, 3, 4, 4, 4, 4, 4, 6, 8, 9, 10, 11, 23]*/
归并排序为什么如此高效, 左神说过是因为比较排序中的比较次数没有浪费。 确实如此, 比较排序可以抽象为决策树模型, 比较次数最少就是 n l o g n nlogn nlogn, 而对于三大平方的‘傻瓜式’排序算法, 因为浪费了比较次数,导致时间复杂度变高了。
练习
//请用递归和非递归方法实现。//阐述一下归并排序的思想。public static void mergeSort(int[] nums) {/*write code here! */}
你已经学会了归并排序了, 快速试试吧!。
总结
本篇并不涉及算法的严格分析, 因为算法导论一书中已经写好了严谨有力的证明(算法导论第二章和第4章)。
下次见!
相关文章:

【算法】---归并排序(递归非递归实现)
参考 左程云算法 算法导论 前言 本篇介绍 归并排序分治法 前置知识 了解递归, 了解数组。 引入 归并排序 归并排序最早是由公认的现代计算机之父John von Neumann发明的, 这是一种典型的分治思想应用。 我们先介绍分治思想 分治思想 分治思想的…...
UniVue大版本更新:UniVue2.0.0-preview
大版本发布说明 距离上次更新好像已经过去很久了,最近太忙了没时间维护新版本,也是自己在使用的过程中发现了很多问题也有了更多的灵感,由于和之前的版本区别太大,决定重新开一个大版本。这个UniVue2之后的版本追求是性能…...

RabbbitMQ篇(环境搭建 - 下载 安装)(持续更新迭代)
目录 一、Windows 1. 下载安装程序 2. 安装配置erlang 3. 安装rabbitMQ 4. 验证 二、Linux 1. 下载rpm包 1.1. 下载Erlang的rpm包 1.2. 下载socat的rpm包 1.3. 下载RabbitMQ的rpm包 2. 安装 2.1. 安装Erlang 2.2. 安装socat 2.3. 安装RabbitMQ 3. 启动RabbitMQ服…...

C++基础补充(02)C++其他控制语句break continue goto等
文章目录 1. break2. continue 语句3. goto 语句goto的存在 4. 跳出多重循环4.1 goto 直接跳转4.2 C11及其后版本的 return 语句4.3 使用标志变量 在C中,控制语句用于管理程序的执行流程。常见有 break、continue 和 goto。 1. break break语句主要用于在循环或者s…...
决策树中联合概率分布公式解释说明
学习决策树时书本中有一公式 7-3 是: P ( X x i , Y y j ) p i j ( i 1 , 2 , … , m , j 1 , 2 , … , n ) P(X x_i, Y y_j) p_{ij} \quad (i 1, 2, \dots, m, \ j 1, 2, \dots, n) P(Xxi,Yyj)pij(i1,2,…,m, j1,2,…,n) 这个公式表示的是随机变…...

计算机毕业设计 农场投入品运营管理系统的设计与实现 Java实战项目 附源码+文档+视频讲解
博主介绍:✌从事软件开发10年之余,专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ 🍅文末获取源码联系🍅 👇🏻 精…...

php email功能实现:详细步骤与配置技巧?
php email发送功能详细教程?如何使用php email服务? 无论是用户注册、密码重置,还是订单确认,电子邮件都是与用户沟通的重要手段。AokSend将详细介绍如何实现php email功能,并提供一些配置技巧,帮助你更好…...

MapBox Android版开发 6 关于Logo
MapBox Android版开发 6 关于Logo Logo的显示查看源码及思路(Logo)第一步第二步 隐藏Logo示例查看源码及思路(Info)第一步第二步 隐藏Logo和Info示例 看到有网友留言问如何移除Logo,今天看了下V9源码,发现M…...

2024年房市
24年8月15日,国家统计局公布,“7月末,商品房待售面积73926万平方米”。(原文链接:https://www.stats.gov.cn/sj/zxfb/202408/t20240815_1955982.html) 7.39亿平方存量商品房,估价均价1万每平,总价约&am…...
index索引
index索引: create index 【1】on 【2】(【3】) 1为索引名,通常为id_表名_列名。2为表名。3为列名。 CREATE INDEX id_account_id ON account(id); -- 根据id创建索引 CREATE INDEX id_account_idname on account(id,name); -- 创建组合索引 索…...

理解互联网链路:从本地ISP到Tier 1 ISP运营商
1. 互联网服务提供商(ISP) 互联网服务提供商(ISP)是指提供互联网接入服务的公司或组织。它们负责将用户连接到互联网,并提供相关的服务,如电子邮件、网站托管和其他在线服务。ISP可以分为不同的层级&#…...

基于元神操作系统实现NTFS文件操作(三)
1. 背景 本文主要介绍DBR的读取和解析,并提供了基于元神操作系统的实现代码。由于解析DBR的目的是定位到NTFS磁盘分区的元文件$Root进行文件操作,所以只解析了少量的部分,其它部分可以参考相关文档进行理解。 DBR存在于磁盘分区的第一个扇区…...

深度学习与数学归纳法
最近发现,深度学习可以分为两个主要的阶段,分别是前向推理以及反向传播,分别对应着网络的推理和参数训练两个步骤。其中推理有时候也称为归纳推理。 在做参数训练的时候,本质上是在利用历史数据求网络参数的先验分布; …...

《Linux从小白到高手》理论篇(六):Linux软件安装一篇通
List item 本篇介绍Linux软件安装相关的操作命令,看完本文,有关Linux软件安装相关操作的常用命令你就掌握了99%了。 Linux软件安装 RPM RPM软件的安装、删除、更新只有root权限才能使用;查询功能任何用户都可以操作;如果普通用…...

【Spring】运行Spring Boot项目,请求响应流程分析以及404和500报错
1. 运行项目 import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Appl…...

②EtherCAT转Modbus485RTU网关多路同步高速采集无需编程串口服务器
EtherCAT转Modbus485RTU网关多路同步高速采集无需编程串口服务器https://item.taobao.com/item.htm?ftt&id798036415719 EtherCAT 串口网关 EtherCAT 转 RS485 (接上一章) 自由协议通信步骤 (以MS-A2-1041为例) 接收与…...
matlab-对比两张图片的HSV分量的差值并形成直方图
%对比两张图片的HSV分量的差值并形成直方图,改个路径就能用,图片分辨率要一致 close all; clear all; clc; I1imread(E:\test\resources\image\1.jpg); I2imread(E:\test\resources\image\2.jpg); HSV1 rgb2ntsc(I1); HSV2 rgb2ntsc(I2); %HSV,HSV 代…...

微服务SpringGateway解析部署使用全流程
官网地址: Spring Cloud Gateway 目录 1、SpringGateway简介 1、什么是网关 2、为什么用网关【为了转发】 2、应用: 1.启动nacos 2.创建网关项目 3.网关配置1 4.网关配置2【了解】 5.过滤器配置【了解】 1、SpringGateway简介 核心功能有三个&…...

Solidity 存储和内存管理:深入理解与高效优化
在 Solidity 中,存储和内存管理是编写高效智能合约的关键组成部分。合约执行的每一步操作都可能涉及到数据的存储和读取,而这些操作对 gas 的消耗有很大影响。因此,理解 Solidity 的存储模型以及如何优化数据的管理对于合约的安全性、性能和成…...

机器学习篇-day02-KNN算法实现鸢尾花模型和手写数字识别模型
一. KNN简介 KNN思想 K-近邻算法(K Nearest Neighbor,简称KNN)。比如:根据你的“邻居”来推断出你的类别 KNN算法思想:如果一个样本在特征空间中的k 个最相似的样本中的大多数属于某一个类别,则该样本也属…...
MySQL 隔离级别:脏读、幻读及不可重复读的原理与示例
一、MySQL 隔离级别 MySQL 提供了四种隔离级别,用于控制事务之间的并发访问以及数据的可见性,不同隔离级别对脏读、幻读、不可重复读这几种并发数据问题有着不同的处理方式,具体如下: 隔离级别脏读不可重复读幻读性能特点及锁机制读未提交(READ UNCOMMITTED)允许出现允许…...

.Net框架,除了EF还有很多很多......
文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...

CentOS下的分布式内存计算Spark环境部署
一、Spark 核心架构与应用场景 1.1 分布式计算引擎的核心优势 Spark 是基于内存的分布式计算框架,相比 MapReduce 具有以下核心优势: 内存计算:数据可常驻内存,迭代计算性能提升 10-100 倍(文档段落:3-79…...
macOS多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用
文章目录 问题现象问题原因解决办法 问题现象 macOS启动台(Launchpad)多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用。 问题原因 很明显,都是Google家的办公全家桶。这些应用并不是通过独立安装的…...

【JavaWeb】Docker项目部署
引言 之前学习了Linux操作系统的常见命令,在Linux上安装软件,以及如何在Linux上部署一个单体项目,大多数同学都会有相同的感受,那就是麻烦。 核心体现在三点: 命令太多了,记不住 软件安装包名字复杂&…...
Spring AI与Spring Modulith核心技术解析
Spring AI核心架构解析 Spring AI(https://spring.io/projects/spring-ai)作为Spring生态中的AI集成框架,其核心设计理念是通过模块化架构降低AI应用的开发复杂度。与Python生态中的LangChain/LlamaIndex等工具类似,但特别为多语…...

USB Over IP专用硬件的5个特点
USB over IP技术通过将USB协议数据封装在标准TCP/IP网络数据包中,从根本上改变了USB连接。这允许客户端通过局域网或广域网远程访问和控制物理连接到服务器的USB设备(如专用硬件设备),从而消除了直接物理连接的需要。USB over IP的…...
Android第十三次面试总结(四大 组件基础)
Activity生命周期和四大启动模式详解 一、Activity 生命周期 Activity 的生命周期由一系列回调方法组成,用于管理其创建、可见性、焦点和销毁过程。以下是核心方法及其调用时机: onCreate() 调用时机:Activity 首次创建时调用。…...

华为OD机试-最短木板长度-二分法(A卷,100分)
此题是一个最大化最小值的典型例题, 因为搜索范围是有界的,上界最大木板长度补充的全部木料长度,下界最小木板长度; 即left0,right10^6; 我们可以设置一个候选值x(mid),将木板的长度全部都补充到x,如果成功…...

Qt的学习(一)
1.什么是Qt Qt特指用来进行桌面应用开发(电脑上写的程序)涉及到的一套技术Qt无法开发网页前端,也不能开发移动应用。 客户端开发的重要任务:编写和用户交互的界面。一般来说和用户交互的界面,有两种典型风格&…...