赞
踩
一、归并排序的理解
本质而言可以看作二叉树遍历的一种应用。
二、merge操作
merge方法用于将已经排好序的左右两边进行整理和合并。
假设要被排序的数组被分成了很多个小块, 在进行当前递归步骤之前,每一个小块上的元素已经是有序的了,需要通过merge操作将两个已经排号序的小块整理为一整块有序部分。
这里的merge操作在非递归实现和递归实现中是通用的。
// public static void merge(int[] arr, int L, int M, int R){ int[] help = new int[R - L + 1]; // 设置一个长度为L到R元素个数的辅助数组help int i = 0; // 为help使用所准备的指针 int p1 = L; // 设置一个左指针从左往右移动 int p2 = M + 1; // 设置一个右指针从右往左移动 while (p1 <= M && p2 <= R){ // 当左右指针都没有越界时 // help中放入p1或p2指针所对应的元素中教小的那个,i以及对应指针p1或p2移动一位 help[i++] = arr[p1] <= arr[p2] ? arr[p1++]:arr[p2++]; } // 上面的while循环终止的条件是,要么p1越界,要么p2越界 // p1,p2不可能都越界,所以下面两个while循环只会有一个执行 // 然后将没越界的那个要走的剩余部分放入help中 while (p1 <= M){ help[i++] = arr[p1++]; } while (p2 <= R){ help[i++] = arr[p2++]; } // 然后将help数组中的内容重新刷入arr中 for (i = 0; i < help.length; i++){ arr[L + i] = help[i]; } }
三、递归实现
public class Code01_MergeSort { // 递归方法实现 public static void mergeSort1(int[] arr){ if(arr == null || arr.length < 2){ return; } process(arr, 0, arr.length - 1); } // 递归函数的定义:让arr在[L..R]范围上, 变成有序的 public static void process(int[] arr, int L, int R) { if (L == R){ // base case: 当范围上只有一个数时停止划分 return; } int mid = L + ((R - L) >> 1); // 如果范围上不止一个数, 先选出中点 process(arr, L, mid); process(arr, mid+1, R); merge(arr, L, mid, R); } }
四、非递归(迭代)实现
用迭代的方法进行归并排序的话,可以理解为,自初始的有序长度1开始,不断对已经有序的左组和长度位置,未经排序的右组进行合并,在合并的过程中左右组会一起变得有序,每完成一次进行合并的区域长度倍增。
public static void mergeSort2(int[] arr){ if(arr == null || arr.length < 2){ return; } int N = arr.length; // 如果设置组长度K,应从2开始 int mergeSize = 1; // 当前有序的,左组长度,从1开始 while(mergeSize < N){ // log N int L = 0; // L用于枚举第N个左组的位置从哪里开始,一次归并完成会扩大mergeSize回到下标0位置重新开始新一轮归并 // 0 - .. while(L < N){ // L .. M 当前的左组,其长度为mergeSize int M = L + mergeSize - 1; // M的位置可以直接算出 if (M >= N){ // 当前左组的右端位置已经到达arr末端,可以不用再排序了 break; } // L..M M+1..R(mergeSize) int R = Math.min(M + mergeSize, N - 1);// 右组右端的位置取决于剩余元素数量够不够和左组一样长 merge(arr, L, M, R); L = R + 1;// 一次左右组归并完成,将L移动至R+1位置 } // 加上下面这一句是为了防止溢出:可能会有未知的错误, 例如超过Int类型最大值(21亿左右) if (mergeSize > N / 2){ // 判断2 * mergeSize是否大于arr长度 break; } mergeSize <<= 1; //每次完成一组排序后如果组长度仍然不比arr长度大,mergeSize*2 } }
五、总结
递归操作的时间复杂度公式:
若递归操作符合: T ( N ) = a ∗ T ( N b ) + O ( N d ) T(N) = a*T(\frac{N}{b}) + O(N^d) T(N)=a∗T(bN)+O(Nd)
d 表示除了递归调用之外剩余所有行为的时间复杂度。
计算时间复杂度: T ( N ) = 2 ∗ T ( N 2 ) + O ( N ) T(N) = 2*T({\frac{N}{2}}) + O(N) T(N)=2∗T(2N)+O(N)
尾项为merge的时间复杂度,merge的时间复杂度为
O
(
N
)
O(N)
O(N),所以对于递归的归并排序而言,d=1。
两个2的来源是“二分等规模的递归操作”。
l
o
g
b
a
=
l
o
g
2
2
=
d
=
1
log_b^a = log_2^2 = d= 1
logba=log22=d=1 所以时间复杂度为:
O
(
N
∗
l
o
g
N
)
O(N*log^N)
O(N∗logN)
归并排序的时间复杂度为
O
(
N
∗
l
o
g
N
)
O(N*log^N)
O(N∗logN)。
归并排序相较于冒泡排序、选择排序和插入排序的时间复杂度 O ( N 2 ) O(N^2) O(N2),其优势在于把比较的结果作为有效信息固定了下来 ,而上面的三种排序,在一次遍历的比较操作中只有一次是有效的。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。