当前位置: 首页 > news >正文

【算法】---归并排序(递归非递归实现)

参考

左程云算法
算法导论

前言

本篇介绍

  • 归并排序
  • 分治法

前置知识

  • 了解递归, 了解数组。

引入

归并排序
归并排序最早是由公认的现代计算机之父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章)。
下次见!

相关文章:

【算法】---归并排序(递归非递归实现)

参考 左程云算法 算法导论 前言 本篇介绍 归并排序分治法 前置知识 了解递归&#xff0c; 了解数组。 引入 归并排序 归并排序最早是由公认的现代计算机之父John von Neumann发明的&#xff0c; 这是一种典型的分治思想应用。 我们先介绍分治思想 分治思想 分治思想的…...

UniVue大版本更新:UniVue2.0.0-preview

大版本发布说明 距离上次更新好像已经过去很久了&#xff0c;最近太忙了没时间维护新版本&#xff0c;也是自己在使用的过程中发现了很多问题也有了更多的灵感&#xff0c;由于和之前的版本区别太大&#xff0c;决定重新开一个大版本。这个UniVue2之后的版本追求是性能&#xf…...

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中&#xff0c;控制语句用于管理程序的执行流程。常见有 break、continue 和 goto。 1. break break语句主要用于在循环或者s…...

决策树中联合概率分布公式解释说明

学习决策树时书本中有一公式 7-3 是&#xff1a; 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实战项目 附源码+文档+视频讲解

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…...

php email功能实现:详细步骤与配置技巧?

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

MapBox Android版开发 6 关于Logo

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

2024年房市

24年8月15日&#xff0c;国家统计局公布&#xff0c;“7月末&#xff0c;商品房待售面积73926万平方米”。(原文链接&#xff1a;https://www.stats.gov.cn/sj/zxfb/202408/t20240815_1955982.html)   7.39亿平方存量商品房&#xff0c;估价均价1万每平&#xff0c;总价约&am…...

index索引

index索引&#xff1a; create index 【1】on 【2】(【3】) 1为索引名&#xff0c;通常为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. 互联网服务提供商&#xff08;ISP&#xff09; 互联网服务提供商&#xff08;ISP&#xff09;是指提供互联网接入服务的公司或组织。它们负责将用户连接到互联网&#xff0c;并提供相关的服务&#xff0c;如电子邮件、网站托管和其他在线服务。ISP可以分为不同的层级&#…...

基于元神操作系统实现NTFS文件操作(三)

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

深度学习与数学归纳法

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

《Linux从小白到高手》理论篇(六):Linux软件安装一篇通

List item 本篇介绍Linux软件安装相关的操作命令&#xff0c;看完本文&#xff0c;有关Linux软件安装相关操作的常用命令你就掌握了99%了。 Linux软件安装 RPM RPM软件的安装、删除、更新只有root权限才能使用&#xff1b;查询功能任何用户都可以操作&#xff1b;如果普通用…...

【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 &#xff08;接上一章&#xff09; 自由协议通信步骤 &#xff08;以MS-A2-1041为例&#xff09; 接收与…...

matlab-对比两张图片的HSV分量的差值并形成直方图

%对比两张图片的HSV分量的差值并形成直方图&#xff0c;改个路径就能用&#xff0c;图片分辨率要一致 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解析部署使用全流程

官网地址&#xff1a; Spring Cloud Gateway 目录 1、SpringGateway简介 1、什么是网关 2、为什么用网关【为了转发】 2、应用&#xff1a; 1.启动nacos 2.创建网关项目 3.网关配置1 4.网关配置2【了解】 5.过滤器配置【了解】 1、SpringGateway简介 核心功能有三个&…...

Solidity 存储和内存管理:深入理解与高效优化

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

机器学习篇-day02-KNN算法实现鸢尾花模型和手写数字识别模型

一. KNN简介 KNN思想 K-近邻算法&#xff08;K Nearest Neighbor&#xff0c;简称KNN&#xff09;。比如&#xff1a;根据你的“邻居”来推断出你的类别 KNN算法思想&#xff1a;如果一个样本在特征空间中的k 个最相似的样本中的大多数属于某一个类别&#xff0c;则该样本也属…...

iOS 26 携众系统重磅更新,但“苹果智能”仍与国行无缘

美国西海岸的夏天&#xff0c;再次被苹果点燃。一年一度的全球开发者大会 WWDC25 如期而至&#xff0c;这不仅是开发者的盛宴&#xff0c;更是全球数亿苹果用户翘首以盼的科技春晚。今年&#xff0c;苹果依旧为我们带来了全家桶式的系统更新&#xff0c;包括 iOS 26、iPadOS 26…...

docker详细操作--未完待续

docker介绍 docker官网: Docker&#xff1a;加速容器应用程序开发 harbor官网&#xff1a;Harbor - Harbor 中文 使用docker加速器: Docker镜像极速下载服务 - 毫秒镜像 是什么 Docker 是一种开源的容器化平台&#xff0c;用于将应用程序及其依赖项&#xff08;如库、运行时环…...

Leetcode 3577. Count the Number of Computer Unlocking Permutations

Leetcode 3577. Count the Number of Computer Unlocking Permutations 1. 解题思路2. 代码实现 题目链接&#xff1a;3577. Count the Number of Computer Unlocking Permutations 1. 解题思路 这一题其实就是一个脑筋急转弯&#xff0c;要想要能够将所有的电脑解锁&#x…...

React Native在HarmonyOS 5.0阅读类应用开发中的实践

一、技术选型背景 随着HarmonyOS 5.0对Web兼容层的增强&#xff0c;React Native作为跨平台框架可通过重新编译ArkTS组件实现85%以上的代码复用率。阅读类应用具有UI复杂度低、数据流清晰的特点。 二、核心实现方案 1. 环境配置 &#xff08;1&#xff09;使用React Native…...

SpringBoot+uniapp 的 Champion 俱乐部微信小程序设计与实现,论文初版实现

摘要 本论文旨在设计并实现基于 SpringBoot 和 uniapp 的 Champion 俱乐部微信小程序&#xff0c;以满足俱乐部线上活动推广、会员管理、社交互动等需求。通过 SpringBoot 搭建后端服务&#xff0c;提供稳定高效的数据处理与业务逻辑支持&#xff1b;利用 uniapp 实现跨平台前…...

零基础设计模式——行为型模式 - 责任链模式

第四部分&#xff1a;行为型模式 - 责任链模式 (Chain of Responsibility Pattern) 欢迎来到行为型模式的学习&#xff01;行为型模式关注对象之间的职责分配、算法封装和对象间的交互。我们将学习的第一个行为型模式是责任链模式。 核心思想&#xff1a;使多个对象都有机会处…...

聊一聊接口测试的意义有哪些?

目录 一、隔离性 & 早期测试 二、保障系统集成质量 三、验证业务逻辑的核心层 四、提升测试效率与覆盖度 五、系统稳定性的守护者 六、驱动团队协作与契约管理 七、性能与扩展性的前置评估 八、持续交付的核心支撑 接口测试的意义可以从四个维度展开&#xff0c;首…...

GC1808高性能24位立体声音频ADC芯片解析

1. 芯片概述 GC1808是一款24位立体声音频模数转换器&#xff08;ADC&#xff09;&#xff0c;支持8kHz~96kHz采样率&#xff0c;集成Δ-Σ调制器、数字抗混叠滤波器和高通滤波器&#xff0c;适用于高保真音频采集场景。 2. 核心特性 高精度&#xff1a;24位分辨率&#xff0c…...

人工智能(大型语言模型 LLMs)对不同学科的影响以及由此产生的新学习方式

今天是关于AI如何在教学中增强学生的学习体验&#xff0c;我把重要信息标红了。人文学科的价值被低估了 ⬇️ 转型与必要性 人工智能正在深刻地改变教育&#xff0c;这并非炒作&#xff0c;而是已经发生的巨大变革。教育机构和教育者不能忽视它&#xff0c;试图简单地禁止学生使…...

uniapp手机号一键登录保姆级教程(包含前端和后端)

目录 前置条件创建uniapp项目并关联uniClound云空间开启一键登录模块并开通一键登录服务编写云函数并上传部署获取手机号流程(第一种) 前端直接调用云函数获取手机号&#xff08;第三种&#xff09;后台调用云函数获取手机号 错误码常见问题 前置条件 手机安装有sim卡手机开启…...