归并排序
归并排序(Merge Sort)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
算法描述
- 把长度为n的输入序列分成两个长度为n/2的子序列;
- 对这两个子序列分别采用归并排序;
- 将两个排序好的子序列合并成一个最终的排序序列。
动画演示:
需求:
排序前:{8,4,5,7,1,3,6,2}
排序后:{1,2,3,4,5,6,7,8}
排序原理:
1、尽可能的一组数据拆分成两个元素相等的子组,并对每一个子组继续拆分,直到拆分后的每个子组的元素个数是1为止。
2、将相邻的两个子组进行合并成一个有序的大组;
3、不断的重复步骤2,直到最终只有一个组为止。
方法介绍:
1、sort(int[] arr)
- 传入排序数组
- 初始化辅助数组
- 定义最大,最小索引值
- 递归调用重载方法
private static void sort(int[] arr){
//1、初始化辅助数组assist;
assist = new int[arr.length];
//2、定义一个minIndex变量和maxIndex变量,分别记录数组中最小的索引和最大的索引
int minIndex = 0;
int maxIndex = arr.length-1;
//3、调用sort重载方法完成数组a中,从索引minIndex到索引maxIndex的元素的排序
sort(arr, minIndex, maxIndex);
}
2、sort(int[] arr, int minIndex, int maxIndex)
- sort[int[] arr] 的重载方法,也是真正的排序方法
- 分组
- 对每一组进行排序
- 归并
private static void sort(int[] arr, int minIndex, int maxIndex){
//做安全性校验
if(minIndex>=maxIndex){
return;
}
//对minIndex到maxIndex之间的数据进行2个分组
int midIndex = minIndex+(maxIndex-minIndex)/2;
//分别对每一组数据进行排序
sort(arr,minIndex,midIndex);
sort(arr,midIndex+1,maxIndex);
//再把两个组中的数据进行归并
merge(arr, minIndex, midIndex, maxIndex);
}
3、merge(int[] arr, int minIndex, int midIndex, int maxIndex)
- 归并(算法的核心部分)
private static void merge(int[] arr, int minIndex, int midIndex, int maxIndex){
//定义三个指针
int i = minIndex;
int p1 = minIndex;
int p2 = midIndex+1;
//遍历,移动p1,p2指针,比较对应位置索引处的值,找出小的那个,放到辅助数组的对应处索引
while(p1<=midIndex && p2<=maxIndex){
//比较对应索引处的值
if(arr[p1]<arr[p2]){
assist[i++] = arr[p1++];
}else {
assist[i++] = arr[p2++];
}
}
//如果p1的指针没有走完,那么顺序移动p1指针,把对应的元素放在辅助数组的对应索引处
while (p1<=midIndex){
assist[i++] = arr[p1++];
}
//如果p2的指针没有走完,那么顺序移动p2指针,把对应的元素放在辅助数组的对应索引处
while (p2<=maxIndex){
assist[i++] = arr[p2++];
}
//把辅助数组中的元素copy到元素组中
for (int j = minIndex;j<=maxIndex;j++){
arr[j] = assist[j];
}
}
前提:
sort(int[] arr, int minIndex, int maxIndex)被执行
图片转自黑马程序员
完整java代码:
public class MergeSort {
//辅助数组
private static int[] assist;
public static void main(String[] args) {
int[] arr = {5,1,2,7,6,3,4,8};
sort(arr);
System.out.println(Arrays.toString(arr));
}
/*
对数组a中的元素进行排序
*/
private static void sort(int[] arr){
//1、初始化辅助数组assist;
assist = new int[arr.length];
//2、定义一个minIndex变量和maxIndex变量,分别记录数组中最小的索引和最大的索引
int minIndex = 0;
int maxIndex = arr.length-1;
//3、调用sort重载方法完成数组a中,从索引minIndex到索引maxIndex的元素的排序
sort(arr, minIndex, maxIndex);
}
/*
对数组a中从minIndex到maxIndex元素进行排序
*/
private static void sort(int[] arr, int minIndex, int maxIndex){
//做安全性校验
if(minIndex>=maxIndex){
return;
}
//对minIndex到maxIndex之间的数据进行2个分组
int midIndex = minIndex+(maxIndex-minIndex)/2;
//分别对每一组数据进行排序
sort(arr,minIndex,midIndex);
sort(arr,midIndex+1,maxIndex);
//再把两个组中的数据进行归并
merge(arr, minIndex, midIndex, maxIndex);
}
/*
对数组中minIndex到midIndex为一组,midIndex+1到maxIndex为一组,对两组数据进行归并
*/
private static void merge(int[] arr, int minIndex, int midIndex, int maxIndex){
//定义三个指针
int i = minIndex;
int p1 = minIndex;
int p2 = midIndex+1;
//遍历,移动p1,p2指针,比较对应位置索引处的值,找出小的那个,放到辅助数组的对应处索引
while(p1<=midIndex && p2<=maxIndex){
//比较对应索引处的值
if(arr[p1]<arr[p2]){
assist[i++] = arr[p1++];
}else {
assist[i++] = arr[p2++];
}
}
//如果p1的指针没有走完,那么顺序移动p1指针,把对应的元素放在辅助数组的对应索引处
while (p1<=midIndex){
assist[i++] = arr[p1++];
}
//如果p2的指针没有走完,那么顺序移动p2指针,把对应的元素放在辅助数组的对应索引处
while (p2<=maxIndex){
assist[i++] = arr[p2++];
}
//把辅助数组中的元素copy到元素组中
for (int j = minIndex;j<=maxIndex;j++){
arr[j] = assist[j];
}
}
}
算法分析
归并排序时间复杂度分析:
归并排序是分治思想的最典型的例子,是一种稳定的排序方法。和选择排序一样,归并排序的性能不受输入数据的影响,但表现比选择排序好的多
上面的算法中,对arr[minIndex…maxIndex]进行排序,先将它分为arr[minIndex…midIndex]和a[midIndex+1…maxIndex]两部分,分别通过递归调用将他们单独排序,最后将有序的子数组归并为最终的排序结果。该递归的出口在于如果一个数组不能再被分为两个子数组,那么就会执行merge进行归并,在归并的时候判断元素的大小进行排序。
终归并排序的时间复杂度为O(nlogn);
希尔排序和归并排序在处理大批量数据时差别不是很大
归并排序的缺点:
需要申请额外的数组空间,导致空间复杂度提升,是典型的以空间换时间的操作。