【算法】---归并排序(递归非递归实现)
参考
左程云算法
算法导论
前言
本篇介绍
- 归并排序
- 分治法
前置知识
- 了解递归, 了解数组。
引入
归并排序
归并排序最早是由公认的现代计算机之父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 个最相似的样本中的大多数属于某一个类别,则该样本也属…...
观成科技:隐蔽隧道工具Ligolo-ng加密流量分析
1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具,该工具基于TUN接口实现其功能,利用反向TCP/TLS连接建立一条隐蔽的通信信道,支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式,适应复杂网…...
谷歌浏览器插件
项目中有时候会用到插件 sync-cookie-extension1.0.0:开发环境同步测试 cookie 至 localhost,便于本地请求服务携带 cookie 参考地址:https://juejin.cn/post/7139354571712757767 里面有源码下载下来,加在到扩展即可使用FeHelp…...
中南大学无人机智能体的全面评估!BEDI:用于评估无人机上具身智能体的综合性基准测试
作者:Mingning Guo, Mengwei Wu, Jiarun He, Shaoxian Li, Haifeng Li, Chao Tao单位:中南大学地球科学与信息物理学院论文标题:BEDI: A Comprehensive Benchmark for Evaluating Embodied Agents on UAVs论文链接:https://arxiv.…...
渗透实战PortSwigger靶场-XSS Lab 14:大多数标签和属性被阻止
<script>标签被拦截 我们需要把全部可用的 tag 和 event 进行暴力破解 XSS cheat sheet: https://portswigger.net/web-security/cross-site-scripting/cheat-sheet 通过爆破发现body可以用 再把全部 events 放进去爆破 这些 event 全部可用 <body onres…...
最新SpringBoot+SpringCloud+Nacos微服务框架分享
文章目录 前言一、服务规划二、架构核心1.cloud的pom2.gateway的异常handler3.gateway的filter4、admin的pom5、admin的登录核心 三、code-helper分享总结 前言 最近有个活蛮赶的,根据Excel列的需求预估的工时直接打骨折,不要问我为什么,主要…...
智能在线客服平台:数字化时代企业连接用户的 AI 中枢
随着互联网技术的飞速发展,消费者期望能够随时随地与企业进行交流。在线客服平台作为连接企业与客户的重要桥梁,不仅优化了客户体验,还提升了企业的服务效率和市场竞争力。本文将探讨在线客服平台的重要性、技术进展、实际应用,并…...
Linux-07 ubuntu 的 chrome 启动不了
文章目录 问题原因解决步骤一、卸载旧版chrome二、重新安装chorme三、启动不了,报错如下四、启动不了,解决如下 总结 问题原因 在应用中可以看到chrome,但是打不开(说明:原来的ubuntu系统出问题了,这个是备用的硬盘&a…...
根据万维钢·精英日课6的内容,使用AI(2025)可以参考以下方法:
根据万维钢精英日课6的内容,使用AI(2025)可以参考以下方法: 四个洞见 模型已经比人聪明:以ChatGPT o3为代表的AI非常强大,能运用高级理论解释道理、引用最新学术论文,生成对顶尖科学家都有用的…...
RNN避坑指南:从数学推导到LSTM/GRU工业级部署实战流程
本文较长,建议点赞收藏,以免遗失。更多AI大模型应用开发学习视频及资料,尽在聚客AI学院。 本文全面剖析RNN核心原理,深入讲解梯度消失/爆炸问题,并通过LSTM/GRU结构实现解决方案,提供时间序列预测和文本生成…...
GC1808高性能24位立体声音频ADC芯片解析
1. 芯片概述 GC1808是一款24位立体声音频模数转换器(ADC),支持8kHz~96kHz采样率,集成Δ-Σ调制器、数字抗混叠滤波器和高通滤波器,适用于高保真音频采集场景。 2. 核心特性 高精度:24位分辨率,…...
