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

常见排序算法总结 (三) - 归并排序与归并分治

归并排序

算法思想

将数组元素不断地拆分,直到每一组中只包含一个元素,单个元素天然有序。之后用归并的方式收集跨组的元素,最终形成整个区间上有序的序列。

稳定性分析

归并排序是稳定的,拆分数组时会自然地将元素分成有先后顺序的子数组,在归并的过程中如果遇到相等的值,优先收集早出现的子数组中的那个即可。

具体实现

递归

// 注意预先定义辅助数组,防止递归层数深的情况下花大量时间空间去开数组
public static int MAX_N = 50010;
public static int[] temp = new int[MAX_N];// 归并,用于将已经有序的两个数组合并成更大的有序数组
private void merge(int[] arr, int left, int mid,  int right) {int index1 = left, index2 = mid + 1, index = left;// 两个数组都有剩余元素,每次收集值较小的元素while(index1 <= mid && index2 <= right) {temp[index++] = arr[index1] <= arr[index2] ? arr[index1++] : arr[index2++];}// 其中一个数组为空,将另一个数组剩余的所有元素都添加到结果数组的末尾while(index1 <= mid) {temp[index++] = arr[index1++];}while(index2 <= right) {temp[index++] = arr[index2++];}// 结果回写到原数组System.arraycopy(temp, left, arr, left, right - left + 1);
}// 归并排序
private void mergeSort(int[] arr, int left, int right) {// 左右端点相同,数组中只有单个元素认为天然有序if (left == right) {return;}int mid = left + ((right - left) >>> 1); // 计算区间中点作为划分数组的依据// 递归地对左半边数组进行排序mergeSort(arr, left, mid);// 递归地对右半边数组进行排序mergeSort(arr, mid + 1, right);// 归并收集结果merge(arr, left, mid, right);
}

非递归

// 注意预先定义辅助数组,防止递归层数深的情况下花大量时间空间去开数组
public static int MAX_N = 50010;
public static int[] temp = new int[MAX_N];// 归并方法,用于将已经有序的两个数组合并成更大的有序数组
private void merge(int[] arr, int left, int mid,  int right) {int index1 = left, index2 = mid + 1, index = left;// 两个数组都有剩余元素,每次收集值较小的元素while(index1 <= mid && index2 <= right) {temp[index++] = arr[index1] <= arr[index2] ? arr[index1++] : arr[index2++];}// 其中一个数组为空,将另一个数组剩余的所有元素都添加到结果数组的末尾while(index1 <= mid) {temp[index++] = arr[index1++];}while(index2 <= right) {temp[index++] = arr[index2++];}// 结果回写到原数组System.arraycopy(temp, left, arr, left, right - left + 1);
}// 归并排序
private void mergeSort(int[] arr) {int n = arr.length;// 根据步长来迭代,每一轮扩大一倍for (int left, mid, right, step = 1; step < n; step <<= 1) {left = 0; // 左端点从 0 开始while (left < n) {mid = left + step - 1; // 计算区间中点的位置// 另一半区间无法构成,直接进行下一轮if (mid >= n - 1) {break;}// 数组内剩余元素不一定够填满右半边数组,右端点要根据情况来取值right = Math.min(left + (step << 1) - 1, n - 1);merge(arr, left, mid, right);// 移动指针,准备归并右半边数组left = right + 1;}}
}

归并分治

分治是一种算法思想,归并分治顾名思义就是涉及到了归并的分治策略。这部分内容其实和排序关系不大,但是作为归并应用的扩展放在一起整理比较合适的。

算法思想

如果一个问题满足两个条件,那么大概率可以使用归并分治解决:

  • 全局的结果相当于划分成两部分之后,左半边、右半边与跨左右三部分的结果的并集,也就是这个问题可以总中间拆分成子问题。
  • 如果拆分之后小范围上有序,能够使得计算跨左右的答案时更方便。

实践案例:Leetcode 493. 翻转对

递归

