算法-排序-归并排序

核心思想

归并排序的核心思想是,对两个有序的集合进行排序。

实现步骤,如下:
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...

你可能感兴趣的:(归并,归并排序,排序,算法)