归并排序
在这里先向大家推荐一个学习数据结构与算法的一个网站,关于我的许多数据结构与算法的知识也是在这里学习的,里面也讲解了许多力扣的习题.欢迎大家一起来学习.
网站链接: 算法吧
排序算法的稳定性
如果相等的元素在排序以后的位置保持不变,这样的排序算法就是稳定的排序算法.
如果只是针对数值类型,谈论稳定性没有意义,稳定性针对有多个属性的对象类型而言,不稳定的排序算法对于数值相同的元素在排序前后的相对位置关系是不确定的.
常用的排序算法中插入排序,冒泡排序,归并排序是稳定的排序算法.
归并排序简介
归并排序与快速排序都是基于**「分治思想」**的排序算法。
分治思想(分而治之)把一个规模较大的问题拆分成为若干个规模较小的相同类型的子问题,然后对这些子问题递归求解,等待每一个子问题完成以后,再得到原问题的解;
分治算法可以并行执行,但是在基础算法领域,分治算法是以 深度优先遍历 的方式执行的。
归并排序的基本思想
归并排序的基本思想是「分治算法」,整个过程就是拆分问题与组合子问题的解,因此我们需要递归来完成.
合并两个有序数组是归并排序的子过程,我们如何合并两个有序数组呢?
我们来看力扣第88题:
题目链接: 88. 合并两个有序数组
参考代码:
class Solution {
public void merge(int[] nums1, int m, int[] nums2, int n) {
// 把 nums1 的有效个元素复制到 nums 里面
int []nums = new int[m];
for(int i = 0;i < m ; i++){
nums[i] = nums1[i];
}
// 从前向后归并,比较 nums[i] 和 nums2[j] 的元素哪个小,谁小谁出列,覆盖 nums1
int i = 0;
int j = 0;
int k = 0;
while(i < m && j < n){
if(nums[i] <= nums2[j]){
nums1[k] = nums[i];
i++;
}else{
nums1[k] = nums2[j];
j++;
}
k++;
}
while(i < m){
nums1[k++] = nums[i++];
}
while(j < n){
nums1[k++] = nums2[j++];
}
}
}
归并排序的基本实现
代码示例:
public class MergeSort {
public static int[] sortArray(int[] nums) {
int len = nums.length;
mergeSort(nums, 0, len - 1);
return nums;
}
//对nums[left...right] 进行归并排序
public static void mergeSort(int[] nums, int left, int right) {
int mid = (left + right) / 2;
if (left < right) {
//左边拆分
mergeSort(nums, left, mid);
//右边拆分
mergeSort(nums, mid + 1, right);
//左右归并
merge(nums, left, mid, right);
}
}
//将nums[left,mid] 与 nums[mid+1,right] 合并到temp数组中
private static void merge(int[] nums, int left, int mid, int right) {
int[] temp = new int[right - left + 1];
int i = left; // 左指针
int j = mid + 1; // 右指针
int k = 0; //k指针指向temp数组
// 把较小的数先移到新数组中
while (i <= mid && j <= right) {
if (nums[i] <= nums[j]) {
temp[k++] = nums[i++];
} else {
temp[k++] = nums[j++];
}
}
// 把左边剩余的数移入数组
while (i <= mid) {
temp[k++] = nums[i++];
}
// 把右边边剩余的数移入数组
while (j <= right) {
temp[k++] = nums[j++];
}
for (int r = 0; r < temp.length; r++) {
nums[r + left] = temp[r];
}
}
//代码测试
public static void main(String[] args) {
int a[] = {51, 46, 20, 18, 65, 97, 82, 30, 89, 88, 77, 50};
sortArray(a);
System.out.println(Arrays.toString(a));
}
}
归并排序的优化
优化的方向
优化 1 :
在「小区间」里转向使用「插入排序」,Java 源码里面也有类似这种操作,「小区间」的长度是个超参数,需要测试决定,我这里参考了 JDK 源码,选择了 7。
优化 2 :
在「两个数组」本身就是有序的情况下,无需合并。
让我们来看一下优化后的代码:
import java.util.Arrays;
public class aa {
public static int[] sortArray(int[] nums) {
int len = nums.length;
mergeSort(nums, 0, len - 1);
return nums;
}
public static void mergeSort(int[] nums, int left, int right) {
int mid = (left + right) / 2;
if (left < right) {
//优化1 : 当区间小于7时,直接使用插入排序;
if (right - left < 7) {
insertionSort(nums, left, right);
return;
}
mergeSort(nums, left, mid);
mergeSort(nums, mid + 1, right);
//优化二:在「两个数组」本身就是有序的情况下,无需合并,直接返回;
if (nums[mid] <= nums[mid + 1]) {
return;
}
merge(nums, left, mid, right);
}
}
// 插入排序
private static void insertionSort(int[] nums, int left, int right) {
for (int i = left + 1; i <= right; i++) {
int temp = nums[i];
int j;
for (j = i; j > left; j--) {
if (nums[j - 1] > temp) {
nums[j] = nums[j - 1];
} else {
break;
}
}
nums[j] = temp;
}
}
private static void merge(int[] nums, int left, int mid, int right) {
int[] temp = new int[right - left + 1];
int i = left;
int j = mid + 1;
int k = 0;
while (i <= mid && j <= right) {
if (nums[i] <= nums[j]) {
temp[k++] = nums[i++];
} else {
temp[k++] = nums[j++];
}
}
while (i <= mid) {
temp[k++] = nums[i++];
}
while (j <= right) {
temp[k++] = nums[j++];
}
for (int r = 0; r < temp.length; r++) {
nums[r + left] = temp[r];
}
}
public static void main(String[] args) {
int a[] = {-74, 48, -20, 2, 10, -84, -5, -9, 11, -24, -91, 2, -71, 64, 63, 80};
sortArray(a);
System.out.println(Arrays.toString(a));
}
}