class Solution {// 注意预先定义辅助数组,防止递归层数深的情况下花大量时间空间去开数组public static int MAX_N = 50010;public static int[] temp = new int[MAX_N];public int reversePairs(int[] nums) {return reversePairs(nums, 0, nums.length - 1);}// 重载一个同名方法,将它改造成方便递归的形式private int reversePairs(int[] nums, int left, int right) {// 只有一个元素的情况下没有答案if(left == right) {return 0;}int mid = left + ((right - left) >>> 1);// 结果等于左半边、右半边与跨左右的结果的并集return reversePairs(nums, left, mid) + reversePairs(nums, mid + 1, right) + merge(nums, left, mid, right);}private int merge(int[] nums, int left, int mid,  int right) {int res = 0;// 当前跨小范围的情况下,更小范围内已经有序for(int i = left, j = mid + 1; i <= mid; i++) {// 确定了当前轮 j 的位置之后,这个指针不会回退,这也是提升性能的根本原因while(j <= right && (long) nums[i] > (long) 2 * nums[j]) {j++;}res += j - mid - 1;}// 常规归并流程int index1 = left, index2 = mid + 1, index = left;while(index1 <= mid && index2 <= right) {temp[index++] = nums[index1] <= nums[index2] ? nums[index1++] : nums[index2++];}while(index1 <= mid) {temp[index++] = nums[index1++];}while(index2 <= right) {temp[index++] = nums[index2++];}System.arraycopy(temp, left, nums, left, right - left + 1);return res;}
}

非递归

class Solution {// 注意预先定义辅助数组,防止递归层数深的情况下花大量时间空间去开数组public static int MAX_N = 50010;public static int[] temp = new int[MAX_N];public int reversePairs(int[] nums) {int res = 0;int n = nums.length;for (int left, mid, right, step = 1; step < n; step <<= 1) {left = 0;while (left < n) {mid = left + step - 1;if (mid >= n - 1) {break;}right = Math.min(left + (step << 1) - 1, n - 1);res += merge(nums, left, mid, right); // 累计每次归并的结果left = right + 1;}}return res;}private int merge(int[] nums, int left, int mid,  int right) {int res = 0;// 当前跨小范围的情况下,更小范围内已经有序for(int i = left, j = mid + 1; i <= mid; i++) {// 确定了当前轮 j 的位置之后,这个指针不会回退,这也是提升性能的根本原因while(j <= right && (long) nums[i] > (long) 2 * nums[j]) {j++;}res += j - mid - 1;}// 常规归并流程int index1 = left, index2 = mid + 1, index = left;while(index1 <= mid && index2 <= right) {temp[index++] = nums[index1] <= nums[index2] ? nums[index1++] : nums[index2++];}while(index1 <= mid) {temp[index++] = nums[index1++];}while(index2 <= right) {temp[index++] = nums[index2++];}System.arraycopy(temp, left, nums, left, right - left + 1);return res;}
}

梳理总结

分治是一种通过不断划分来减小问题规模,而归并是用来收集得到全局结果的方法。上面的例子所使用的都是一分为二的做法,其实每一轮可以划分成更多子问题,演变成多路归并。
正是上面这种不停划分的过程,使得无论是归并排序还是归并分治,都能有效地将暴力搜索需要 O ( N 2 ) O(N ^ 2) O(N2) 量级的方法,优化到 O ( N l o g N ) O(NlogN) O(NlogN) 这个级别。
当然,本质上来说还是空间换时间,这样的操作很明显需要 O ( N ) O(N) O(N) 量级的额外空间。

从使用上来说,归并排序一般不会成为手写排序的选择。但是归并分治则是很多问题的优秀解决方案,需要注意它的使用前提和具体实现。

后记

使用 Leetcode 912. 排序数组 进行测试,归并排序能够比较高高效地完成任务,略逊于计数排序和基数排序(不过其实题目要求使用尽可能少的额外空间,归并排序肯定不属于首选的方案)。

相关文章:

常见排序算法总结 (三) - 归并排序与归并分治

归并排序 算法思想 将数组元素不断地拆分&#xff0c;直到每一组中只包含一个元素&#xff0c;单个元素天然有序。之后用归并的方式收集跨组的元素&#xff0c;最终形成整个区间上有序的序列。 稳定性分析 归并排序是稳定的&#xff0c;拆分数组时会自然地将元素分成有先后…...

