归并排序(二路归并排序)是一个高效的排序算法,仅次于快速排序。
思想:将元素从中间一直往下切分,直到分解成1个元素(不能再分时)就开始将数组进行合并排序。
一直往下二分,不能再分时,再把所有的二分合并并且排序。
核心在于二分,还有就是合并时如何进行排序。
实现:二分使用递归,合并排序实际上是将两个有序序列进行排序,解决这两个问题,代码就没有任何问题了。
时间复杂度:O(nlogn)
空间复杂度:O(N) (使用了一个同等大小的临时数组进行排序)
稳定性:稳定
假设数组为{9,5,8,6,3,0,7,1}
下图是我实现的Java代码的执行过程:
(箭头是java程序一直往下执行的顺序。忽略箭头,从上往下连起来,就是归并排序的总体思想)
Java代码实现
package org.example.sort;
import java.util.Arrays;
import java.util.Random;
/**
* 归并排序
*
* @author Lee Xxn
* @date 2019-12-13
*/
public class MergeSort {
/**
* 归并排序,传入数组,返回排好序的数组
*
* @date 2019/12/13
* @param arr 待排序数组
* @return int[] 排好序的数组
*/
public static int [] sort(int [] arr){
/*归并排序,首先是要把待排序的序列分段,一直分到不能分为止
由于每个分段都需要被记录并且在最后归并时所有的分段需要分别排序,因此需要记录每个分段的起始位置和结束位置,在这里使用递归特别方便*/
//开始进行分段并且排序
splitAndMergeSort(arr,0,arr.length -1);
return arr;
}
/**
* 递归分段及归并
*
* @date 2019/12/13
* @param arr 数组
* @param startIndex 分段开始索引
* @param endIndex 分段结束索引
*/
public static void splitAndMergeSort(int [] arr,int startIndex,int endIndex){
//使用递归,当分到不能再分的时候,结束递归
if(endIndex > startIndex){
//可以分段,则将数组从中间分成两段
int mid = (startIndex + endIndex) / 2;
splitAndMergeSort(arr,startIndex,mid);
splitAndMergeSort(arr,mid +1 ,endIndex);
//不能再分的时候,将数组合并
mergeSort(arr,startIndex,mid,endIndex);
}
}
/**
* 合并并且排序
*
* @date 2019/12/13
* @param arr 数组
* @param leftStart 左段起始位置
* @param split 切分位置
* @param rightEnd 右段结束位置
*/
public static void mergeSort(int [] arr,int leftStart,int split,int rightEnd){
//合并排序,将左段数组和右段数组排序,插入到一个临时数组,再把临时数组拷贝到最终数组中,即把左右两段排好序返回
//由于从1个数开始合并分段,并且返回的都是已经排好序的结果,所以最终是两段排好序的序列进行排序
//因此排序只需要一个循环即可,左右两端依次对比到末尾,对比结果插入到临时数组,再把剩下的序列拷贝到临时数组,就把两个排好序的序列排序完成。
int [] temp = new int [arr.length];//使用临时数组来存放排好序的序列,
int tempStart = leftStart;//记录数组有意义的开始位置
int rightStart = split + 1;//右段的起始位置
int point = leftStart;//操作临时数组的指针
//循环,左右两端依次从头进行比对(头与头对比,哪边满足条件,哪边插入到临时数组,然后往后移一位,只要有一端先移完,就可以终止循环,再比对已经没有意义了,只需要拷贝移动即可)
while(leftStart <= split && rightStart <= rightEnd){
//升序
if(arr[leftStart]< arr[rightStart]){
temp[point++] = arr[leftStart++];
}else{
temp[point++] = arr[rightStart++];
}
}
//看看左右两段哪边没有移完,如果没有移完,说明还有剩下的序列,进行拷贝移动
while (leftStart <= split){
temp[point++] = arr[leftStart++];
}
while (rightStart <= rightEnd){
temp[point++] = arr[rightStart++];
}
//然后将有意义的数组拷贝到原数组中
for(int x = tempStart; x <= rightEnd;x++){
arr[x] = temp[x];
}
}
/**
* main 方法,用于验证算法是否正确
*
*/
public static void main(String [] args){
int genArrNumber = 10;//生成10个测试的数组
int genArrSize = 10;//每个数组长度是10
for(int i = 0;i < genArrNumber;i++) {
int[] arr = new int[genArrSize];
for (int x = 0; x < genArrSize; x++) {
arr[x] = new Random().nextInt(100);
}
System.out.println("随机生成的" + genArrSize + "个长度的数组" + Arrays.toString(arr));
System.out.println("归并排序的结果:" + Arrays.toString(sort(arr)));
}
}
}