算法-排序-归并排序

163 阅读11分钟

核心思想

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

实现步骤,如下:
1.有序集合a
2.有序集合b
3.对集合a和集合b进行归并排序,排序之后放入集合c,并且集合c有序。

代码

有2种实现方案,
1.递归
2.非递归

递归

归并排序可以实现对2个有序的集合进行排序,并且把排序之后的数据放到第3个集合。代码如下。

package sort.mergeSort;

/**
 * 
 * <pre>
 * 类的名称:
 * 类的作用:归并排序
 * 1.2个有序的集合
 * 2.放入第三个集合并且仍然有序
 * </pre>
 * 
 * @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);
	}

	/**
	 * 
	 * <pre>
	 * 归并排序
	 * </pre>
	 * 
	 * @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++];
		}
	}

	/**
	 * 
	 * <pre>
	 * 打印数据
	 * </pre>
	 * 
	 * @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;

/**
 * 
 * <pre>
 * 类的名称:
 * 类的作用:归并排序(递归实现)
 * </pre>
 * 
 * @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;

	
	/**
	 * 
	 * <pre>
	 * 递归调用归并排序
	 * </pre>
	 * 
	 * @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);
		}
	}
	
	/**
	 * 
	 * <pre>
	 * 输出指定范围的元素
	 * </pre>
	 * @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();
	}

	/**
	 * 
	 * <pre>
	 * 打印空格
	 * </pre>
	 * @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("  ");
		}
	}

	/**
	 * 
	 * <pre>
	 * 归并排序
	 * </pre>
	 * 
	 * @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];
		}
	}

	/**
	 * 
	 * <pre>
	 * 打印数据
	 * </pre>
	 * 
	 * @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;

/**
 * 
 * <pre>
 * 类的名称:
 * 类的作用:归并排序(非递归实现,即采用多层遍历的思想)
 * </pre>
 * 
 * @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);
	}

	/**
	 * 
	 * <pre>
	 * 归并排序(非递归实现,即采用多层遍历的思想)
	 * </pre>
	 * @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();
		}
	}

	/**
	 * 
	 * <pre>
	 * 打印数据
	 * </pre>
	 * 
	 * @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] + " ");
		}
	}
	
//	/**
//	 * 
//	 * <pre>
//	 * 输出指定范围的元素
//	 * </pre>
//	 * @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();
//	}
	
	/**
	 * 
	 * <pre>
	 * 打印数据
	 * </pre>
	 * @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();
	}
	
	/**
	 * 
	 * <pre>
	 * 打印空格
	 * </pre>
	 * @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/%E5%BD%92%E5%B9%B6%E6%8E%92%E5%BA%8F