算法|4.归并排序及应用

news/2025/2/19 8:30:39/

算法|4.归并排序及应用

1.归并排序算法

题意:归并排序的递归和非递归实现

解题思路:

​ 递归实现:

  • 预处理:数组为空或者长度小于2的直接返回
  • 调用子过程
  • 子过程终止条件L==R
  • 分解成[L,mid],[mid+1,R] ,子数组有序,合并子问题的解,全数组有序
  • 合并使用的是双指针法,最终需要将辅助数组的值再还给原数组。

​ 非递归实现:

  • 注意:**当N非常接近整数最大值时,*必须加那一句if(mergeSize>N/2){ break;}不然2之后可能滚成一个负数,但是加了只是保证循环能够停止,结果可能最后一部分右组没有归并完成,但是不加很可能死循环。
  • 主要解决的就是拆分过程:两种办法①模拟栈Stack②自底向上有序
  • 这里主要使用第二种,模拟栈的话需要压入的参数就是左右的坐标,可以封装一个成类,针对对象操作,暂无需要,略。
  • 还是先预处理:处理特殊情况
  • 这里引入了步长的概念,多少步长为1组,然后组内有序(直接拷贝到原数组,不再需要辅助数组)
  • 之后不断扩大步长,然后使用merge合并,直至全数组有序

优化的点:

  • 这里主要是非递归实现的版本
  • 这里合并使用的是左组和右组——我们每次需要指定左组下标和右组下标
  • 控制左指针的边界条件:L<N
  • 控制中间指针边界条件(左组存在):mergeSize+L-1,M<N(不满足就是左组不够或者右组没了,不满足直接不做合并)
  • 控制右指针的边界条件(右组存在):R=M+mergeSize-1,右组只要有够不够都需要做合并,只不过R的值需要重新赋值判断一下去边界值和当前计算的最小值
  • 每次重置步长之后,L的起始位置都是0

核心代码:

​ 递归实现:

//递归实现
public static void mergeSort1(int[] arr){if(arr==null||arr.length<2){return ;}process(arr,0,arr.length-1);
}private static void process(int[] arr, int L, int R) {if(L==R){return ;}int mid=L+((R-L)>>1);process(arr,L,mid);process(arr,mid+1,R);merge(arr,L,mid,R);
}private static void merge(int[] arr, int L, int M, int R) {int[] help=new int[R-L+1];int index=0;int p1=L;int p2=M+1;while(p1<=M&&p2<=R){help[index++]=arr[p1]<=arr[p2]?arr[p1++]:arr[p2++];}while(p1<=M){help[index++]=arr[p1++];}while(p2<=R){help[index++]=arr[p2++];}for (int i = 0; i < help.length; i++) {arr[L+i]=help[i];}
}

​ 非递归实现:

//非递归实现
public static void mergeSort2(int[] arr){if(arr==null||arr.length<2){return ;}int N=arr.length;int mergeSize=1;while(mergeSize<N){int L=0;while(L<N){int M=L+mergeSize-1;//左组不够了或者根本左组够了,但是右组没有if(M>=N){break;}int R=Math.min(M+mergeSize-1,N-1);merge(arr,L,M,R);L=R+1;}if(mergeSize>N/2){}mergeSize<<=1;}
}

测试代码:

// for test
public static int[] generateRandomArray(int maxSize, int maxValue) {int[] arr = new int[(int) ((maxSize + 1) * Math.random())];for (int i = 0; i < arr.length; i++) {arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());}return arr;
}// for test
public static int[] copyArray(int[] arr) {if (arr == null) {return null;}int[] res = new int[arr.length];for (int i = 0; i < arr.length; i++) {res[i] = arr[i];}return res;
}// for test
public static boolean isEqual(int[] arr1, int[] arr2) {if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) {return false;}if (arr1 == null && arr2 == null) {return true;}if (arr1.length != arr2.length) {return false;}for (int i = 0; i < arr1.length; i++) {if (arr1[i] != arr2[i]) {return false;}}return true;
}// for test
public static void printArray(int[] arr) {if (arr == null) {return;}for (int i = 0; i < arr.length; i++) {System.out.print(arr[i] + " ");}System.out.println();
}// for test
public static void main(String[] args) {int testTime = 500000;int maxSize = 10;int maxValue = 100;System.out.println("测试开始");for (int i = 0; i < testTime; i++) {int[] arr1 = generateRandomArray(maxSize, maxValue);int[] arr2 = copyArray(arr1);mergeSort1(arr1);mergeSort2(arr2);if (!isEqual(arr1, arr2)) {System.out.println("Oops!Error!");printArray(arr1);printArray(arr2);break;}}System.out.println("测试结束");
}

测试结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z5wiiOeG-1685098557569)(F:\typora插图\image-20230526174128752.png)]

2.小和问题

题意:在一个数组中,一个数左边比它小的数的总和,叫该数的小和,数组中所有数的小和累加起来,叫数组小和。给定一个数组arr,求数组小和。