【后端开发】Go语言编程实践,Goroutines和Channels,基于共享变量的并发,反射与底层编程

【后端开发】Go语言编程实践&#xff0c;Goroutines和Channels&#xff0c;基于共享变量的并发&#xff0c;反射与底层编程 【后端开发】Go语言高级编程&#xff0c;CGO、Go汇编语言、RPC实现、Web框架实现、分布式系统 文章目录 1、并发基础, Goroutines和Channels2、基于共享…...

PyTorch 2.5.1: Bugs修复版发布

一&#xff0c;前言 在深度学习框架的不断迭代中&#xff0c;PyTorch 社区始终致力于提供更稳定、更高效的工具。最近&#xff0c;PyTorch 2.5.1 版本正式发布&#xff0c;这个版本主要针对 2.5.0 中发现的问题进行了修复&#xff0c;以提升用户体验。 二&#xff0c;PyTorch 2…...

【Android】组件化嘻嘻嘻gradle耶耶耶

文章目录 Gradle基础总结&#xff1a;gradle-wrapper项目根目录下的 build.gradlesetting.gradle模块中的 build.gradlelocal.properties 和 gradle.properties 组件化&#xff1a;项目下新建一个Gradle文件定义一个ext扩展区域config.gradle全局基础配置&#xff08;使用在项目…...

vulnhub靶场【哈利波特】三部曲之Aragog

前言 使用virtual box虚拟机 靶机&#xff1a;Aragog : 192.168.1.101 攻击&#xff1a;kali : 192.168.1.16 主机发现 使用arp-scan -l扫描&#xff0c;在同一虚拟网卡下 信息收集 使用nmap扫描 发现22端口SSH服务&#xff0c;openssh 80端口HTTP服务&#xff0c;Apach…...

HarmonyOS开发中,如何高效定位并分析内存泄露相关问题

HarmonyOS开发中&#xff0c;如何高效定位并分析内存泄露相关问题 (1)Allocation的应用调试方式Memory泳道Native Allocation泳道 (2)Snapshot(3)ASan的应用使用约束配置参数使能ASan方式一方式二 启用ASanASan检测异常码 (4)HWASan的应用功能介绍约束条件使能HWASan方式一方式…...

java调用ai模型:使用国产通义千问完成基于知识库的问答

整体介绍&#xff1a; 基于RAG&#xff08;Retrieval-Augmented Generation&#xff09;技术&#xff0c;可以实现一个高效的Java智能问答客服机器人。核心思路是将预先准备的问答QA文档&#xff08;例如Word格式文件&#xff09;导入系统&#xff0c;通过数据清洗、向量化处理…...

2023年第十四届蓝桥杯Scratch国赛真题—推箱子

推箱子 程序演示及其源码解析&#xff0c;可前往&#xff1a; https://www.hixinao.com/scratch/creation/show-188.html 若需在线编程&#xff0c;在线测评模考&#xff0c;助力赛事可自行前往题库中心&#xff0c;按需查找&#xff1a; https://www.hixinao.com/ 题库涵盖…...

银河麒麟V10-SP1设置redis开机自启

前言&#xff1a; redis安装请看&#xff1a;银河麒麟V10-SP1离线安装redis5.0.1_银河麒麟v10 redis5.0-CSDN博客 一、编辑自启文件 vim /etc/systemd/system/redis.service [Unit] DescriptionRedis In-Memory Data Store Afternetwork.target [Service] Typeforking ExecS…...

释放超凡性能,打造鸿蒙原生游戏卓越体验

11月26日在华为Mate品牌盛典上&#xff0c;全新Mate70系列及多款全场景新品正式亮相。在游戏领域&#xff0c;HarmonyOS NEXT加持下游戏的性能得到充分释放。HarmonyOS SDK为开发者提供了软硬协同的系统级图形加速解决方案——Graphics Accelerate Kit&#xff08;图形加速服务…...

Node.js 实战: 爬取百度新闻并序列化 - 完整教程

