[Vol.01-01][15-27, 35, 36][插入排序与二分查找排序]

入排序的过程与很多人打牌时,整理手中的牌的做法差不多:在开始摸牌时,我们的左手是空的,牌面朝下放在桌上;接着,一次从桌上摸起一张牌,并将它插入到左手一把牌中的正确位置上。为了找到这张牌的正确位置,需要将它与手中已有的每一张牌从右到左依次地进行比较。无论在什么时候,左手中的牌都是排好序的,而这些牌原来都是桌上那副牌里最顶上的那些牌。

入排序(Insertion Sort)算法的工作原理是:对于输入序列中未排序的元素,在已排序的子序列中从后向前扫描,找到相应的位置并插入。插入排序在实现上,通常采用in-place排序(原地排序,即只需要O(1)的额外空间的排序),因而在从后向前扫描的过程中,需要反复把已排好序的元素依次向后移动,为当前待插入元素提供插入空间。

入排序使用的是增量方法:在子数组A[0 ... i – 1]排好序后,将元素A[i]插入,形成排好序的子数组A[0 ... i]。

设A[i]表示待插入到手中的"当前牌";在外层for循环(循环变量为i)的每一轮迭代的开始,包含元素A[0 ... i –1]的子序列构成了左手中当前已经排好序的一手牌,元素A[i + 1 ... n –1]对应于仍然在桌上的那堆牌,实际上,元素A[0 ... j –1]是最初在位置0到i – 1上的那些元素,但是现在已经排好序了;在内层for循环中,要将A[i – 1]、A[i –2]、A[i –3]等元素依次向右移动一个位置,直到找到A[i]的适当位置为止,这时将A[i]的值插入到这个位置。

向整型数组的插入排序算法实现代码如下:

/**
 * 插入排序
 */
public static void insertionSort(int[] array) {
	if (array == null)
		return;
	int n = array.length;
	if (n <= 1)
		return;
	int temp = 0; // 暂存未排序的第一个元素,可以用array[i]代替
	int j = 0; // 已排序子序列索引,用于查找temp的插入位置
	for (int i = 1; i < n; i++) { // 从第二个元素开始循环
		temp = array[i];
		j = i;
		while (j > 0 && temp < array[j - 1]) { // 注意这里的短路陷阱
			array[j] = array[j - 1];
			j--;
		}
		array[j] = temp;
	}
}

果元素比较操作的代价大于元素移动操作的代价,那么可以采用二分查找策略来减少比较操作的次数。该算法可以认为是插入排序算法的一个变种,称为二分查找排序(Binary Insertion Sort)。

向整型数组的二分查找排序算法实现代码如下:

/**
 * 二分查找排序
 */
public static void binaryInsertionSort(int[] array) {
	if (array == null)
		return;
	int n = array.length;
	if (n <= 1)
		return;
	int key = 0;
	int j = 0;
	for (int i = 1; i < n; i++) {
		key = array[i];
		j = binarySearch(array, key, 0, i - 1);
		for (int k = i; k > j; k--) {
			array[k] = array[k - 1];
		}
		array[j] = key;
	}
}

/**
 * 改造过的二分查找,返回key应该在已排序序列中插入的位置
 */
private static int binarySearch(int[] array, int key, int low, int high) {
	int middle = 0;
	int temp = 0;
	// 如果key不在序列array中,则二分查找在退出循环时,必有:low == high + 1
	// 如果key不在序列array中,在最后一次成功循环后,必有:low == high == middle
	// 如果key不在序列array中,在二分查找退出循环后,low的值即为key应插入的位置
	while (low <= high) {
		middle = low + (high - low) / 2;
		temp = array[middle];
		if (key > temp)
			low = middle + 1;
		else if (key < temp)
			high = middle - 1;
		else
			// 这里没有对middle加1,对key == array[middle]的统一处理在line.54-56
			break;
	}
	// 需要对middle的值进行修正以表示key的正确插入位置
	// if (key > array[middle])
	// return middle + 1;
	// else if (key < array[middle])
	// return middle;
	if (low > high) { // line.43-46和line.47-49作用是一样的
		middle = low;
		return middle; // 可以去掉,但会使line.54每次都判断
	}
	// 稳定排序处理
	// 注意短路陷阱:如果没有middle <= high,则middle可能在middle++之后等于high + 1
	// 这样在执行array[middle] == key语句时,会抛出数组越界异常
	while (middle <= high && array[middle] == key)
		middle++;
	return middle;
}

入排序算法是一种稳定的排序算法。

入排序是一个对少量元素进行排序的有效算法。插入排序的最好情况是输入序列已经排好序,时间复杂度是O(n);最坏情况是输入序列按逆序排序,时间复杂度是O(n2);平均情况是A[0 ... i – 1]中的一半元素小于A[i],一半元素大于A[i],时间复杂度是O(n2)。

意:在Insertion Sort Algorithm的line.15-18的while循环中,即使是采用了二分查找策略,插入排序的最坏情况和平均情况时间复杂度也不会改善至O(nlog2n),这是因为移动序列元素的代价并没有改变(line.16);二分查找可以改善查找A[i]插入位置的时间代价(O(log2i)),但是却不能改善移动元素的时间代价(O(i)),因此,二分查找排序算法不能帮助插入排序算法改善最坏情况和平均情况的时间复杂度(如果换成链表,则无法直接使用二分查找)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值