前言
归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。《百度百科》
1. 归并排序
归并过程为:比较 a[i] 和 b[j] 的大小,若 a[i] ≤ b[j],则将第一个有序表中的元素 a[i] 复制到 r[k] 中,并令 i 和 k 分别加上 1;否则将第二个有序表中的元素 b[j] 复制到 r[k] 中,并令 j 和 k 分别加 上1,如此循环下去,直到其中一个有序表取完,然后再将另一个有序表中剩余的元素复制到r中从下标 k 到下标 t 的单元。归并排序的算法我们通常用递归实现,先把待排序区间 [s,t] 以中点二分,接着把左边子区间排序,再把右边子区间排序,最后把左区间和右区间用一次归并操作合并成有序的区间 [s,t]。
原理:
归并操作的工作原理如下:
第一步:申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
第二步:设定两个指针,最初位置分别为两个已经排序序列的起始位置
第三步:比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
重复步骤3直到某一指针超出序列尾
将另一序列剩下的所有元素直接复制到合并序列尾
总结:
- 将两个已排好序的数组合并成一个有序的数组,称之为归并排序
- 步骤:遍历两个数组,比较它们的值。谁比较小,谁先放入大数组中,直到数组遍历完成
1. 演算归并排序过程
现在我有两个已经排好顺序的数组:int[] arr1 = {2, 7, 8}和int[] arr2 = {1, 4, 9},我还有一个大数组来装载它们int[] arr = new int[6];
1.1 第一轮
那么,我将两个数组的值进行比较,谁的值比较小,谁就放入大数组中!
首先,拿出 arr1[0] 和 arr2[0] 进行比较,显然是 arr2[0] 比较小,因此将 arr2[0] 放入大数组中,同时 arr2 指针往后一格。
所以,现在目前为止arr = {1}。
1.2 第二轮
随后,拿 arr1[0] 和 arr2[1] 进行比较,显然是 arr1[0] 比较小,将 arr1[0] 放入大数组中,同时 arr1 指针往后一格。
所以,现在目前为止arr = {1,2}。
1.3 第三轮
随后,拿 arr1[1] 和 arr2[1] 进行比较,显然是 arr2[1] 比较小,将 arr2[1] 放入大数组中,同时 arr2 指针往后一格。
所以,现在目前为止 arr = {1,2,4}。
……..
遍历到最后,我们会将两个已排好序的数组变成一个已排好序的数组 arr = {1,2,4,7,8,9}。
2. 归并排序前提分析(分治法)
从上面的演算我们就直到,归并排序的前提是需要两个已经排好顺序的数组,那往往不会有两个已经排好顺序的数组给我们的呀(一般是杂乱无章的一个数组),那这个算法是不是很鸡肋的呢??
其实并不是的,首先假设题目给出的数组是这样子的:int[] arr = {2, 7, 8, 1, 4, 9};
当我们要做归并的时候就以 arr[3] 也就元素为 1的 那个地方分开。是然后用一个指针 L 指向 arr[0],一个指针 M 指向 arr[3],用一个指针 R 指向 arr5。有了指针的帮助,我们就可以将这个数组切割成是两个有序的数组了(操作的方式就可以和上面一样了)。
可是上面说了,一般给出的是杂乱无章的一个数组,现在还是达不到要求。比如给出的是这样一个数组:int[] arrays = {9, 2, 5, 1, 3, 2, 9, 5, 2, 1, 8};
此时,我们就得用到分治的思想了:
那么我们也可以这样想将 int[] arr = {2, 7, 8, 1, 4, 9};数组分隔成一份一份的,arr[0] 它是一个有序的"数组",arr[1] 它也是一个有序的"数组",利用指针 (L,M,R) 又可以像操作两个数组一样进行排序。最终合成{2,7}…….再不断拆分合并,最后又回到了我们的arr = {1,2,4,7,8,9},因此归并排序是可以排序杂乱无章的数组的。
这就是我们的分治法--->将一个大问题分成很多个小问题进行解决,最后重新组合起来。
3. 归并代码实现
实现步骤:
- 拆分
- 合并
/**
* 归并排序
* 分解待排序的数组成两个各具 n/2 个元素的子数组,递归调用归并排序两个子数组,合并两个已排序的子数组成一个已排序的数组
*/
public class MergeSort {
public static void main(String[] args) {
int[] sort = SortManager.sortArr;
int[] temp = new int[sort.length];
mergeSort(sort, temp, 0, sort.length - 1);
for (int i = 0; i < sort.length; i++) {
System.out.println("输出结果:" + sort[i]);
}
}
/**
* 归并排序
*
* @param arrays
* @param L 指向数组第一个元素
* @param R 指向数组最后一个元素
*/
public static void mergeSort(int[] arrays, int[] temp, int L, int R) {
// 当left == right时,不需要再划分
if (L < R) {
//取中间的数,进行拆分
int M = (L + R) / 2;
//左边的数不断拆分
mergeSort(arrays, temp, L, M);
//右边的数不断拆分
mergeSort(arrays, temp, M + 1, R);
//合并
merge(arrays, temp, L, M, R);
}
}
/**
* 合并数组
*
* @param arrays
* @param L 指向数组第一个元素
* @param M 指向数组分隔的元素
* @param R 指向数组最后的元素
*/
private static void merge(int[] arrays, int[] temp, int L, int M, int R) {
int i = L, j = M + 1;
// arrays数组的第一个元素
int k = 0;
//比较这两个数组的值,哪个小,就往数组上放
while (i <= M && j <= R) {
temp[k++] = arrays[i] < arrays[j] ? arrays[i++] : arrays[j++];
}
//如果左边的数组还没比较完,右边的数都已经完了,那么将左边的数抄到大数组中(剩下的都是大数字)
while (i <= M) {
temp[k++] = arrays[i++];
}
//如果右边的数组还没比较完,左边的数都已经完了,那么将右边的数抄到大数组中(剩下的都是大数字)
while (j <= R) {
temp[k++] = arrays[j++];
}
// 把temp数据复制回原数组
for (i = 0; i < k; i++) {
arrays[L + i] = temp[i];
}
}
}
申明:开始的图片来源网络,侵删