归并排序
-
时间复杂度O(nlogn)
-
空间复杂度O(n)
-
稳定
-
思路
- 使用递归的方式不断切分子区间,当区间长度小于一定值后,对小区间采取插入排序,插入排序在小区间,基本有序的情况下的性能十分优异
-
写代码的时候还是有很多坑的,思路要时刻保持清醒,特别是在确定数组索引的时候`,一定要清楚地知道想干神魔
//基础版 public static void main(String[] args) { int [] nums={8,6,5,9,88,4,4,2,6,5,7,1}; int left=0; int right=nums.length-1; mergeSort(nums,left,right); System.out.println(Arrays.toString(nums)); } private static void mergeSort(int[] nums, int left, int right) { //当left=right时,子区间只存在一个元素,递归终止 //当left>right,子区间越界,递归终止 if(left>=right){ return ; } //防止大整形溢出 int mid=left+(right-left)/2; //将数组一分为而,递归地进行下去 mergeSort(nums,left,mid); mergeSort(nums,mid+1,right); //来到这一个就是划分的两个子数组已经有序,现在要做的就是将两个有序的数组合并成一个有序的数组 mergeTwoSortedArray(nums,left,mid,right); } private static void mergeTwoSortedArray(int[] nums, int left,int mid, int right) { //毫无疑问,需要额外的空间来完成这一合并过程 int len=right-left+1; int [] temp=new int[len]; //将元素拷贝到新数组中保存元素,后续直接对原数组元素进行操作 for (int i = 0; i < len; i++) { temp[i]=nums[left+i]; } //需要将在nums的索引映射到从零开始,减去left即可 int l=0; int r=mid-left+1; for (int i = 0; i < len; i++) { //这里可能会懵逼 //当其中一个子区间的元素用尽了.那么从另一个区间来拿就好了,这是前两个if //剩下就需要比较一下,拿小的那一个元素就好 if(l>mid-left){ nums[i+left]=temp[r]; r++; }else if(r>len-1){ nums[i+left]=temp[l]; l++; }else if(temp[l]>temp[r]){ nums[i+left]=temp[r]; r++; }else { nums[i+left]=temp[l]; l++; } } }
优化点
1. 每次合并时都要开辟新的数组,影响性能
2. 如果要合并的区间已经有序(前一个区间的最大值小于后一个区间的最小值)就不需要对其进行合并
3. 当区间的长度小于某个值时,对其进行插入排序
//优化版
private static final int INSERTION_SORT_THRESHOLD = 6;
public static void main(String[] args) {
int[] nums = {8, 6, 5, 9, 88, 4, 4, 2, 6, 5, 7, 1};
int left = 0;
int right = nums.length - 1;
//使用同一份临时数组,避免每次给次归并创建新数组的开销
int[] temp = new int[nums.length];
mergeSort(nums, left, right, temp);
System.out.println(Arrays.toString(nums));
}
private static void mergeSort(int[] nums, int left, int right, int[] temp) {
//当区间长度小于某个值以后采用插入排序
if (right - left <= INSERTION_SORT_THRESHOLD) {
insertionSort(nums, left, right);
return;
}
//避免整形溢出
int mid = left + (right - left) / 2;
mergeSort(nums, left, mid, temp);
mergeSort(nums, mid + 1, right, temp);
//如果数组已经有序,就不用合并了
if (nums[mid] <= nums[mid + 1]) {
return;
}
mergeTwoSortedArray(nums, left, mid, right, temp);
}
private static void mergeTwoSortedArray(int[] nums, int left, int mid, int right, int[] temp) {
//其实这里我开始犯傻想把这个动作,放到程序开头,直接复制一个不就好了嘛,免得每次复制一次多麻烦
//你觉得我想的有道理吗,你可以帮帮我嘛(老猿警告)
//答案当然不行,你必须复制的是两个排好序的子区间,而不是开始的无序数组
System.arraycopy(nums, left, temp, left, right - left + 1);
int l = left;
int r = mid + 1;
for (int i = left; i <= right; i++) {
if (l == mid + 1) {
nums[i] = temp[r];
r++;
} else if (r == right + 1) {
nums[i] = temp[l];
l++;
} else if (temp[l] <= temp[r]) {//这里的等号关乎稳定性,不要忘记了 优先往数组放前面的元素,保持稳定性
nums[i] = temp[l];
l++;
} else {
nums[i] = temp[r];
r++;
}
}
}
private static void insertionSort(int[] nums, int left, int right) {
for (int i = left + 1; i <= right; i++) {
int temp = nums[i];
int j = i;
while (j > left && nums[j - 1] > temp) {
nums[j] = nums[j - 1];
j--;
}
nums[j] = temp;
}
}