算法-从归并排序到归并分治

ops/2024/10/22 11:34:16/

文章目录

    • 前言介绍
    • 1 . 简单的归并排序
    • 2 . 数组的最小和问题
    • 3 . 逆序数对问题
    • 4 . 翻转对数量的计算

前言介绍

归并排序是Merge sort)是一种有效、稳定的排序算法,它采用了分治法(Divide and Conquer)的典型应用,何为分治 ? 即把多个事件分为两个或者多个子问题来解决(其实是一种递归寻找子问题的思路)

归并分支的思想

  • 原理 : 在思考一个问题在大范围上的答案, 是否等于, 左部分的答案 + 右部分的答案 + 跨越左右的答案
  • 在计算跨越左右的答案的时候, 我们要思考如果加上左右都有序的这个条件, 会不会获得计算的便利性
  • 如果满足上述的两点, 那我们的这个题大概率是通过归并分治的思路去解决
  • 只需要改编我们的merge函数即可, 在求解问题的同时, 加上归并排序的过程即可

1 . 简单的归并排序

首先先讲一下我们的归并排序,归并排序是一种高效的排序算法
在这里插入图片描述
代码的实现细节如下

class Solution {public int[] sortArray(int[] nums) {if (nums == null || nums.length < 2) {return nums;}//下面是排序的主体mergeSort(nums, 0, nums.length - 1);return nums;}private void mergeSort(int[] nums, int left, int right) {if (left >= right) {return;}int mid = left + ((right - left) >> 1);mergeSort(nums, left, mid);mergeSort(nums, mid + 1, right);//其实质是每次通过merge函数之后,之前比较的结果就会被保留进行加速merge(nums, left, mid, right);}private void merge(int[] nums, int left, int mid, int right) {int[] temp = new int[right - left + 1];int pl = left;int pr = mid + 1;int index = 0;while (pl <= mid && pr <= right) {if (nums[pl] <= nums[pr]) {temp[index++] = nums[pl++];} else {temp[index++] = nums[pr++];}}while (pl <= mid) {temp[index++] = nums[pl++];}while (pr <= right) {temp[index++] = nums[pr++];}for (int i = 0; i < index; ++i) {nums[left + i] = temp[i];}}
}

有了上面的归并分治的基础,我们回头看我们的归并排序,其实就是分治法的一种标准应用, 总体有序 = 左半边有序 + 右半边有序 + 跨越左右有序

2 . 数组的最小和问题

在这里插入图片描述
用暴力方法,这道题是很好想的,时间复杂度是O(N^2)
我们先来尝试分析一下这道题

首先 :
逆向思维转换,求数组的小和,其实就是求一个数在小和里面贡献了多少
即为,对于任意一个数来说,该数右边有几个数比该数大,那就贡献了几份这个数字的大小
其次 :
进行分治法分析 , 总小和数 = 左小和数 + 右小和数 + 左跨右的小和数
请一定要记住 , 计算某一半边的小和数的时候 , 一定不能与另一半边关联(分)
最后 :
有了分治的思想 , 当左半边跟右半边有序的时候 , 题目会不会更简便 , 答案是肯定的 , 有序了之后 , 每次滑动的时候 , 指针是不会进行回退的(滑动窗口的思想) , 所以时间复杂度可以达到 O(N) (统计的时候)

下面是代码实现