很多时候我们需要爬取一些公开的网页内容来做一些数据分析和统计。而多数时候&#xff0c;大家会用到python &#xff0c;因为实现起来很方便。但是其实Node.js 用来爬取网络内容&#xff0c;也是非常强大的。 今天我向大家介绍一下我自己写的一个百度新闻的爬虫&#xff0c;可…...

106.【C语言】数据结构之二叉树的三种递归遍历方式

目录 1.知识回顾 2.分析二叉树的三种遍历方式 1.总览 2.前序遍历 3.中序遍历 4.后序遍历 5.层序遍历 3.代码实现 1.准备工作 2.前序遍历函数PreOrder 测试结果 3.中序遍历函数InOrder 测试结果 4.后序遍历函数PostOrder 测试结果 4.底层分析 1.知识回顾 在99.…...

qt QToolButton详解

1、概述 QToolButton是Qt框架中的一个控件&#xff0c;它继承自QAbstractButton。QToolButton通常用于工具栏&#xff08;QToolBar&#xff09;中&#xff0c;提供了一种快速访问命令或选项的方式。与普通的QPushButton按钮相比&#xff0c;QToolButton通常只显示一个图标而不…...

2024年大热,Access平替升级方案,也适合Excel用户

欢迎各位看官&#xff0c;您来了&#xff0c;就对了&#xff01; 您多半是Access忠实粉丝&#xff0c;至少是excel用户&#xff0c;亦或是WPS用户吧。那就对了&#xff0c;今天的分享肯定对您有用。 本文1100字&#xff0c;阅读时长2分50秒&#xff01; 现实总是不尽人意&am…...

探索Scala的模式匹配:身份证识别与等级判定!!! #Scala # scala #匹配模式

在Scala编程语言中&#xff0c;模式匹配是一个强大且表达力丰富的特性&#xff0c;它允许我们以声明式的方式处理多种情况。今天&#xff0c;我们将通过两个有趣的例子来展示Scala模式匹配的魅力&#xff1a;身份证号识别和等级判定。 1. 身份证号识别&#xff1a;定位你的家乡…...

python数据分析之爬虫基础:爬虫介绍以及urllib详解

前言 在数据分析中&#xff0c;爬虫有着很大作用&#xff0c;可以自动爬取网页中提取的大量的数据&#xff0c;比如从电商网站手机商品信息&#xff0c;为市场分析提供数据基础。也可以补充数据集、检测动态变化等一系列作用。可以说在数据分析中有着相当大的作用&#xff01;…...

【星海随笔】syslinux

Ubuntu相关资料 https://www.pugetsystems.com/labs/hpc/ubuntu-22-04-server-autoinstall-iso/#Step_2_Unpack_files_and_partition_images_from_the_Ubuntu_2204_live_server_ISO https://launchpad.net/ubuntu/source/squashfs-tools/1:4.6.1-1build1 sudo tar -xf my_compu…...

力扣C语言刷题记录 (二)移除元素

给你一个数组 nums 和一个值 val&#xff0c;你需要 原地 移除所有数值等于 val 的元素。元素的顺序可能发生改变。然后返回 nums 中与 val 不同的元素的数量。 假设 nums 中不等于 val 的元素数量为 k&#xff0c;要通过此题&#xff0c;您需要执行以下操作&#xff1a; 更改…...

【Vue3】【Naive UI】<NAutoComplete>标签

【Vue3】【Naive UI】标签 <NAutoComplete> 是 Naive UI 库中的一个组件&#xff0c;用于实现自动完成或联想输入功能。 它允许用户在输入时看到与当前输入匹配的建议列表&#xff0c;从而帮助用户更快地填写表单字段。 这个组件通常用于搜索框、地址输入等场景&#xff…...

【Halcon】使用均值滤波出现假边怎么办?

在图像处理过程中,均值滤波是一种常见的平滑技术,用于减少图像中的噪声。然而,当应用于具有显著边缘或对比度变化的图像时,均值滤波可能会导致“假边”现象,即原本不存在的边缘在滤波后变得明显。以下是如何在Halcon中处理这一问题,并提供一个完整的示例代码。 示例背景…...

抖音增长新引擎:品融电商,一站式全案代运营领跑者

