概念 :归并算法是将两个或者两个以上的 有序列表 合并成一个新的有序列表,即把待排序列分为若干个子序列,每个子序列都是有序的,然后把子序列合并成整体有序列表。若将两个有序表合并成一个有序表,称为二路归并。
算法思想:归并排序(mergeSort)是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略(分治法将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之)
步骤:
- 分解:将序列每次折半拆分
分组次数:log2^n
- 合并:将划分后的序列段两两排序合并
合并次数:o(n)
算法描述:
- 将序列每相邻的两个数字进行归并操作,形成n/2个子序列,排序后每个子序列包含两个元素。
- 再讲子序列的相邻两个序列进行归并,形成n/4个子序列。
- 直到所有元素排序完毕,排序结束。
方法:递归方法
public class MergeSort {
//测试
public static void main(String[] args) {
int[] arr=new int[] {5,4,3,6,2,8,7,1};
mergeSort(arr,0,arr.length-1);
for(int a:arr){
System.out.print(a+" ");
}
}
//并归排序
public static int[] mergeSort(int[] arr, int start,int end) {
if(start<end){
int mid=(start+end)/2;//划分排序的子序列个数
mergeSort(arr,start,mid);//对左侧子序列进行排序
mergeSort(arr,mid+1,end);//对右侧子序列进行排
merge(arr,start,mid,end);//合并
}
return arr;
}
/*
left左边有序列的作用值
right右边有序列的索引值
mid:中间索引(分割数组的元素)
*/
public static void merge(int[] arr, int left, int mid, int right) {
int[] tmp=new int[arr.length];//中转数组
int p1=left;//左边的有序序列的初始索引
int p2=mid+1;//右边有序序列的初始索引
int k=left;//k是存放指针
while(p1<=mid&&p2<=right){//把两个合并的子序列里面较小的数放前边,大的数放后边
if(arr[p1]<=arr[p2]){
tmp[k++]=arr[p1++];
}else{
tmp[k++]=arr[p2++];
}
}
while(p1<=mid)//左边的有序序列还有剩余的元素,把没有排完的数直接追加到tmp后边
tmp[k++]=arr[p1++];
while(p2<=right)//右边的有序序列还有剩余的元素,把没有排完的数直接追加到tmp后边
tmp[k++]=arr[p2++];
for(int i=left;i<=right;i++){//把临时数组tmp里的内容赋给arr
arr[i]=tmp[i];
}
}
}
优化:
1.当元素个数比较小时,调用直接插入排序
2.当左边数组最大元素都小于右边数组的最小元素,说明整个数组有序,排序结束
public static void mergeSort2(int[] array,int low,int high) {
// 优化一:数据比较少时,采用直接 插入排序
if (high - low <= 15) {
InsertSort.insertSort1(array,array.length);
return;
}
int mid = (low + high) / 2;
// 左边小数组
mergeSort2(array,low,mid);
// 右边小数组
mergeSort2(array,mid+1,high);
// 合并 ---->优化二:当左边元素的最大值比右边元素的最小值都da大时,说明整个数组有序,直接借宿排序
if (array[mid] > array[mid+1]) {
merge(array,low,mid,high);
}
}
/*
p: 开始位置:左边数组的开始位置
q:中间位置:右边数组的开始位置
r:结束位置
*/
private static void merge2(int[] array,int p,int mid,int r) {
int i = p;//左边的开始索引
int j = mid+ 1;//右边的开始索引
int[] temp = new int[r-p+1];//k开辟一个临时数组
int k = 0;//新数组的下标
// 此时两个数组中均有元素
while (i <= mid && j <= r) {
if (array[i] <= array[j]) {
// 第一个数组中的相同位置元素最小
temp[k++] = array[i++];
}else {
temp[k++] = array[j++];
}
}
// 判断当前还有哪个数组元素没有走完
int start = i;
int end = mid;
//剩下第二个数组
if (j <= r) {
start = j;
end = r;
}
// 把剩余元素直接放置在temp数组后即可
while (start <= end) {
temp[k++] = array[start++];
}
// 将临时空间中已经合并好的元素拷贝回原数组
for (i = 0;i < r-p+1;i++) {
array[p+i] = temp[i];
}
}
直接插入排序的 代码在这里:https://blog.csdn.net/qq_40955824/article/details/89556522
时间复杂度:
空间复杂度:临时数组在合并后空间会释放
稳定性:取决于合并函数的写法,arr[p1]<=arr[jp2
归并排序是稳定排序,它也是一种十分高效的排序,能利用完全二叉树特性的排序一般性能都不会太差。java中Arrays.sort()采用了一种名为TimSort的排序算法,就是归并排序的优化版本。每次合并操作的平均时间复杂度为O(n),而完全二叉树的深度为|log2n|。总的平均时间复杂度为O(nlogn)。而且,归并排序的最好,最坏,平均时间复杂度均为O(nlogn)。