暴力方法:O(N^2)
归并排序应用:O(NlogN)

解题思路:

  • 注意:ans+=arr[p1]<arr[p2]?(R-p2+1)*arr[p1]:0;在复制之前求
  • 合并过程中计算小和
  • 小和产生规则:左组拷贝产生小和,右组拷贝不产生小和,相等先拷贝右组:小和是左边比当前数小的总和。要的是原数组中的小和。
  • 总体来说,是一个规则的转换:左边有多少数比当前数小转变成右边有多少数比当前数要大。
  • 指针不回退技巧:单调的,已经排好序了

对数器:

  • 穷举遍历

核心代码:

public static int smallSum(int[] arr){if(arr==null||arr.length<2){return 0;}return process(arr,0,arr.length-1);
}private static int process(int[] arr, int L, int R) {if(L==R){return 0;}int mid=L+((R-L)>>1);return process(arr,L,mid)+process(arr,mid+1,R)+merge(arr,L,mid,R);
}private static int merge(int[] arr, int L, int M, int R) {int ans=0;int[] help=new int[R-L+1];int index=0;int p1=L;int p2=M+1;while(p1<=M&&p2<=R){ans+=arr[p1]<arr[p2]?(R-p2+1)*arr[p1]:0;help[index++]=arr[p1]<arr[p2]?arr[p1++]:arr[p2++];}while(p1<=M){help[index++]=arr[p1++];}while(p2<=R){help[index++]=arr[p2++];}for (int i = 0; i < help.length; i++) {arr[L+i]=help[i];}return ans;
}

测试代码:

// for test
public static int test(int[] arr) {if (arr == null || arr.length < 2) {return 0;}int res = 0;for (int i = 1; i < arr.length; i++) {for (int j = 0; j < i; j++) {res += arr[j] < arr[i] ? arr[j] : 0;}}return res;
}

非特殊情况,不再放生成随机数组代码,只放对数器代码。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-87ikTikr-1685098557570)(F:\typora插图\image-20230526175839813.png)]

3.逆序对个数

题意:在一个数组中,任何一个前面的数a,和任何一个后面的数b,如果(a,b)是降序的,就称为降序对。给定一个数组arr,求数组的降序对总数量。

暴力方法:O(N^2)
归并排序应用:O(NlogN)

解题思路:

  • 注意:ans+=arr[p1]>arr[p2]?(p2-M):0;help[index--]=arr[p1]<=arr[p2]?arr[p2--]:arr[p1--];下边拷贝规则相等先右边,上边的顾好!!!!!结合例子反复验算谨慎!!!!!不要再反复改了!醉了…
  • 转换指标:从左向右,当左边比右边大的个数,转换成从右向左,右边比当前左数小的个数
  • 计数规则:左边产生,右边不产生,相等的先拷贝左边的
  • 对应的,归并的顺序改变一下

核心代码:

public static int reversePairNumber(int[] arr){if(arr==null||arr.length<2){return 0;}return process(arr,0,arr.length-1);
}private static int process(int[] arr, int L, int R) {if(L==R){return 0;}int mid=L+((R-L)>>1);return process(arr,L,mid)+process(arr,mid+1,R)+merge(arr,L,mid,R);
}private static int merge(int[] arr, int L, int M, int R) {int ans=0;int[] help=new int[R-L+1];int index=help.length-1;int p1=M;int p2=R;while(p1>=L&&p2>=M+1){ans+=arr[p1]>arr[p2]?(p2-M):0;help[index--]=arr[p1]<=arr[p2]?arr[p2--]:arr[p1--];}while(p1>=L){help[index--]=arr[p1--];}while(p2>=M+1){help[index--]=arr[p2--];}for (int i = 0; i < help.length; i++) {arr[L+i]=help[i];}return ans;
}

测试代码:

// for test
public static int test(int[] arr) {int ans = 0;for (int i = 0; i < arr.length; i++) {for (int j = i + 1; j < arr.length; j++) {if (arr[i] > arr[j]) {ans++;}}}return ans;
}

测试结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YrJznnb5-1685098557571)(F:\typora插图\image-20230526181737428.png)]

4.大于其2倍右侧数的个数

题意:在一个数组中,对于任何一个数num,求有多少个(后面的数*2)依然<num,返回总个数

暴力方法:O(N^2)
归并排序方法:O(NlogN)

解题思路:

  • 这部分判断逻辑需要单独判断,否则会污染数据或者越界,不容易处理。
  • 本质上来说,归并排序从左向右从右向左均可

核心代码:

