归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
算法过程:
归并排序的过程其实只分为两步:拆分与合并
拆分:
- 假设有一个大小为n的序列,每次将该序列分隔成相等的两部分(序列大小为基数时前半部分比后半部分多一个元素),例如第一次划分,可以将一个[0,n-1]的序列划分为:[0,(n-1)/2]和[(n-1)/2+1,n-1]两部分,第二次划分再将其分为:[0,((n-1)/2)/2],[((n-1)/2)/2+1,(n-1)/2],[(n-1)/2+1,((n-1)/2+1+n-1)/2]和[((n-1)/2+1+n-1)/2+1,n-1]…
其实就是每次找到这个待划分序列的首部low和尾部high,取中间值mid = (low+high)/2,最终划分的到的两个序列分别为:[low,mid]和[mid+1,high]; - 层层递归之后,最终得到的子序列的low与high相等,即序列中只有一个元素时,停止划分,开始合并;
- 要注意的是,归并排序的划分并不是一次性将所有的序列都划分成最小子序列后在开始合并,而是从左到,先划分出两个可以合并的最小子序列,将其合并,再向右寻找划分,再合并,再划分,再合并······
拆分过程:
合并:
- 归并排序的合并中,因为待合并的两个子序列内部都是有序的,所以归并排序的合并过程实际上就是从两个子序列小端(即首部)开始每次比较两个序列前面的元素,将较小的存到临时数组的前面。
- 先创建一个临时数组,与待排序数组大小一致,用来存放临时排序好的值;
- 定义的第二个待排序区间的最小值(即首元素)索引为low2=mid+1,此时第一个数组为[low,mid],第二个数组为[low2,high];
- 在low<mid和low2<high的条件下,比较arr[low1]与arr[low2]的值,将较小的一个放入数组前面,然后该索引向后移动一位,继续进行比较。
- 若步骤4中low或low2递增到不满足low<mid和low2<high,此时一个子序列已全部放入数组,此时将另一个序列中剩下的数依次放入数组中接下来的位置;
- 最后,将临时数组中的元素按照先后次序依次放入原数组中,这一部分的排序就完成了;
- 继续分割,合并其他子序列,直到合并最后两个子序列,归并排序完成。
合并过程:
代码:
import java.util.Arrays;
import java.util.Random;
/***
* 归并排序(递归)
* 时间复杂度:O(nlog2n)
* 空间复杂度:O(n)
* 稳定性:稳定
*/
public class MyMergeSort {
public static void main(String[] args) {
int[] arr =new int[100];
Random random = new Random();
for (int i = 0; i < arr.length; i++) {
arr[i] = random.nextInt(100)+1;
}
System.out.println("原数组为:\n" + Arrays.toString(arr));
MyMergeSort(arr, 0, arr.length - 1);
System.out.println("归并排序后的数组为:\n" + Arrays.toString(arr));
for (int j = 0; j < arr.length - 1; j++) {
if (arr[j] > arr[j + 1]) {
System.out.println("false!");
}
}
}
public static void MyMergeSort(int[] arr,int low,int high){
if(low >= high){
return;
}
int mid = (low+high)/2;
MyMergeSort(arr,low,mid);
MyMergeSort(arr,mid+1,high);
//分隔完成,已经变成一个一个有序的子序列
//开始合并
merge(arr,low,mid,high);
}
//合并函数
public static void merge(int[] arr,int low,int mid,int high){
int[] tmpArr = new int[arr.length];
int tmpIndex= low;
int low2 = mid+1;
int i = low;
//保证两个子序列中都有数据
while(low<=mid && low2<=high){
if(arr[low]<=arr[low2]){
tmpArr[tmpIndex++] = arr[low++];
}
if(arr[low2]<=arr[low]){
tmpArr[tmpIndex++] = arr[low2++];
}
}
while(low<=mid){
tmpArr[tmpIndex++] = arr[low++];
}
while(low2<=high){
tmpArr[tmpIndex++] = arr[low2++];
}
while(i<=high){
arr[i] = tmpArr[i];
i++;
}
}
}
结果展示:
算法分析:
时间复杂度:
归并排序法n个数据一般需要log2n次处理,每次处理的时间复杂度为O(n),且归并排序的执⾏效率与要排序的原始数组的有序程度⽆关,所以归并排序的最佳,最坏和平均情况的时间复杂度都为:O(nlog2n)。
空间复杂度:
在排序过程中需要一个与原始文件相同大小的额外空间,所以其空间复杂度为:O(n)。
稳定性:
归并排序法在排序的过程中,值相同的元素,在合并前后的先后顺序不变。所以,归
并排序是⼀个稳定的排序算法。