排序算法(三)插入排序

0. 简介
  • 插入排序法相对于前 2 种排序法来说,思路相对简单。对于长度为 n 的数组 ‘int[] array = new int[n];’(其中 n > 1),先将数组的前两个元素即 array[0] 和 array[1] 进行排序,这两个元素组成了一个 ‘有序数组片段’,其长度为 2,接下来将数组 array 中的元素从 array[2] 开始,直到数组的最后一个元素 array[n - 1] 为止,把每个元素依次插入前面的 ‘有序数组片段’ 中,从而完成排序工作。这里有两点需要注意,一:对于移动数组的操作(这里有两种方式移动数组,一种是数组挨个的依次往后挪位置,一种是使用直接复制数组操作),二:对 ‘有序数组片段’ 的查询,以确定插入位置(对于有序数组,可使用二分法查找,以优化性能)。暂时没有其它好说的了。
1. 插入排序程序示例
/** 插入排序法 */
class InsertSort{
	/**
	 * 排序方式:递增 或 递减
	 */
	public enum SortType{
		INCREMENT_SORT("递增排序"), DECREMENT_SORT("递减排序");
		private final String desc;
		private SortType(String desc) {
			this.desc = desc;
		}
		public String desc() {
			return this.desc;
		}
	}
	/**
	 * XXX 可优化
	 * 插入排序法(递增排序方式)
	 * @param datas 等待排序的数组
	 * @param type 指定排序方式,取值为枚举类型,查看 {@code InsertSort.SortType} 
	 */
	public static void doSorting(int[] datas, SortType type) {
		// 仅仅支持递增排序(添加递减操作逻辑会使得代码更长,而不易阅读)
		type = SortType.INCREMENT_SORT;
		if (datas != null && datas.length > 1) {
			final int arrayLength = datas.length;
			// 先排序数组的前两个元素,产生初始的长度为 2 的 ’有序数组片段‘
			if (datas[0] > datas[1]) {
				int temp = datas[0];
				datas[0] = datas[1];
				datas[1] = temp;
 			}
			int c = 0;
			// 从第三个位子(即 datas[2] )开始执行插入排序
			for(int i = 2;i < arrayLength;i++) {
				c = datas[i];
				if (c <= datas[0]) {
					// 在’有序数组片段‘头部插入
					moveToRight(datas, 0, i -1);
					datas[0] = c;
				}else if(c >= datas[i - 1]) {
					// 在’有序数组片段‘尾部插入(当前元素 datas[i] 紧邻 ’有序数组片段‘ 的尾部)
					continue;
				}else {
					// XXX 该部分可以继续优化
					// 从’有序数组片段‘的中间元素开始比较/插入(二分)
					int middleIndex = Math.round(i / 2.0f - 1.0f);
					int middleValue = datas[middleIndex];
					if (c == middleValue) {
						// 运气真好
						moveToRight(datas, middleIndex, i -1);
						datas[middleIndex] = c;
					}else if(c < middleValue) {
						// continue to left
						middleIndex--;
						for(;middleIndex >= 0;middleIndex--) {
							// 找到第一个 c 大于或等于的元素
							if (c >= datas[middleIndex]) {
								moveToRight(datas, middleIndex + 1, i - 1);
								datas[middleIndex + 1] = c;
								break;
							}
						}
					}else {
						// c > middleValue
						// continue to right
						middleIndex++;
						for(;middleIndex < i;middleIndex++) {
							// 找到第一个 c 小于或等于的元素
							if (c <= datas[middleIndex]) {
								moveToRight(datas, middleIndex, i - 1);
								datas[middleIndex] = c;
								break;
							}
						}
					}
				}
			}
		}
	}
	
	/**
	 * 将指定的数组片段向后(右边)移动一个位置。该方法用于代理 moveToRight_verbose() 和 moveToRight_fast() 这两个方法,便于在这两个方法间切换实现。
	 * @param array 目标数组
	 * @param startIndex 数组片段的起始位置(包括该位置的元素)
	 * @param endIndex 数组片段的终止位置(包括该位置的元素)
	 */
	private static void moveToRight(int[] array, int startIndex, int endIndex) {
		// v1 verbose
//		moveToRight_verbose(array, startIndex, endIndex);
		// v2 fast and simple
		moveToRight_fast(array, startIndex, endIndex);
	}
	