public static int reversePairs(int[] arr){if(arr==null||arr.length<2){return 0;}return process(arr,0,arr.length-1);
}private static int process(int[] arr, int L, int R) {if(L==R){return 0;}int mid=L+((R-L)>>1);return process(arr,L,mid)+process(arr,mid+1,R)+merge(arr,L,mid,R);
}private static int merge(int[] arr, int L, int M, int R) {int ans=0;//[L,M],[M+1,R]//符合要求的是[M+1,p)int p=M+1;//遍历左组for (int i = L; i <= M; i++) {while(p<=R&&(long)arr[i]>(long)arr[p]*2){p++;}ans+=p-M-1;}int[] help=new int[R-L+1];int index=help.length-1;int p1=M;int p2=R;while(p1>=L&&p2>=M+1){help[index--]=arr[p1]<=arr[p2]?arr[p2--]:arr[p1--];}while(p1>=L){help[index--]=arr[p1--];}while(p2>=M+1){help[index--]=arr[p2--];}for (int i = 0; i < help.length; i++) {arr[L+i]=help[i];}return ans;
}

测试代码:

public static int test(int[] arr) {int ans = 0;for (int i = 0; i < arr.length; i++) {for (int j = i + 1; j < arr.length; j++) {if (arr[i] > (arr[j] << 1)) {ans++;}}}return ans;
}

测试结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qxsM0lci-1685098557571)(F:\typora插图\image-20230526184057230.png)]

归并算法总结

算法描述:

基于分治法的一种排序算法,将全序列拆分成子序列,使子序列有序后,再进行合并得到全有序的序列。

复杂度分析及算法评价:

  • 时间复杂度:O(nlogn)
  • 空间复杂度:O(logn)
  • 稳定的算法

改写方法:

  • 抓住指针不回退,单调的即技巧
  • 流程像——顺序决策,与大小有关

例题总结:

  • 归并排序递归实现:
  • 归并排序非递归实现:判断左组存在,判断右组存在,滚成整数最大值,循环停不下来
  • 小和问题:原则——右组拷贝不产生小和,左组拷贝产生小和,相等时先拷贝右组。先计算再拷贝
  • 逆序数对问题:从右向左拷贝;拷贝原则只与左右方向有关,等号斟酌,计算永远在拷贝之前。
  • 左大右的2倍问题:左右无关;可能有数组下标越界风险,单独处理;复制之前的merge记得去掉ans

http://www.ppmy.cn/news/90531.html

相关文章

nodejs连接mysql

npm i express #node后端框架npm i corsnpm i mysqlconst app require(express)(); const cors require(cors); const port 5000; const mysql require(mysql) //引入mysql 模块app.use(cors({}))const conn mysql.createConnection({user: root,password: qwertyuiop…

4.Ansible Inventory介绍及实战 - A list or group of lists nodes

什么是inventory&#xff1f; 官方解释&#xff1a;Ansible automates tasks on managed nodes or “hosts” in your infrastructure, using a list or group of lists known as inventory. Ansible可以同时与您基础设施中的一个或多个系统协同工作&#xff61;为了与多台服务…

EasyRecovery16绿色版安装下载及使用教程

如果你已经在下载了PC版本的EasyRecovery&#xff0c;那么该如何安装EasyRecovery呢&#xff1f;现在就呈上EasyRecovery教程&#xff0c;以便顺利完成安装。EasyRecovery不仅能够恢复多种类型的数据&#xff0c;更能够适用于不同媒体介质&#xff0c;其中包括计算机&#xff0…

总结MySQL 的一些知识点:MySQL 连接的使用

MySQL 连接的使用 在前几章节中&#xff0c;我们已经学会了如何在一张表中读取数据&#xff0c;这是相对简单的&#xff0c;但是在真正的应用中经常需要从多个数据表中读取数据。 本章节我们将向大家介绍如何使用 MySQL 的 JOIN 在两个或多个表中查询数据。 你可以在 SELECT…

麓言信息 学习UI设计师有没有前途

而目前市场上的UI设计师大多从美工、平面设计师等转岗&#xff0c;自身缺乏系统的Ui设计培训和学习&#xff0c;很难适应当前企业的高规格要求&#xff0c;也导致了一名合格的Ui设计师在社会上是十分值钱、十分抢手的。现在越来越多的年轻群体已经把求职的目光聚焦在UI设计领域…

List<Model> distinct 不起作用

在对 List<Model> 使用 Distinct() 方法时&#xff0c;如果没有得到预期的结果&#xff0c;可能是由于你的 Model 类型没有正确重写 Equals() 和 GetHashCode() 方法所导致的。 当调用 Distinct() 方法时&#xff0c;它使用对象的哈希码和相等性来确定哪些元素是独特的。…

四.Retrofit

文章目录 前言1.如何封装OkHttp2.Retrofit的设计思想3.ServiceMethod存在的价值4.Retrofit的整理流程5.Retrofit使用到了哪些设计模式面试题 前言 核心思想就是AOP思想&#xff0c;面向切面编程。 AOP使用场景比如&#xff1a;LeakCanary、BlockCanary、Matrix、LifeCycle、Ok…

GMesh网格选项介绍

GMesh网格介绍 2D mesh algorithm MeshAdapt&#xff1a;这是一种自适应网格算法&#xff0c;可在需要更大的精度或在某些区域需要更密集的网格时自动添加额外的网格。该算法的优点包括较高的收敛性和灵活性&#xff0c;它可以让用户在需要的地方添加更多的网格&#xff0c;但…