 /*** 求一个数组的小和 :* 基本思路是首先用逆向思维法, 想要计算小和, 可以统计某一个数字在小和中的出现的次数来累加求和计算* 整个数组的小和 == 左侧数组的小和 + 右侧数组的小和 + 跨越左右的小和* 这道题, 如果不进行排序的话, 时间复杂度还是O(N^2)...* 在进行排序了之后,我们的统计的时间复杂度其实是O(N)...* 所以这道题可以采用归并分治的策略来解决** @param nums* @param left* @param right* @return*/public class Main {public static void main(String[] args) {Scanner in = new Scanner(System.in);int sz = in.nextInt();int[] nums = new int[sz];for (int i = 0; i < nums.length; ++i) {nums[i] = in.nextInt();}long res = getMinSum(nums,0,nums.length - 1);System.out.println(res);}public static long getMinSum(int[] nums, int left, int right) {if (left == right) {return 0;}int mid = left + ((right - left) >> 1);return getMinSum(nums, left, mid) + getMinSum(nums, mid + 1,right) + mergeMinSum(nums, left, mid, right);}private static long mergeMinSum(int[] nums, int left, int mid, int right) {int pl = left;int pr = mid + 1;long ans = 0;long tempSum = 0;//下面是进行统计的过程//下面这个循环的时间复杂度其实是O(N),因为我们的左指针是不发生回退的while (pr <= right) {while (pl <= mid && nums[pl] <= nums[pr]) {tempSum += nums[pl++];}ans += tempSum;pr++;}//下面我们进行排序的过程pl = left;pr = mid + 1;int index = 0;int[] temp = new int[right - left + 1];while (pl <= mid && pr <= right) {temp[index++] = nums[pl] <= nums[pr] ? nums[pl++] : nums[pr++];}while (pl <= mid) {temp[index++] = nums[pl++];}while (pr <= right) {temp[index++] = nums[pr++];}for (int i = 0; i < temp.length; ++i) {nums[left + i] = temp[i];}return ans;}}

3 . 逆序数对问题

在这里插入图片描述
归并分治的分析
总交易逆序对数目 = 左边数目 + 右边数目 + 左跨右的数目
然后如果在每次统计完进行归并排序之后,我们又会加速这过程
其实就是归并排序(滑动窗口)
代码实现如下

class Solution {public int reversePairs(int[] record) {if (record == null || record.length <= 1) {return 0;}return process(record, 0, record.length - 1);}// 下面就是归并的思路了private int process(int[] nums, int left, int right) {// 递归终止条件if (left == right) {return 0;}int mid = left + ((right - left) >> 1);return process(nums, left, mid) + process(nums, mid + 1, right) + mergeOfrevers(nums, left, mid, right);}private int mergeOfrevers(int[] nums, int left, int mid, int right) {// 先统计,后进行排序int pl = left;int pr = mid + 1;int ans = 0;while (pl <= mid) {while (pr <= right && nums[pl] > nums[pr]) {pr++;}ans += pr - mid - 1;pl++;}// 下面就没什么可说的了,就是简单的归并排序的过程pl = left;pr = mid + 1;int index = 0;int[] temp = new int[right - left + 1];while (pl <= mid && pr <= right) {temp[index++] = nums[pl] <= nums[pr] ? nums[pl++] : nums[pr++];}while (pl <= mid) {temp[index++] = nums[pl++];}while (pr <= right) {temp[index++] = nums[pr++];}for (int i = 0; i < temp.length; ++i) {nums[left + i] = temp[i];}return ans;}
}

4 . 翻转对数量的计算

在这里插入图片描述
这道题也没什么可以说的点, 跟上一道题的思路, 完完全全一致
直接上代码实现 ,改动的时候, 记得把 nums[pl] <= nums[pr]* 2
换成 nums[pl] / 2.0 <= nums[pr]
因为要防止数字越界

class Solution {public int reversePairs(int[] record) {if (record == null || record.length <= 1) {return 0;}return process(record, 0, record.length - 1);}// 下面就是归并分治的思路了private int process(int[] nums, int left, int right) {// 递归终止条件if (left == right) {return 0;}int mid = left + ((right - left) >> 1);return process(nums, left, mid) + process(nums, mid + 1, right) + mergeOfrevers(nums, left, mid, right);}private int mergeOfrevers(int[] nums, int left, int mid, int right) {// 先统计,后进行排序int pl = left;int pr = mid + 1;int ans = 0;while (pl <= mid) {while (pr <= right && nums[pl]  / 2.0 >  (double)nums[pr]) {pr++;}ans += pr - mid - 1;pl++;}// 下面就没什么可说的了,就是简单的归并排序的过程pl = left;pr = mid + 1;int index = 0;int[] temp = new int[right - left + 1];while (pl <= mid && pr <= right) {temp[index++] = nums[pl] <= nums[pr] ? nums[pl++] : nums[pr++];}while (pl <= mid) {temp[index++] = nums[pl++];}while (pr <= right) {temp[index++] = nums[pr++];}for (int i = 0; i < temp.length; ++i) {nums[left + i] = temp[i];}return ans;}
}

http://www.ppmy.cn/ops/45510.html

相关文章

Docker搭建FRP内网穿透服务器

使用Docker搭建一个frp内网穿透 在现代网络环境中&#xff0c;由于防火墙和NAT等原因&#xff0c;内网设备无法直接被外网访问。FRP (Fast Reverse Proxy) 是一款非常流行的内网穿透工具&#xff0c;它能够帮助我们将内网服务暴露给外网。本文将介绍如何在Linux服务器上使用Do…

npm镜像源管理、nvm安装多版本node异常处理

查看当前使用的镜像源 npm config get registry --locationglobal 设置使用官方源 npm config set registry https://registry.npmjs.org/ --locationglobal 设置淘宝镜像源 npm config set registry https://registry.npm.taobao.org/ --locationglobal 需要更改淘宝镜像源地址…

查看redis内存使用情况及总内存量

import redis# 配置 Redis 主机和端口 redis_hosts [{"域名或ip1", "port": "端口号1"},{"域名或ip2", "port": "端口号2"},] all_used_port[端口号1,端口号2] # 转换字节为GB def bytes_to_gb(bytes_value):re…

Oracle数据库之事务(十四)

在Oracle数据库中&#xff0c;事务是工作的逻辑单元&#xff0c;用于确保数据的一致性和完整性。以下是对Oracle事务的详细解释&#xff1a; 1. 定义 事务&#xff1a;在数据库中&#xff0c;事务是由一个或多个SQL语句组成的逻辑单元&#xff0c;这些语句共同完成一组相关的…

人类行为验证处理方案 —— 脱离UI组件库实现登录、注册+表单校验

目录 01: 构建登录模块基础UI结构 02: 表单校验实现原理与方案分析 表单校验的实现原理 自定义表单校验方案分析 文章中的方案实现 03: 基于 vee-validate 实现普适的表单校验 04: 什么是人类行为验证&#xff1f;它的目的、实现原理、构建方案分别是什么&am…

vue实现左侧拖拽拉伸,展开收起

需求&#xff1a;1.左侧是个树形结构&#xff0c;有的文字过长展示不全&#xff0c;想通过拖拽显示全部的数据 2.展开收起 实现图中效果 <div class"catalog-drag"><svg t"1687228434888" class"icon" viewBox"0 0 1…

【香橙派 AIpro】新手保姆级开箱教程:Linux镜像+vscode远程连接

香橙派 AIpro 开发板 AI 应用部署测评 写在最前面一、开发板概述官方资料试用印象适用场景 二、详细开发前准备步骤1. 环境准备2. 环境搭建3. vscode安装ssh插件4. 香橙派 AIpro 添加连接配置5. 连接香橙派 AIpro6. SSH配置 二、详细开发步骤1. 登录 juypter lab2. 样例运行3. …

蓝桥杯-班级活动

题目描述 小明的老师准备组织一次班级活动。班上一共有 ( n ) 名&#xff08;( n ) 为偶数&#xff09;同学&#xff0c;老师想把所有的同学进行分组&#xff0c;每两名同学一组。为了公平&#xff0c;老师给每名同学随机分配了一个 ( n ) 以内的正整数作为 id&#xff0c;第 …