一、简介
归并排序(Merge Sort)是建立在归并操作上的一种有效,稳定的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
二、基本思想
先“分”再“治”。
先将数组分割,再进行有序合并。
三、实例
第一个例子:
参数及含义解释:
arr : 排序的原始数组
left :左边有序序列的初始索引
mid :中间索引
这个mid 在仅有8个元素时,第一次(0+7) / 2 = 3,即为其最初的下标。所以,mid 就表示左边有序序列的最后一个元素。mid + 1,就表示右边有序序列的第一个元素。
right :右边索引
temp: 做中转的数组
“分”的步骤1:mid = (0+7)/2 = 3,分成了下标left ~ mid和(mid+1) ~ right。即下标0 ~ 3(8,4,5,7) 和 4 ~ 7(1,3,6,2)。
“分”的步骤2:下标0 ~ 3 ,mid = (0 + 3)/2 = 1,分成了8,4和5,7。
“分”的步骤3:mid = (0 + 1)/2 = 0,0 ~ 0 ,分成了:8和4。此时已经递归到最里层了。这时就触发后面的“治”(此处略)。
以最后一次“治”为例:
图1:i 指向数字4,j 指向数字1,temp为临时数组。
“治”的步骤1:先把左右两边(有序)的数据,按照规则填充到temp 数组。直到左右两边的有序序列,有一边处理完毕为止。
1)4和1分别是有序子序列的首元素,二者比较大小。4>1,将1填充进temp 数组。并将j 后移。
2)4和2进行比较,2<4,将2填充进temp,并将j 后移。
3)4和3进行比较,3<4,将3填充进temp,并将j 后移。
图2:
4)4和6进行比较,4<6,填充进tmep,并将 i 后移。
5)5和6比较,5<6,填充进tmep,并将 i 后移。
“治”的步骤2:把有剩余数据的一边的数据依次全填充到temp。
7和6比较,6<7,填充进temp,并将 i 后移。此时,右侧有序列已经没有元素了。接下来,把有剩余数据的一边的数据(7,8)依次全填充到 temp。
“治”的步骤3:将tmep数组的元素拷贝到arr(并不是每次都拷贝所有)。
第二个例子:
序列元素个数为奇数时,亦同理
输出结果:
分的时候,递归到最里层时,完成分割。之后开始从最里层往外面合。
进入递归,被分开的元素序列中,例如5,3,7被分成了5,3和7。再深入一层把5,3分成了5和3。这就直捣黄龙,递归到底了。开始合!把5和3合成3,5。再退回到上一层递归(5,3和7),对这俩序列进行合并!得到结果:3,5,7。其他的类似。
先深入,再出来,这便是递归的奥义吧。(~ ̄▽ ̄)~
【分、合(递归)全过程】:5,3,7,2,0分为5,3,7和2,0。进入左子树把5,3,7分为5,3和7。再进入左子树分为5和3。触发合并语句:最先把3,5给合了。回溯,再把3,5,7给合了。这时,进入右子树2,0,把2,0分为2和0,触发合并语句,合成了0,2。回溯,合并根节点的左右子树,触发合并语句,合成了0,2,3,5,7。程序执行结束~
代码:
package com.huey.sort;
import java.util.Arrays;
public class MergeSort {
public static void main(String[] args) {
int[] arr = {
8, 4, 5, 7, 1, 3, 6, 2 };
int[] temp = new int[arr.length];// 归并排序需要一个额外空间
mergeSort(arr, 0, arr.length - 1, temp);
System.out.println("归并排序后:" + Arrays.toString(arr));
}
// 分+合方法
public static void mergeSort(int[] arr, int left, int right, int[] temp) {
if (left < right) {
// mid 在递归时就是right.mid+1就是left
int mid = (left + right) / 2;// 中间索引
// 向左递归 进行分解
mergeSort(arr, left, mid, temp);
// 向右递归 进行分解
mergeSort(arr, mid + 1, right, temp);
// 到合并【8,4,5,7是栈顶,先合并】
merge(arr, left, mid, right, temp);
}
}
// 合并的方法
/**
* @param arr 排序的原始数组
* @param left 左边有序序列的初始索引
* @param mid 中间索引
* @param right 右边索引
* @param temp 做中转的数组
*/
public static void merge(int[] arr, int left, int mid, int right, int[] temp) {
int i = left;// 初始化i,左边有序序列的初始索引
int j = mid + 1;// 初始化j,右边有序序列的初始索引
int t = 0;// 指向temp数组的当前索引
// (一)
// 先把左右两边(有序)的数据,按照规则填充到temp 数组
// 直到左右两边的有序序列,有一边处理完毕为止
while (i <= mid && j <= right) {
// 继续
// 如果左边的有序序列的当前元素,小于等于右边有序序列的当前元素
// 即将左边的当前元素,填充到temp数组
// 然后t++,i++
if (arr[i] <= arr[j]) {
temp[t] = arr[i];
t += 1;
i += 1;
} else {
// 反之
temp[t] = arr[j];
t += 1;
j += 1;
}
}
// (二)
// 把有剩余数据的一边的数据依次全填充到temp
while (i <= mid) {
// 左边的有序序列还有剩余的元素,就全部填充到temp
temp[t] = arr[i];
t += 1;
i += 1;
}
while (j <= right) {
// 右边的有序序列还有剩余的元素,就全部填充到temp
temp[t] = arr[j];
t += 1;
j += 1;
}
// (三)
// 将tmep数组的元素拷贝到arr
// 并不是每次都拷贝所有
t = 0;
int tempLeft = left;//
System.out.println("tempLeft = " + tempLeft + ", right = " + right);
while (tempLeft <= right) {
// 第一次合并 tempLeft = 0,right = 1// tempLeft = 2,right = 3 // t= 0,r= 3.......
// 【最后一次】tempLeft = 0,right = 7
arr[tempLeft] = temp[t];
t += 1;
tempLeft += 1;
}
}
}
输出结果:
速度测试:
八千万:
大致在11~12s.