归并排序(merge sort)
- 具体算法:
- I.对原数组进行分组:对数组进行遍历,每检测出一个有序序列则记录一个分组,一般分组都是上升序列,下降序列也会被转换成上升序列
- II.对两两相邻的分组进行合并,合并后的分组也将被记录
- III.迭代合并之前合并后的分组直到出现最后的一个有序的大分组,也就是排序的最终结果
java.util.DualPivotQuicksort类中的static void sort(int[] a, int left, int right,int[] work, int workBase, int workLen)方法:在对数组进行快排前,先判断该数组是否适合merge sort(条件:),如果适合则使用merger sort。
//判断数组长度是否小于快排阙值,是则使用快排
if (right - left < QUICKSORT_THRESHOLD) {
sort(a, left, right, true);
return;
}
//值得注意的是:run[count]=k应该是上升有序序列分组终止元素的下一个元素的索引号
for (int k = left; k < right; run[count] = k) {
if (a[k] < a[k + 1]) { // ascending
//确定上升序列起止
while (++k <= right && a[k - 1] <= a[k]);
} else if (a[k] > a[k + 1]) { // descending
//确定下降序列起止
while (++k <= right && a[k - 1] >= a[k]);
//将下降序列转换成上升序列
for (int lo = run[count] - 1, hi = k; ++lo < --hi; ) {
int t = a[lo]; a[lo] = a[hi]; a[hi] = t;
}
} else {
//如果相等序列分组长度等于MAX_RUN_LENGTH则对数组进行快排并结束方法
for (int m = MAX_RUN_LENGTH; ++k <= right && a[k - 1] == a[k]; ) {
if (--m == 0) {
sort(a, left, right, true);
return;
}
}
}
//如果有序序列分组数等于MAX_RUN_LENGTH则对数组进行快排并结束方法
if (++count == MAX_RUN_COUNT) {
sort(a, left, right, true);
return;
}
}
//如果最后一个序列分组的终止索引号为right,则新增一个只含right的序列分组。
if (run[count] == right++) { // The last run contains one element
run[++count] = right;
//如果最后一个序列分组的终止索引为1则表明该数组已经有序,无需排序,结束方法。
} else if (count == 1) { // The array is already sorted
return;
}
//n=2^m,此处将数组体积分布函数分为两支,如果m为奇数则odd为1,表明该数组在较大的一支,如果m为偶数则odd为0,表明该数组在较小的一支。
byte odd = 0;
for (int n = 1; (n <<= 1) < count; odd ^= 1);
//声明一个数组来当作排序结果容器数组,也就是来放置最后序列正确的元素。
int[] b; // temp array; alternates with a
int ao, bo; // array offsets from 'left'
int blen = right - left; // space needed for b
//允许传入非空数组当作排序结果容器数组,如果传入的是空数组或是传入数组不够大则创建一个数组。
if (work == null || workLen < blen || workBase + blen > work.length) {
work = new int[blen];
workBase = 0;
}
//较小的数组可以使用arraycopy函数将需要排序的序列直接复制到结果容器数组上
if (odd == 0) {
System.arraycopy(a, left, work, workBase, blen);
b = a;
bo = 0;
a = work;
ao = workBase - left;//此处需要减left的原因:因为排序循环中用到的a[q+ao]中q并非是从left开始索引的,而是从0开始,即q=left+len(left,q),因此在此处需要减去才能得到a中与run记录索引号相对应的元素。
} else {
//较大的数组只能在原数组上操作
b = work;
ao = 0;
bo = workBase - left;//此处减left原因同ao
}
for (int last; count > 1; count = last) {
//归并排序:
//对两个相邻的上升有序序列分组进行两两排序,p、i是前一个分组的起始索引号,q、mi是后一个分组的起始索引号,hi是后一个分组的终止索引号。p,q在原数组上向右移动,i,mi,hi在排序结果容器数组上向右移动,last是这些分组排序形成的新分组的终止索引号
//如果p < mi && a[p] <= a[q]则将a[p]放入b[i],否则a[q]放入b[i]。最后两个分组合并成一个有序的分组,将该分组的终止索引号放在last加一的位置。
for (int k = (last = 0) + 2; k <= count; k += 2) {
int hi = run[k], mi = run[k - 1];
for (int i = run[k - 2], p = i, q = mi; i < hi; ++i) {
if (q >= hi || p < mi && a[p + ao] <= a[q + ao]) {
b[i + bo] = a[p++ + ao];
} else {
b[i + bo] = a[q++ + ao];
}
}
run[++last] = hi;
}
//如果分组数是单数,则最后一个分组将无分组与其排序,因此只将其直接放入结果容器数组中,到最后肯定只剩下前面所有分组排成的一个新大分组与这最后一个分组进行排列。
if ((count & 1) != 0) {
for (int i = right, lo = run[count - 1]; --i >= lo;
b[i + bo] = a[i + ao]
);
run[++last] = right;
}
int[] t = a; a = b; b = t;
int o = ao; ao = bo; bo = o;//交换a,b原因:因为排序后b放置了下一次排序的分组,角色转换成本次排序中a担任的角色,因此需要交换
}