核心思想
归并排序的核心思想是,对两个有序的集合进行排序。
实现步骤,如下:
1.有序集合a
2.有序集合b
3.对集合a和集合b进行归并排序,排序之后放入集合c,并且集合c有序。
代码
有2种实现方案,
1.递归
2.非递归
递归
归并排序可以实现对2个有序的集合进行排序,并且把排序之后的数据放到第3个集合。代码如下。
package sort.mergeSort;
/**
*
*
* 类的名称:
* 类的作用:归并排序
* 1.2个有序的集合
* 2.放入第三个集合并且仍然有序
*
*
* @author gzh
* @date 2017年11月28日下午6:07:47
*/
public class MergeSortNotRecursion {
public static void main(String[] args) {
// 定义数据
int[] a = { 1, 3, 5, 7, 9 };
int[] b = { 2, 4, 6, 8, 10 };
int[] c = new int[10];
// 排序
mergeSort(a, a.length, b, b.length, c);
// 打印数据
outData(c);
}
/**
*
*
* 归并排序
*
*
* @param a 第一个集合
* @param sizeA 第一个集合的大小
* @param b 第二个集合
* @param sizeB 第二个集合的大小
* @param c 第三个集合
* @author gzh
* @date 2017年11月28日下午6:13:04
*/
private static void mergeSort(int[] a, int sizeA, int[] b, int sizeB,
int[] c) {
// 遍历,直到遍历完集合a或集合b
int indexA = 0, indexB = 0, indexC = 0;
while (indexA < sizeA && indexB < sizeB) {
if (a[indexA] < b[indexB]) {
c[indexC++] = a[indexA++];
} else {
c[indexC++] = b[indexB++];
}
}
// 如果集合a没有遍历完,继续遍历a
while (indexA < sizeA) {
c[indexC++] = a[indexA++];
}
// 如果集合b没有遍历完,继续遍历b
while (indexB < sizeB) {
c[indexC++] = b[indexB++];
}
}
/**
*
*
* 打印数据
*
*
* @param c
* @author gzh
* @date 2017年11月28日下午6:10:36
*/
private static void outData(int[] c) {
for (int i = 0; i < c.length; i++) {
System.out.print(c[i] + " ");
}
}
}
日志
1 2 3 4 5 6 7 8 9 10
但是,实际编程的时候,一般是要求对一个集合进行排序。
实现的核心,还是归并排序算法。
不过,这里的归并排序算法,需要稍微修改一下,因为刚才上面的算法是对2个排序的集合进行排序,然后再放入第3个集合,总共涉及到3个集合。
修改之后的归并排序算法,是直接对待排序的集合进行排序,也就是说,只涉及到一个集合。具体实现细节,基本一样。有一点需要注意的是,在归并排序类的内部,也就是排序算法使用到了一个临时集合。
最后总结一下,所谓归并排序的递归实现,就是递归调用归并排序方法。代码如下。
package sort.mergeSort;
/**
*
*
* 类的名称:
* 类的作用:归并排序(递归实现)
*
*
* @author gzh
* @date 2017年11月29日上午9:44:23
*/
public class MergeSortRecursion {
// 定义数据
private static int[] a = { 9, 1, 3, 2, 4, 0, 7};
public static void main(String[] args) {
// 排序
int[] temp = new int[a.length];
mergeSortRecursion(temp, 0, a.length - 1);
}
private static int space = 0;
/**
*
*
* 递归调用归并排序
*
*
* @param temp
* 临时集合
* @param lowerBound
* 当前递归的起始位置
* @param upperBound
* 当前递归的结束位置
* @author gzh
* @date 2017年11月29日上午9:57:00
*/
private static void mergeSortRecursion(int[] temp, int lowerBound,
int upperBound) {
if (space!=0) { //第一次调用时,不需要打印空格
outSpace(++space);
}
System.out.print("当前递归开始:" + lowerBound + "~" + upperBound);
outData(lowerBound,upperBound);
if (lowerBound == upperBound) { // 如果只有一个元素,递归结束
outSpace(space);
System.out.print("当前递归结束(只有一个元素,递归结束):" + lowerBound + "~" + upperBound);
outData(lowerBound,upperBound);
return;
} else { // 否则,递归调用合并排序
int middle = (lowerBound + upperBound) / 2;
outSpace(++space);
System.out.print("排序左边:" + lowerBound + "~" + middle);
outData(lowerBound,middle);
mergeSortRecursion(temp, lowerBound, middle);
outSpace(--space);
System.out.print("排序右边:" + (middle + 1) + "~" + upperBound);
outData((middle + 1),upperBound);
mergeSortRecursion(temp, middle + 1, upperBound);
mergeSort(temp, lowerBound, middle, middle + 1, upperBound);
space-=2;
outSpace(space);
System.out.print("当前递归结束:" + lowerBound + "~" + upperBound);
outData(lowerBound,upperBound);
}
}
/**
*
*
* 输出指定范围的元素
*
* @param lowerBound 开始位置
* @param upperBound 结束位置
* @author gzh
* @date 2017年11月29日下午2:04:12
*/
private static void outData(int lowerBound, int upperBound) {
System.out.print("——");
for (int i = lowerBound; i <= upperBound; i++) {
System.out.print(a[i] + " ");
}
System.out.println();
}
/**
*
*
* 打印空格
*
* @param n 空格数量
* @author gzh
* @date 2017年11月29日下午12:59:27
*/
private static void outSpace(int n) {
for (int i = 0; i < n; i++) {
System.out.print(" ");
}
}
/**
*
*
* 归并排序
*
*
* @param temp
* 临时集合
* @param firstStart
* 第一个集合的起始位置
* @param firstEnd 第一个集合的结束位置
* @param indexSecond
* 第二个集合的起始位置
* @param secondEnd
* 第二个集合的结束位置
* @author gzh
* @date 2017年11月29日上午10:01:47
*/
private static void mergeSort(int temp[], int firstStart, int firstEnd,
int indexSecond, int secondEnd) {
//保存临时值
int indexFirstTemp = firstStart;
//计算排序元素的数量
int n = secondEnd-firstStart + 1;
int sizeFirst = indexSecond - 1; // 计算第一个集合的大小
// 遍历,直到遍历完集合a或集合b
int i = 0;
while (firstStart <= sizeFirst && indexSecond <= secondEnd) {
if (a[firstStart] < a[indexSecond]) {
temp[i++] = a[firstStart++];
} else {
temp[i++] = a[indexSecond++];
}
}
// 如果集合a没有遍历完,继续遍历
while (firstStart <= sizeFirst) {
temp[i++] = a[firstStart++];
}
// 如果集合b没有遍历完,继续遍历
while (indexSecond <= secondEnd) {
temp[i++] = a[indexSecond++];
}
// 复制数据到原始集合
for (i = 0; i < n; i++) {
a[indexFirstTemp+i] = temp[i];
}
}
/**
*
*
* 打印数据
*
*
* @param c 集合
* @author gzh
* @date 2017年11月28日下午6:10:36
*/
private static void outData(int[] c) {
for (int i = 0; i < c.length; i++) {
System.out.print(c[i] + " ");
}
}
}
日志
当前递归开始:0~6——9 1 3 2 4 0 7
排序左边:0~3——9 1 3 2
当前递归开始:0~3——9 1 3 2
排序左边:0~1——9 1
当前递归开始:0~1——9 1
排序左边:0~0——9
当前递归开始:0~0——9
当前递归结束(只有一个元素,递归结束):0~0——9
排序右边:1~1——1
当前递归开始:1~1——1
当前递归结束(只有一个元素,递归结束):1~1——1
当前递归结束:0~1——1 9
排序右边:2~3——3 2
当前递归开始:2~3——3 2
排序左边:2~2——3
当前递归开始:2~2——3
当前递归结束(只有一个元素,递归结束):2~2——3
排序右边:3~3——2
当前递归开始:3~3——2
当前递归结束(只有一个元素,递归结束):3~3——2
当前递归结束:2~3——2 3
当前递归结束:0~3——1 2 3 9
排序右边:4~6——4 0 7
当前递归开始:4~6——4 0 7
排序左边:4~5——4 0
当前递归开始:4~5——4 0
排序左边:4~4——4
当前递归开始:4~4——4
当前递归结束(只有一个元素,递归结束):4~4——4
排序右边:5~5——0
当前递归开始:5~5——0
当前递归结束(只有一个元素,递归结束):5~5——0
当前递归结束:4~5——0 4
排序右边:6~6——7
当前递归开始:6~6——7
当前递归结束(只有一个元素,递归结束):6~6——7
当前递归结束:4~6——0 4 7
当前递归结束:0~6——0 1 2 3 4 7 9
非递归
上面介绍的递归实现,设计思路是:
1.合并排序算法
2.递归调用合并排序算法
非递归实现,设计思路一样,也是:
1.合并排序算法
2.非递归调用合并排序算法
重点来了,递归实现和非递归的实现,相同点都是基于合并排序算法,不同的地方只是在于怎么去调用合并排序算法。
递归调用,就是递归方法不断地调用自己,递归结束的标志是当前待排序的集合只有一个元素。
非递归调用,就是采用循环遍历的思想(即迭代),去不断地调用合并排序算法,迭代结束的标志是当前待排序的集合大小等于待排序集合的大小。
package sort.mergeSort;
import java.util.Arrays;
/**
*
*
* 类的名称:
* 类的作用:归并排序(非递归实现,即采用多层遍历的思想)
*
*
* @author gzh
* @date 2017年11月30日上午9:53:56
*/
public class MergeSortNotRecursion {
private static int[] a = { 9, 1, 3, 2, 4};
public static void main(String[] args) {
merge_sort(a);
outData(a);
}
/**
*
*
* 归并排序(非递归实现,即采用多层遍历的思想)
*
* @param arr 待排序的集合
* @author gzh
* @date 2017年11月30日上午9:59:15
*/
public static void merge_sort(int[] arr) {
int len = arr.length;
int[] result = new int[len];
int block, start;
int low,mid,high=0;
// 原版代码的迭代次数少了一次,没有考虑到奇数列数组的情况
for (block = 1; block < len * 2; block *= 2) {
for (start = 0; start < len; start += 2 * block) {
low = start;
mid = (start + block) < len ? (start + block) : len;
high = (start + 2 * block) < len ? (start + 2 * block)
: len;
// 两个块的起始下标及结束下标
int start1 = low, end1 = mid;
int start2 = mid, end2 = high;
//排序范围
System.out.print("当前排序开始:" + start + "~" + (high-1)); //排序范围
outData(arr,start,(high-1)); //排序元素
//第一个集合
outSpace(1);
System.out.print("第一个集合:" + start1 + "~" + (end1-1)); //排序范围
outData(arr,start1,(end1-1)); //排序元素
//第二个集合
outSpace(1);
System.out.print("第二个集合:" + start2 + "~" + (end2-1)); //排序范围
outData(arr,start2,(end2-1)); //排序元素
// 开始对两个block进行归并排序
while (start1 < end1 && start2 < end2) {
// result[low++] = arr[start1] < arr[start2] ? arr[start1++]
// : arr[start2++];
if (arr[start1] < arr[start2]) {
result[low++] = arr[start1++];
} else {
result[low++] = arr[start2++];
}
}
while (start1 < end1) {
result[low++] = arr[start1++];
}
while (start2 < end2) {
result[low++] = arr[start2++];
}
System.out.print("当前排序结束:" + start + "~" + (high-1)); //排序范围
outData(result,start,(high-1)); //排序元素
}
//内循环每一轮排序之后,把当前这一轮的排序结果result复制给待排序的集合arr
// int[] temp = arr;
// arr = result;
// result = temp;
// for (int i = 0; i < result.length; i++) { //System.arraycopy(),相当于是循环赋值,即只复制值,不改变引用地址的值
// arr[i] = result[i];
// }
System.arraycopy(result, 0, arr, 0, result.length); //System.arraycopy()不会改变对象引用arr的值,即arr还是指向方法传入参数时的那个集合对象(即待排序的集合对象)
// arr = Arrays.copyOf(result, result.length); //Arrays.copyOf()排序之后,会改变对象引用arr的值,即arr会重新指向一个新的集合对象——此时,待排序的集合对象和arr已经没有关系
outData(arr);
System.out.println();
System.out.println();
}
}
/**
*
*
* 打印数据
*
*
* @param c
* @author gzh
* @date 2017年11月28日下午6:10:36
*/
private static void outData(int[] c) {
for (int i = 0; i < c.length; i++) {
System.out.print(c[i] + " ");
}
}
// /**
// *
// *
// * 输出指定范围的元素
// *
// * @param lowerBound 开始位置
// * @param upperBound 结束位置
// * @author gzh
// * @date 2017年11月29日下午2:04:12
// */
// private static void outData(int[] arr, int lowerBound, int upperBound) {
// System.out.print("——");
//
// for (int i = lowerBound; i <= upperBound; i++) {
// System.out.print(a[i] + " ");
// }
//
// System.out.println();
// }
/**
*
*
* 打印数据
*
* @param arr
* @param lowerBound
* @param upperBound
* @author gzh
* @date 2017年11月30日上午11:22:25
*/
private static void outData(int[] arr, int lowerBound, int upperBound) {
System.out.print("——");
for (int i = lowerBound; i <= upperBound; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
/**
*
*
* 打印空格
*
* @param n 空格数量
* @author gzh
* @date 2017年11月29日下午12:59:27
*/
private static void outSpace(int n) {
for (int i = 0; i < n; i++) {
System.out.print(" ");
}
}
}
日志
当前排序开始:0~1——9 1
第一个集合:0~0——9
第二个集合:1~1——1
当前排序结束:0~1——1 9
当前排序开始:2~3——3 2
第一个集合:2~2——3
第二个集合:3~3——2
当前排序结束:2~3——2 3
当前排序开始:4~4——4
第一个集合:4~4——4
第二个集合:5~4——
当前排序结束:4~4——4
1 9 2 3 4
当前排序开始:0~3——1 9 2 3
第一个集合:0~1——1 9
第二个集合:2~3——2 3
当前排序结束:0~3——1 2 3 9
当前排序开始:4~4——4
第一个集合:4~4——4
第二个集合:5~4——
当前排序结束:4~4——4
1 2 3 9 4
当前排序开始:0~4——1 2 3 9 4
第一个集合:0~3——1 2 3 9
第二个集合:4~4——4
当前排序结束:0~4——1 2 3 4 9
1 2 3 4 9
当前排序开始:0~4——1 2 3 4 9
第一个集合:0~4——1 2 3 4 9
第二个集合:5~4——
当前排序结束:0~4——1 2 3 4 9
1 2 3 4 9
1 2 3 4 9
算法的速度
1.递归实现
合并排序算法有一个while循环,所以速度是N。
递归调用的时候,不断地对待排序集合进行折半,拆分成2个更小的集合,所以速度是logN。
所以,递归实现的速度是N * logN。
2.非递归实现
两层嵌套循环,所以速度是N*N。
参考
《java数据结构和算法》-第六章-递归-归并排序
https://zh.wikipedia.org/wiki...