抖音增长新引擎&#xff1a;品融电商&#xff0c;一站式全案代运营领跑者 在抖音这个日活超7亿的流量汪洋中&#xff0c;品牌如何破浪前行&#xff1f;自建团队成本高、效果难控&#xff1b;碎片化运营又难成合力——这正是许多企业面临的增长困局。品融电商以「抖音全案代运营…...

C++ 基础特性深度解析

目录 引言 一、命名空间&#xff08;namespace&#xff09; C 中的命名空间​ 与 C 语言的对比​ 二、缺省参数​ C 中的缺省参数​ 与 C 语言的对比​ 三、引用&#xff08;reference&#xff09;​ C 中的引用​ 与 C 语言的对比​ 四、inline&#xff08;内联函数…...

【学习笔记】深入理解Java虚拟机学习笔记——第4章 虚拟机性能监控,故障处理工具

第2章 虚拟机性能监控&#xff0c;故障处理工具 4.1 概述 略 4.2 基础故障处理工具 4.2.1 jps:虚拟机进程状况工具 命令&#xff1a;jps [options] [hostid] 功能&#xff1a;本地虚拟机进程显示进程ID&#xff08;与ps相同&#xff09;&#xff0c;可同时显示主类&#x…...

【HarmonyOS 5 开发速记】如何获取用户信息(头像/昵称/手机号)

1.获取 authorizationCode&#xff1a; 2.利用 authorizationCode 获取 accessToken&#xff1a;文档中心 3.获取手机&#xff1a;文档中心 4.获取昵称头像&#xff1a;文档中心 首先创建 request 若要获取手机号&#xff0c;scope必填 phone&#xff0c;permissions 必填 …...

【开发技术】.Net使用FFmpeg视频特定帧上绘制内容

目录 一、目的 二、解决方案 2.1 什么是FFmpeg 2.2 FFmpeg主要功能 2.3 使用Xabe.FFmpeg调用FFmpeg功能 2.4 使用 FFmpeg 的 drawbox 滤镜来绘制 ROI 三、总结 一、目的 当前市场上有很多目标检测智能识别的相关算法&#xff0c;当前调用一个医疗行业的AI识别算法后返回…...

深度学习水论文:mamba+图像增强

&#x1f9c0;当前视觉领域对高效长序列建模需求激增&#xff0c;对Mamba图像增强这方向的研究自然也逐渐火热。原因在于其高效长程建模&#xff0c;以及动态计算优势&#xff0c;在图像质量提升和细节恢复方面有难以替代的作用。 &#x1f9c0;因此短时间内&#xff0c;就有不…...

RabbitMQ入门4.1.0版本(基于java、SpringBoot操作)

RabbitMQ 一、RabbitMQ概述 RabbitMQ RabbitMQ最初由LShift和CohesiveFT于2007年开发&#xff0c;后来由Pivotal Software Inc.&#xff08;现为VMware子公司&#xff09;接管。RabbitMQ 是一个开源的消息代理和队列服务器&#xff0c;用 Erlang 语言编写。广泛应用于各种分布…...

华为OD机考-机房布局

import java.util.*;public class DemoTest5 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseSystem.out.println(solve(in.nextLine()));}}priv…...

Spring AI Chat Memory 实战指南:Local 与 JDBC 存储集成

一个面向 Java 开发者的 Sring-Ai 示例工程项目&#xff0c;该项目是一个 Spring AI 快速入门的样例工程项目&#xff0c;旨在通过一些小的案例展示 Spring AI 框架的核心功能和使用方法。 项目采用模块化设计&#xff0c;每个模块都专注于特定的功能领域&#xff0c;便于学习和…...

java高级——高阶函数、如何定义一个函数式接口类似stream流的filter

java高级——高阶函数、stream流 前情提要文章介绍一、函数伊始1.1 合格的函数1.2 有形的函数2. 函数对象2.1 函数对象——行为参数化2.2 函数对象——延迟执行 二、 函数编程语法1. 函数对象表现形式1.1 Lambda表达式1.2 方法引用&#xff08;Math::max&#xff09; 2 函数接口…...