经典排序之归并排序详解
@Desciption: 归并排序
归并:把两个或多个有序的序列合并成一个
算法思想:就是先将一个 代排序的数组递归成 一个 一个 的元素 然后在分别两两合并 然后合并完 就成了 一些 有序数组 然后在继续 两两合并这个两个有序数组… 直到合并完毕 那么带排序的就变成了 有序数组了。
算法效率分析:
1、时间复杂度: O(nlog2n)
分析 : 由于此算法为 归并的趟数 * 每次合并元素的数 * 2路“归并树”—形态上为一刻倒立的二叉树 那么 趟数就位 log2n 级别的 * 每次的合并
即当与在二叉树每一层对比某两个有序序列的元素 那么每一层总的对比肯定小于等于n-1 * 那么每一趟的合并时间复杂为O(n)级别的
那么此算法时间复杂度为 O(nlog2n)
2、空间复杂度:O(n),来自于merger方法的辅助数组help[]
分析:由于这里运用了递归的方式实现的,这里的递归栈也使用了一些辅助空间,但是它不会超过log2n级别的 * 而辅助数组是n级别的 因此
最终取高阶 空间复杂度即为O(n)级别的
稳定性:稳定的
分析:在merge方法里合并的时候,如果遇到两个关键字相等的时候,会优先将左边相等的放进辅助数组里 * 因此归并排序是稳定的算法。
动画演示
代码:
package zcy_MergeSort;
import org.junit.Test;
/**
* @author 米兰的小铁匠
* @date 2022/4/10/010 11:31
* @Desciption: 归并排序
* 归并:把两个或多个有序的序列合并成一个
* 算法思想:就是先将一个 代排序的数组递归成 一个 一个 的元素 然后在分别两两合并
* 然后合并完 就成了 一些 有序数组 然后在继续 两两合并这个两个有序数组... 直到合并完毕
* 那么带排序的就变成了 有序数组了。
*
* 算法效率分析:
*
* 1、时间复杂度: O(nlog2n)
* 分析 : 由于此算法为 归并的趟数 * 每次合并元素的数
* 2路“归并树”---形态上为一刻倒立的二叉树 那么 趟数就位 log2n 级别的
* 每次的合并 即当与在二叉树每一层对比某两个有序序列的元素 那么每一层总的对比肯定小于等于n-1
* 那么每一趟的合并时间复杂为O(n)级别的
* 那么此算法时间复杂度为 O(nlog2n)
*
* 2、空间复杂度:O(n),来自于merger方法的辅助数组help[]
* 分析:由于这里运用了递归的方式实现的,这里的递归栈也使用了一些辅助空间,但是它不会超过log2n级别的
* 而辅助数组是n级别的 因此 最终取高阶 空间复杂度即为O(n)级别的
*
* 稳定性:稳定的
* 分析:在merge方法里合并的时候,如果遇到两个关键字相等的时候,会优先将左边相等的放进辅助数组里
* 因此归并排序是稳定的算法。
*/
@SuppressWarnings("all")
public class MergeSort {
//递归方法实现
public static void mergeSort1(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
process(arr, 0, arr.length - 1);
}
public static void process(int arr[], int L, int R) {
//如果递归到 L和R指向一样的 就代表递归结束 返回上一层递归函数。
if (L == R) {
return;
}
int mid = (L + R) >> 1; //mid取中间的,注意这里是向下取整,如果mid=2.5 那么java会向下取整为2
process(arr, L, mid); //对左半部分进行排序
process(arr, mid + 1, R); //对右半部分进行排序
merge(arr, L, mid, R); //归并两个有序序列
}
public static void merge(int arr[], int L, int M, int R) {
int[] help = new int[R - L + 1];//定义辅助数组为两个有序序列的长度-此长度即为R-L+1
int i = 0;
int p1 = L;
int p2 = M + 1;
/*
* 对这两个有序序列进行归并
* 分别从两个有序序列的第一个开始往后比较,谁小就把谁放进辅助数组,然后往后移动
* 当移动到一个有序序列越界时,就代表一个有序序列已经整个放进辅助数组里面了。
* 然后while循环结束
* */
while (p1 <= M && p2 <= R) {
/*
* 这里 假如如果左边的小于等于右边的就把左边放进辅助数组里面去,否则放右边的
* 也就是 只有当右边小于左边的,才把右边的放进辅助数组里面去。
* 这样就保证了排序的稳定性
* */
help[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
}
//如果p1小于等于M,说明P1没有完全放进辅助数组,而P2完全放进去了,那么就把P1剩下的放进辅助数组
while (p1 <= M) {
help[i++] = arr[p1++];
}
//如果p2小于等于R,说明P2没有完全放进辅助数组,而P1完全放进去了,那么就把P2剩下的放进辅助数组
while (p2 <= R) {
help[i++] = arr[p2++];
}
//最后将辅助数组元素全部复制到arr数组里面去
for (i = 0; i < help.length; i++) {
arr[L + i] = help[i];
}
}
}
测试:
//测试
@Test
public void test() {
int[] arr = {1,4,6,2,7,43,2,0,21};
System.out.println("-------排序前---------");
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println("");
System.out.println("-------排序后---------");
mergeSort1(arr);
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
}
运行结果: