归并排序
自顶向下的归并排序(递归)
public class MergeSort {//从小到大排序
public static void mergeSort(int[] array) {
if (array == null || array.length <= 1) {
return;
}
int[] temp = new int[array.length];//临时数组储存排序后的元素
sort(array, 0, array.length - 1, temp);
}
public static void sort(int[] array, int start, int end, int[] temp) {
if (start < end) {//头尾索引相等就不再分割
int mid = (start + end) / 2;
//分割为左右子序列
sort(array, start, mid, temp);//左子序列从start到mid
sort(array, mid + 1, end, temp);//右子序列从mid+1到end
merge(array, start, mid, end, temp);//排序并合并左右子序列
}
}
public static void merge(int[] array, int start, int mid, int end, int[] temp) {
int left = start;//左子序列指针,遍历start到mid
int right = mid + 1;//右子序列指针,遍历mid+1到end
int index = 0;//temp数组索引
while (left <= mid && right <= end) {//左右子序列遍历比较,排序放入temp数组
if (array[left] <= array[right]) {
temp[index++] = array[left++];
} else {//等价于array[left] > array[right]
temp[index++] = array[right++];
}
}//直到其中一方子序列遍历比较完毕,退出循环
while (left <= mid) {//剩余左子序列,顺序放入temp数组
temp[index++] = array[left++];
}
while (right <= end) {//剩余右子序列,顺序放入temp数组
temp[index++] = array[right++];
}
index = 0;//temp数组索引重置为0
while (start <= end) {//将已排序的元素,从temp拷贝到array
array[start++] = temp[index++];
}
}
}
对于长度为 N 的任意数组
1、自顶向下的归并排序需要 N / 2 * lgN 至 N * lgN 次比较
2、自顶向下的归并排序最多需要访问数组 6 * N * lgN 次
(1)每次归并最多需要访问数组 6 * N 次(2 * N 次用来复制,2 * N 次用来将排好序的元素移动回去,另外最多比较 2 * N 次)
(2)根据 1 即可得到 2 的结果
3、归并排序所需的时间和 N * lgN 成正比
4、归并排序的主要缺点是辅助数组所使用的额外空间和 N 的大小成正比
优化
1、对小规模子数组使用插入排序
(1)用不同的方法处理小规模问题能改进大多数递归算法的性能,因为递归会使小规模问题中方法的调用过于频繁
(2)插入排序非常简单,因此很可能在小数组上比归并排序更快
2、测试数组是否已经有序
(1)若 a[mid] <= a[mid+1],就认为数组已经是有序的,并跳过 merge 方法,即子数组 a 末尾值 <= 子数组 b 开头值,不需要进行排序
(2)这个改动不影响排序的递归调用,但是任意有序的子数组算法的运行时间就变为线性的
3、不将元素复制到辅助数组
(1)可以节省将数组元素复制到用于归并的辅助数组所用的时间(但空间不行)
(2)需要调用两种排序方法,一种将数据从输入数组排序到辅助数组,一种将数据从辅助数组排序到输入数组
(3)关键:在排序前拷贝一个和原数组元素完全一样的辅助数组(不再是创建一个空数组);在递归调用的每个层次交换输入数组和输出数组的角色
4、 将辅助数组作为 mergeSort 方法的局部变量(上方代码已实现)
(1)不把辅助数组声明为 merge() 方法的局部变量,避免每次归并时,都创建一个新的数组。如果这么做,那么创建新数组将成为归并排序运行时间的主要部分
(2)不把辅助数组声明为 static,因为可能会有多个程序同时使用这个类
自底向上的归并排序(循环)
1、思想
(1)先归并那些微型数组,然后再成对归并得到的子数组,直到将整个数组归并在一起,这种实现方法比标准递归方法所需要的代码量更少
(2)首先进行的是两两归并(把每个元素想象成一个大小为 1 的数组),然后是四四归并(将两个大小为 2 的数组归并成一个有 4 个元素的数组),然后是八八的归并,一直下去
(3)在每一轮归并中,最后一次归并的第二个子数组可能比第一个子数组要小,如果不是,则所有的归并中两个数组大小都应该一样,而在下一轮中子数组的大小会翻倍
2、对于长度为 N 的任意数组
(1)自底向上的归并排序需要 N / 2 * lgN 至 N * lgN 次比较
(2)自底向上的归并排序最多访问数组 6 * N * lgN 次
3、当数组长度为 2 的幂时,自顶向下和自底向上的归并排序所用的比较次数和数组访问次数正好相同,只是顺序不同
4、自底向上的归并排序比较适合用链表组织的数据
(1)将链表先按大小为 1 的子链表进行排序,然后是大小为 2 的子链表,然后是大小为 4 的子链表等
(2)这种方法只需要重新组织链表链接就能将链表原地排序,不需要创建任何新的链表结点
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战