数据结构第21节 归并排序以及优化方案

归并排序(Merge Sort)是一种分治策略的排序算法。它将一个大数组分成两个子数组,递归地对它们进行排序,然后将排序后的子数组合并成一个有序数组。

Java代码实现:

public class MergeSort {public static void main(String[] args) {int[] array = {5, 2, 8, 3, 1, 6, 9, 7, 4};mergeSort(array, 0, array.length - 1);// 打印排序后的数组for (int i : array) {System.out.print(i + " ");}}public static void mergeSort(int[] array, int left, int right) {if (left < right) {int middle = (left + right) / 2;// 递归调用归并排序函数,对左边子数组进行排序mergeSort(array, left, middle);// 递归调用归并排序函数,对右边子数组进行排序mergeSort(array, middle + 1, right);// 合并两个子数组merge(array, left, middle, right);}}private static void merge(int[] array, int left, int middle, int right) {int n1 = middle - left + 1; // 左子数组长度int n2 = right - middle;    // 右子数组长度// 创建临时数组int[] L = new int[n1];int[] R = new int[n2];// 复制数据到临时数组中for (int i = 0; i < n1; ++i)L[i] = array[left + i];for (int j = 0; j < n2; ++j)R[j] = array[middle + 1 + j];// 合并临时数组回原数组int i = 0, j = 0;int k = left;while (i < n1 && j < n2) {if (L[i] <= R[j]) {array[k] = L[i];i++;} else {array[k] = R[j];j++;}k++;}// 拷贝剩余元素while (i < n1) {array[k] = L[i];i++;k++;}while (j < n2) {array[k] = R[j];j++;k++;}}
}

这个程序首先定义了一个mergeSort方法来执行排序操作。它接收数组和左右边界作为参数。如果左边界小于右边界,它会计算中间点,并递归地对左侧和右侧的子数组进行排序,最后调用merge方法来合并这两个子数组。

merge方法创建了两个临时数组来存储分割后的子数组,然后比较这两个子数组中的元素,并按顺序将较小的元素放回原始数组中,直到其中一个子数组的元素被全部放入原始数组。最后,如果有任何剩余的元素,它们会被复制到原始数组中。

整个过程重复进行,直到所有子数组都被排序并合并回原始数组,从而得到完全排序的数组。

在归并排序中,我们可以做一些小的改进来优化性能或者代码的可读性。以下是一些可能的优化点:

  1. 避免重复计算:在mergeSort函数中,每次递归调用都会计算中间点。我们可以在主函数中预先计算中间点,减少重复计算。

  2. 使用非递归版本:虽然递归实现简洁,但是过多的递归调用可能会导致栈溢出。可以使用迭代的方式来实现归并排序,使用循环和栈或队列等数据结构。

  3. 优化合并过程:在合并时,可以尝试减少数组的复制次数,比如使用索引直接在原数组上进行操作。

  4. 使用哨兵值:在合并阶段,可以通过设置哨兵值来简化边界条件的处理。

下面是基于上述优化点之一,即使用非递归版本的归并排序实现:

public class MergeSortNonRecursive {public static void main(String[] args) {int[] array = {5, 2, 8, 3, 1, 6, 9, 7, 4};mergeSortNonRecursive(array);// 打印排序后的数组for (int i : array) {System.out.print(i + " ");}}public static void mergeSortNonRecursive(int[] array) {int n = array.length;int[] temp = new int[n]; // 用于合并操作的临时数组// 初始步长为1,每次翻倍for (int step = 1; step < n; step *= 2) {// 从头开始,每次步长移动for (int leftStart = 0; leftStart < n - step; leftStart += 2 * step) {int mid = leftStart + step - 1;int rightEnd = Math.min(leftStart + 2 * step - 1, n - 1);merge(array, leftStart, mid, rightEnd, temp);}}}private static void merge(int[] array, int leftStart, int mid, int rightEnd, int[] temp) {int leftEnd = mid;int rightStart = mid + 1;int left = leftStart;int right = rightStart;int index = leftStart;// 合并过程while (left <= leftEnd && right <= rightEnd) {if (array[left] <= array[right]) {temp[index++] = array[left++];} else {temp[index++] = array[right++];}}// 拷贝剩余的元素while (left <= leftEnd) {temp[index++] = array[left++];}while (right <= rightEnd) {temp[index++] = array[right++];}// 将排序结果复制回原数组for (int i = leftStart; i <= rightEnd; i++) {array[i] = temp[i];}}
}

在这个版本中,我们使用了一个循环来控制步长的增长,从1开始,每次翻倍,直到步长大于或等于数组的长度。在每次循环中,我们将数组按照当前步长分割成多个子数组对,并对每一对子数组进行合并操作。这样就避免了递归带来的潜在问题,同时也保持了归并排序的时间复杂度O(n log n)。

对于归并排序,进一步的优化通常集中在减少内存使用、提高缓存效率以及避免不必要的数据复制等方面。以下是一个更优化的归并排序实现,主要改进点在于:

  1. 减少额外空间:通常归并排序需要与输入数组相同大小的辅助数组,这里我们通过调整合并策略来减少所需的空间。

  2. 提高缓存局部性:通过调整合并的方式,让数据访问模式更加连续,从而提高CPU缓存的命中率。

  3. 微优化:在合并过程中,通过避免不必要的边界检查和条件判断,提高执行效率。

以下是结合这些优化点的归并排序Java代码:

public class OptimizedMergeSort {public static void main(String[] args) {int[] array = {5, 2, 8, 3, 1, 6, 9, 7, 4};optimizedMergeSort(array);// 打印排序后的数组for (int i : array) {System.out.print(i + " ");}}public static void optimizedMergeSort(int[] array) {int n = array.length;int[] aux = new int[n/2]; // 辅助数组,大小为输入数组的一半// 自底向上迭代归并排序for (int sz = 1; sz < n; sz *= 2) {for (int lo = 0; lo < n - sz; lo += sz * 2) {int mid = lo + sz - 1;int hi = Math.min(lo + sz * 2 - 1, n - 1);merge(array, lo, mid, hi, aux);}}}private static void merge(int[] array, int lo, int mid, int hi, int[] aux) {// 使用辅助数组进行合并int auxLo = lo;int auxHi = mid + 1;int k = lo;// 如果辅助数组空间不足,则直接使用原数组的一部分if (auxHi - auxLo > aux.length) {System.arraycopy(array, auxLo, aux, 0, auxHi - auxLo);auxLo = 0;auxHi -= auxLo;} else {System.arraycopy(array, auxLo, aux, auxLo, auxHi - auxLo);}while (auxLo <= mid - lo && auxHi <= hi) {if (aux[auxLo] <= array[auxHi]) {array[k++] = aux[auxLo++];} else {array[k++] = array[auxHi++];}}// 剩余部分拷贝回原数组while (auxLo <= mid - lo) {array[k++] = aux[auxLo++];}while (auxHi <= hi) {array[k++] = array[auxHi++];}}
}

在这个版本中,我们使用了一个大小仅为原数组一半的辅助数组aux。当合并操作涉及的数据量超过aux的容量时,我们会直接在原数组中进行操作,而不是使用额外的辅助空间。这减少了所需的辅助空间,但同时也增加了算法的复杂性,因为需要更仔细地管理原数组和辅助数组之间的数据交换。

请注意,这种优化可能会增加代码的复杂性和难度,而且在某些情况下,由于现代处理器的高速缓存机制,使用较大的辅助数组可能反而能提供更好的性能。因此,在实际应用中,需要根据具体情况和目标平台来权衡是否采用这样的优化。

将上述优化后的归并排序代码以表格形式展示,可以帮助理解代码的结构和流程。下面是对代码逻辑的分解,以表格形式呈现:

阶段函数/代码段描述
初始化public static void main(String[] args)主函数,初始化数组并调用排序函数。
排序函数optimizedMergeSort(int[] array)归并排序的主体函数,使用迭代而非递归方式。
int n = array.length;获取数组长度。
int[] aux = new int[n/2];创建辅助数组,大小为原数组的一半。
for (int sz = 1; sz < n; sz *= 2)控制合并的步长,自底向上逐步合并子数组。
for (int lo = 0; lo < n - sz; lo += sz * 2)对数组进行分割,准备合并相邻的子数组。
int mid = lo + sz - 1;计算左侧子数组的末尾位置。
int hi = Math.min(lo + sz * 2 - 1, n - 1);计算右侧子数组的末尾位置。
merge(array, lo, mid, hi, aux);调用合并函数,合并子数组。
合并函数merge(int[] array, int lo, int mid, int hi, int[] aux)实现子数组的合并。
int auxLo = lo;辅助数组起始位置。
int auxHi = mid + 1;辅助数组或右侧子数组的起始位置。
int k = lo;目标数组位置指针。
if (auxHi - auxLo > aux.length)检查辅助数组空间是否足够。
System.arraycopy(array, auxLo, aux, 0, auxHi - auxLo);如果空间不足,直接使用原数组的一部分。
while (auxLo <= mid - lo && auxHi <= hi)合并左侧和右侧子数组。
if (aux[auxLo] <= array[auxHi])比较并选择较小的元素放入目标位置。
while (auxLo <= mid - lo)将左侧剩余元素放入目标位置。
while (auxHi <= hi)将右侧剩余元素放入目标位置。

这个表格展示了代码的主要结构和每个部分的功能,有助于理解优化后的归并排序算法是如何工作的。注意,表格中的描述是简化的,实际代码中可能包含更多的细节和边界条件的处理。

为了更好地说明优化后的归并排序的过程,我们可以将整个排序过程分解成几个关键步骤,并用表格形式表示。这里以数组 [5, 2, 8, 3, 1, 6, 9, 7, 4] 为例,展示整个排序过程。

初始状态:

Array: [5, 2, 8, 3, 1, 6, 9, 7, 4]

过程分析:

第一阶段:分割数组
  • Step 1: 以步长为1开始,分割数组为单个元素。
  • Step 2: 合并相邻的单个元素,形成有序对。
  • Step 3: 以步长为2,分割数组为有序对。
  • Step 4: 合并相邻的有序对,形成更长的有序序列。
  • Step N: 重复此过程,直到整个数组被排序。
第二阶段:合并数组
  • Merge 1: 合并 [5, 2][8, 3] 成为 [2, 5, 3, 8]
  • Merge 2: 合并 [1, 6][9, 7] 成为 [1, 6, 7, 9]
  • Merge 3: 合并 [4] 单独存在。
  • Merge 4: 合并 [2, 5, 3, 8][1, 6, 7, 9] 成为 [1, 2, 3, 5, 6, 7, 8, 9]
  • Merge 5: 最后合并 [1, 2, 3, 5, 6, 7, 8, 9][4] 成为最终的有序数组。

归并排序过程解析:

步骤数组状态描述
初始[5, 2, 8, 3, 1, 6, 9, 7, 4]初始未排序数组
Step 1[5, 2], [8, 3], [1, 6], [9, 7], [4]分割为单个元素
Step 2[2, 5], [3, 8], [1, 6], [7, 9], [4]合并相邻元素
Step 3[2, 3, 5, 8], [1, 6, 7, 9], [4]合并相邻有序对
Step 4[1, 2, 3, 5, 6, 7, 8, 9], [4]再次合并
Step 5[1, 2, 3, 4, 5, 6, 7, 8, 9]完全排序

请注意,实际的归并排序过程中,数组的“分割”并不是物理上的分割,而是逻辑上的分割,即在算法的控制下分别处理数组的不同部分。合并操作则是在原地进行的,利用辅助数组或特定的算法技巧,将两个有序子数组合并成一个更大的有序子数组。

以上表格提供了一种简化的方式来理解归并排序的各个阶段,但实际上每一次合并操作都涉及到复杂的元素比较和重排。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/46688.shtml

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

相关文章

4.Flink程序编程规范

目录 概述 概述 Flink程序编程规范 官网文档速递 1.Obtain an execution environment 获取执行环境2.Load/create the initial data 加载/创建初始数据 > 数据接入3.Specify transformations on this data 针对数据做处理操作 > 数据处理4.Specify where to put the re…

Java-使用Redisson实现的分布式锁

在使用Redisson实现的分布式锁时&#xff0c;可以很容易地在Java中加入多线程代码来模拟并发环境下 的锁行为。以下是一个使用Redisson的RLock接口创建分布式锁并在多线程环境中使用的示例代 码&#xff1a; 首先&#xff0c;需要在项目中添加Redisson的依赖。如果你使用Mav…

LLM-阿里 DashVector + langchain self-querying retriever 优化 RAG 实践【Query 优化】

文章目录 前言self querying 简介代码实现总结 前言 现在比较流行的 RAG 检索就是通过大模型 embedding 算法将数据嵌入向量数据库中&#xff0c;然后在将用户的查询向量化&#xff0c;从向量数据库中召回相似性数据&#xff0c;构造成 context template, 放到 LLM 中进行查询…

python如何判断变量是否可迭代

python如何判断变量是否可迭代&#xff1f;方法如下&#xff1a; 方法一&#xff1a; 适用于python2和python3 >>> from collections import Iterable >>> isinstance("str", Iterable) True 方法二&#xff1a; 适用于python3 s "hello …

InterSystems IRIS使用python pyodbc连接 windows环境,odbc驱动安装,DSN配置,数据源配置

一、创建的数据库和数据 SELECT 1SELECT $ZVERSIONCREATE TABLE MyApp.Person ( ID INT PRIMARY KEY, Name VARCHAR(100) NOT NULL, Age INT, Gender CHAR(1) );CREATE TABLE MyApp.Person2 ( ID INT PRIMARY KEY, Name VARCHAR(100) NOT NULL, Age INT, Gender CHA…

Gil-Pelaez inversion

一、特征函数 A.随即变量的特征函数定义与性质 B.特征函数与PDF的关系 傅里叶变换:C.特征函数与矩函数关系 二、Gil-Pelaez反演定理 输入功率 P i n P_{in}

八、Docker版MySQL主从复制

目录 一、MySQL主从复制原理就不做讲解了&#xff0c;详情请查看MySQL专栏 二、主从复制搭建步骤 1、新建主服务器容器实例3307 2、进入/usr/mysql/mysql-master/conf目录下新建my.cnf 3、修改完配置后&#xff0c;重启master实例 4、进入mysql-master容器 5、在mysql-ma…

MYSQL 四、mysql进阶 9(数据库的设计规范)

一、为什么需要数据库设计 二、范 式 2.1 范式简介 在关系型数据库中&#xff0c;关于数据表设计的基本原则、规则就称为范式。 可以理解为&#xff0c;一张数据表的设计结 构需要满足的某种设计标准的 级别 。要想设计一个结构合理的关系型数据库&#xff0c;必须满足一定的…

13 IP层协议-网际控制报文协议ICMP

计算机网络资料下载&#xff1a;CSDNhttps://mp.csdn.net/mp_blog/creation/editor/140148186 为了更有效的转发IP数据报和提高交付成果的机会&#xff0c;在网际层使用了网际控制报文协议ICMP。ICMP允许主机或路由器报告差错情况和提供有关异常情况的报告。ICMP不是高层协议数…

携程Java后端实习一面

携程的面试比较注重八股文和项目&#xff0c;算法相关没有字节腾讯严厉&#xff0c;大家参加携程的技术岗面试需要重视八股文和项目细节&#xff0c;要学会深挖项目&#xff0c;希望大家早日oc&#x1f60a;&#x1f44d; HashMap底层原理&#xff0c;扩容机制&#xff0c;从并…

Java面试八股之Redis集群Cluster

Redis集群Cluster Redis Cluster是一种基于数据分片&#xff08;Sharding&#xff09;的分布式缓存和存储系统&#xff0c;它实现了数据的水平扩展、高可用性和自动故障转移。以下是对Redis Cluster模式详细实现流程的描述&#xff1a; 1. 初始化与配置 部署节点&#xff1a…

C++ //练习 15.15 定义你自己的Disc_quote和Bulk_quote。

C Primer&#xff08;第5版&#xff09; 练习 15.15 练习 15.15 定义你自己的Disc_quote和Bulk_quote。 环境&#xff1a;Linux Ubuntu&#xff08;云服务器&#xff09; 工具&#xff1a;vim 代码块 /******************************************************************…

Python酷库之旅-第三方库Pandas(026)

目录 一、用法精讲 65、pandas.bdate_range函数 65-1、语法 65-2、参数 65-3、功能 65-4、返回值 65-5、说明 65-6、用法 65-6-1、数据准备 65-6-2、代码示例 65-6-3、结果输出 66、pandas.period_range函数 66-1、语法 66-2、参数 66-3、功能 66-4、返回值 6…

Gooxi受邀参加第三届中国数据中心服务器与设备峰会

7月2-3日&#xff0c;第三届中国数据中心服务器与设备峰会在上海召开&#xff0c;作为国内最聚焦在服务器领域的专业峰会&#xff0c;吸引了来自全国的行业专家、服务器与机房设备厂家&#xff0c;企业IT用户&#xff0c;数据中心业主共同探讨AIGC时代下智算中心设备的设计之道…

2024年最新最全面的Apifox使用-自动化测试

正文 编排测试场景运行测试持续集成查看测试结果 编排测试场景 新建测试场景 测试场景用于将多个接口有序地组合在一起运行&#xff0c;用于测试一个完整业务流程。 打开 Apifox 后点击左侧菜单栏中的“自动化测试”&#xff0c;点击左上角 号&#xff0c;选择所归属的目录…

记录|.NET上位机开发和PLC通信的实现

本文记录源自&#xff1a;B站视频 实验结果&#xff1a;跟视频做下来是没有问题的。能运行。 目录 前言一、项目Step1. 创建项目Step2. 创建动态图片展示Step3. 创建图片型按钮Step4. 创建下拉框Step1~4的效果展示Step5. 编程实体类操作类Main函数 Step1~5的效果展示Main函数 最…

Binder框架(二) ServiceManager初始化

0、总体流程四部 开机由init进程解析init.rc文件启动servicemanager.rc。启动会调用main.cpp的main函数 main函数里面主要做了以下几件事 &#xff1a; 1.1 打开/dev/binder设备; 1.2 通过mmap映射设备的内存空间到ServiceManager进程中。 1.3 设置ServiceManager为context…

气象水文耦合模式WRF-Hydro建模、编译及运行流程、依赖库准备、案例实践等

目录 第一部分 WRF-Hydro模型功能及运行流程、依赖库准备 第二部分 WRF-Hydro模式编译、离线运行及案例实践 第三部分 结合多案例进行模式数据制备及实践应用 第四部分 模式耦合编译及运行、总结 更多应用 WRF-Hydro模型是一个分布式水文模型&#xff0c;‌它基于WRF‌陆面…

数据库启动报ORA-600 6711故障分析处理---惜分飞

几个月以前的一个数据库故障,今天拿出来在win上重新分析,数据库启动报ORA-600 6711错 C:\Users\XFF>SQLPLUS / AS SYSDBA SQL*Plus: Release 12.1.0.2.0 Production on 星期日 7月 14 16:17:32 2024 Copyright (c) 1982, 2014, Oracle. All rights reserved. 已连接到空…

2024 7.8~7.14 周报

一、上周工作 2024 7.1~7.7 周报-CSDN博客 二、本周计划 跑实验、机器学习、数学表达式 三、完成情况 1. 跑实验 1&#xff09;编码器加入DenseNet&#xff0c;并在解码器的第一层加入cbam&#xff1a; 损失函数——smooth、lpips&#xff0c;跑的CurveVelA&#xff0c;…