归并排序是建立在归并操作上的一种有效的排序算法该算法是采用分治法的一个非常典型的应用。将已有序的子
序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序
表,称为二路归并。
需求
排序前:{8,4,5,7,1,3,6,2}
排序后:{1,2,3,4,5,6,7,8}
排序原理
1.尽可能的一组数据拆分成两个元素相等的子组,并对每一个子组继续拆分,直到拆分后的每个子组的元素个数是
1为止。
2.将相邻的两个子组进行合并成一个有序的大组;
3.不断的重复步骤2,直到最终只有一个组为止。
归并排序API设计
归并排序代码实现
import java.util.Arrays;
/**
* @author: ChengLong
* @datetime: 2021/5/24 16:35
*/
public class Demo05Merge {
public static void main(String[] args) {
Integer[] arr = {8,4,5,7,1,3,6,2};
sort(arr);
System.out.println(Arrays.toString(arr));
}
// 完成归并操作需要的辅助数组
private static Comparable[] assist;
/*.private static boolean less(Comparable v,Comparable w):判断v是否小于w
* */
private static boolean less(Comparable v,Comparable w){
return v.compareTo(w) < 0;
}
/*
交换a数组中,索引i和索引j处的值
* */
private static void exch(Comparable[] a,int i,int j){
Comparable temp;
temp = a[i];
a[i] = a[j];
a[j] = temp;
}
/*对数组内的元素进行排序
* */
public static void sort(Comparable[] a){
// 1.初始化辅助数据assist
assist = new Comparable[a.length];
// 2.定义一个lo变量和hi变量,分别记录数据中最小的索引和最大的索引;
int lo=0;
int hi=a.length-1;
// 3.调用sort重载方法完成数组a中,从索引lo到索引hi的元素的排序
sort(a,lo,hi);
}
/*
* 对数组a中从索引lo到索引hi之间的元素进行排序
* */
private static void sort(Comparable[] a, int lo, int hi){
// 做安全性校验
if (hi<=lo){
return;
}
// 对lo到hi之间的数据进行分组
// int mid = lo+(hi-lo)/2;
int mid = (hi+lo)/2;
// 分别对每一组数据进行排序
sort(a,lo,mid);
sort(a,mid+1,hi);
// 再把两个组中的数据进行归并
merge(a,lo,mid,hi);
}
/*
* 从索引lo到所以mid为一个子组,从索引mid+1到索引hi为另一个子组,把数组a中的这两个子组的数据合并成一个有序的大组(从索引lo到索引hi)
* */
private static void merge(Comparable[] a, int lo, int mid, int hi){
// 定义三个指针
int i = lo;
int p1 = lo;
int p2 = mid+1;
// 遍历,移动p1指针和p2指针,比较对应索引的值,找出小的那个,放到辅助数组的对应位置
while (p1<=mid && p2<=hi){
// 比较对应索引处的值
if (less(a[p1],a[p2])){
assist[i++] = a[p1++];
}else {
assist[i++] = a[p2++];
}
}
// 遍历,如果p1的指针没有走完,那么顺序移动p1指针,把对应的元素放到辅助数组的对应索引处
while (p1<=mid){
assist[i++] = a[p1++];
}
// 遍历,如果p2的指针没有走完,那么顺序移动p2指针,把对应的元素放到辅助数组的对应索引处
while (p2<=hi){
assist[i++] = a[p2++];
}
// 把辅助数组中的元素拷贝到原数组中
for (int index = lo;index<=hi;index++){
a[index] = assist[index];
}
}
}
归并排序时间复杂度分析
归并排序是分治思想的最典型的例子,上面的算法中,对a[lo...hi]进行排序,先将它分为a[lo...mid]和a[mid+1...hi]
两部分,分别通过递归调用将他们单独排序,最后将有序的子数组归并为最终的排序结果。该递归的出口在于如果
一个数组不能再被分为两个子数组,那么就会执行merge进行归并,在归并的时候判断元素的大小进行排序。
假设元素的个数为n,那么使用归并排序拆分的次数为log2(n),所以共log2(n)层,那么使用log2(n)替换上面32^3中
的3这个层数,最终得出的归并排序的时间复杂度为:log2(n) 2^(log2(n))=log2(n)*n,根据大O推导法则,忽略底
数,最终归并排序的时间复杂度为O(nlogn)
归并排序的缺点
需要申请额外的数组空间,导致空间复杂度提升,是典型的以空间换时间的操作。