	/**
	 * 将指定的数组片段向后(右边)移动一个位置,该方法移动大量数据时,效率较低。
	 * @param array 目标数组
	 * @param startIndex 数组片段的起始位置(包括该位置的元素)
	 * @param endIndex 数组片段的终止位置(包括该位置的元素)
	 */
	private static void moveToRight_verbose(int[] array, int startIndex, int endIndex) {
//		if (array != null && array.length > endIndex + 1 && endIndex >= startIndex) {
		// 验证参数(可不验证)
//		}
		for(int i = endIndex + 1;i > startIndex;i--) {
			array[i] = array[i - 1];
		}			
	}
	
	/**
	 * 将指定的数组片段向后(右边)移动一个位置,使用复制数组操作,当移动大量数组元素时将显著提高效率。
	 * @param array 目标数组
	 * @param startIndex 数组片段的起始位置(包括该位置的元素)
	 * @param endIndex 数组片段的终止位置(包括该位置的元素)
	 */
	private static void moveToRight_fast(int[] array, int startIndex, int endIndex) {
//		if (array != null && array.length > endIndex + 1 && endIndex >= startIndex) {
		// 验证参数(可不验证)
//		}
		System.arraycopy(array, startIndex, array, startIndex + 1, endIndex - startIndex + 1);			
	}
	
	/**
	 * 验证数组 sortedArray 中,以下标 startIndex 开始,长度为 length 的数组片段的排序是否符合 type 指定的顺序,
	 * <br> 对于全等数组可以说它是递增也可以说是递减。
	 * @param sortedArray 目标数组
	 * @param startIndex 起始元素的下标(包括)
	 * @param length 数组片段的长度
	 * @param type 排序顺序
	 * @return true:指定数组片段符合 type 指定的顺序,否则返回 false
	 */
	public static boolean valicateSortedArray(int[] sortedArray, int startIndex, int length, SortType type) {
		int arraySize = 0;
		boolean valicated = true;
		int count = 0;
		if (sortedArray != null && (arraySize = sortedArray.length) > startIndex
				&& (startIndex + length -1) < arraySize) {	
			count += 1;
			for(int i = startIndex + 1;i < arraySize;i++) {
				count++;
				// 每个数与它的前任比较
				if (type == SortType.INCREMENT_SORT && sortedArray[i] < sortedArray[i - 1]) {
					// 验证递增数组
					valicated = false;
					break;
				}else if (type == SortType.DECREMENT_SORT && sortedArray[i] > sortedArray[i - 1]) {
					// 验证递减数组
					valicated = false;
					break;
				}
			}
		}
		System.out.println("已验证 - 元素个数:" + count);
		return valicated;
	}
}

简单测试下:

public class Structure_3_4 {
	private static final ThreadLocal<Long> startTime = new ThreadLocal<Long>();
	public static void main(String[] args) {
		int NUMBERS_NUM = 100000; // 生成随机数数量
		int BOUND = 1000000; // 随机数边界
		Random random = new Random();
		int[] datas = new int[NUMBERS_NUM];
		System.out.println("number of elements is [" + NUMBERS_NUM + "]");
		for (int i = 0; i < NUMBERS_NUM; i++) {
			datas[i] = random.nextInt(BOUND);
		}
		// 记录排序操作起始时间
		startTime.set(System.currentTimeMillis());
		// do sorting
		InsertSort.doSorting(datas, InsertSort.SortType.INCREMENT_SORT);
		long costTimeByMS = System.currentTimeMillis() - startTime.get();
		// 打印统计信息
		double costTime = costTimeByMS / 1000.0;
		System.out.println("cost time:" + (costTime == 0 ? costTimeByMS + "ms" : costTime + "s"));
		System.out.println("done\n-----------------------------------");
		System.out.println("验证:" + InsertSort.valicateSortedArray(datas, 0, datas.length
				, InsertSort.SortType.INCREMENT_SORT));
		// 输出已排序数组最后 10 个元素
		for (int i = 1;i <= 10 && (NUMBERS_NUM - i) >= 0;i++) {
			System.out.println("第 " + (NUMBERS_NUM - i + 1) + " 个数: " + datas[NUMBERS_NUM - i]);
		}
	}
}

输出结果如下(排序 10万个数耗时 1.23s):

number of elements is [100000]
cost time:1.23s
done
-----------------------------------
已验证 - 元素个数:100000
验证:true
第 100000 个数: 999977
第 99999 个数: 999972
第 99998 个数: 999954
第 99997 个数: 999946
第 99996 个数: 999942
第 99995 个数: 999920
第 99994 个数: 999905
第 99993 个数: 999900
第 99992 个数: 999884
第 99991 个数: 999855
上一篇:jquery ajax request payload和fromData请求方式


下一篇:机器学习之KMeans聚类