归并排序是稳定排序,它也是一种十分高效的排序,它也是timesort算法的基础,归并排序算法使用到了分治法(教材上一直都这么说,但这属于是一句正确的废话),当然,具体实现的过程中也用到了双指针的思想。网上大部分的教程啰里吧嗦讲了好几页,这里废话不多说,直接说原理:
第一步:把数列中的所有数字“打散”,
第二步:用双指针的思路,再把散掉的数字重新组合在一起。
即先“分”,再“治”(归并)。
现在将5,4,3,6,1,2从小到大排序,上图!!!
首先实现第一步,将数列分成左右两个部分,将数列“打散”,但是“打散”要有一定的套路,逐层从中间分开,最后将所有数字拆出来(棕色圆圈代表索引值):
开始第二步,把散掉的数字组合起来,如图:
通过图我们看到了,打散和合并的过程是对称的,怎么打散的,后来就再怎么合并,只不过在合并的过程中,有一个大小比较的过程,现在的关键问题是:怎么比较?比较后又怎么合并?
这就需要借助到两个工具——双指针和临时数组!
这里以6、1、2这三个数字的合并过程为例。
先创建一个长度为2的临时数组,由于6和1最开始是在一起的,所以我们现在再把它们合并到一起。由于6大于1,所以把它们两个放到一个临时数组中[1,6],然后再创建一个长度为3的临时数组,开始合并[1,6]和[2],那么我们该如何把数字放到这个临时数组的合适位置呢?此时可以借助双指针!一个指针指向1,另一个指针指向2,2比1大,所以把1放到临时数组的第一个位置。
1被取走了,所以现在把黑色指针指到6,红色指针不变。
2比6小,所以把2放到临时数组的第二个位置。还剩一个6,所以把6放到临时数组第3个位置。因此右半部分的数字排序完毕!
我们依然按照相同的思路将5,4,3合并成有序数列:
最后的步骤就是先创建一个长度为6的空数组arr,然后用双指针,分别指向[3,4,5]和[1,2,6]这两个数列中的第一个字,把下面这两个有序数列中的数字放到这个长度为6的空数组里:
- 3大于1,所以在arr[0]的位置写入1,黑色指针不动,红色指针向右移动一位。
- 3大于2,所以在arr[1]的位置写入2,黑色指针不动,红色指针向右移动一位。
- 6大于3,所以在arr[2]的位置写入3,黑色指针向右移动一位,红色指针不动。
- 6大于4,所以在arr[3]的位置写入4,黑色指针向右移动一位,红色指针不动。
- 6大于5,所以在arr[4]的位置写入5,至此,左侧的数字全部“归位”,而右侧剩余的数字(6)直接按顺序放入arr中即可。所有数字排序完毕。
如果一侧的数字全部放入空数组以后,另一侧的数字还有剩余,那么这些数字不用再进行比较,直接按顺序放进空数组即可。
废话不多说!上代码!
// 测试代码
public static void main(String[] args) {
// 测试数组
int[] arr = {4, 3, 2, 0, 7, 41, 6, 5, 3};
// 成功排序后的数组
int[] A = mergeSort(arr, 0, arr.length - 1);
for (int i = 0; i < arr.length; i++) {
System.out.println(A[i]);
}
}
// 归并排序代码
public static int[] mergeSort(int[] arr, int left, int right) {
// 分割到只剩一个数字时,将这个数字放进数组并返回
if (left == right) {
return new int[]{arr[left]};
}
int mid = (left + right) / 2;
int[] Larr = mergeSort(arr, left, mid);
int[] Rarr = mergeSort(arr, mid + 1, right);
int[] temp = new int[Larr.length + Rarr.length];
int i = 0;
int j = 0;
int k = 0;
while (i < Larr.length && j < Rarr.length) {
if (Larr[i] >= Rarr[j]) {
temp[k++] = Rarr[j++];
} else {
temp[k++] = Larr[i++];
}
}
// 下面两个while循环,是将左右两个数组中的剩余数字全部放进临时数组temp中
while (i < Larr.length) {
temp[k++] = Larr[i++];
}
while (j < Rarr.length) {
temp[k++] = Rarr[j++];
}
return temp;
}