插入排序(直接插入、二分插入、希尔排序)

直接插入

描述: 从原始序列第二个元素起,依次选取并插入到前面合适的位置,直到序列最后一个元素完成插入。

java代码

public void insertSort(int[] arr) {
	int temp, j;
	for (int i = 1; i < arr.length; i++) {			// 从第二个元素起
		temp = arr[i];								// 取出待排元素
		// 待排元素与它前面的元素从后往前比较,根据比较结果判断前面元素是否后移		 
		for (j = i; j > 0 && temp < arr[j - 1]; j--) {
			arr[j] = arr[j - 1];
		}	
		// 每次满足循环条件执行元素后移时也执行了j--,此时j所指的位置就是前面元素后移之后空出来的那个位置
		arr[j] = temp;
	}
}

算法分析

  • 时间复杂度 O ( n 2 ) Ο(n^2) O(n2),最好情况n个初始元素顺序,每个元素只和前面元素比较一次,共比较n-1次,不移动;最坏情况逆序,每个元素都要和前面所有元素比较,共比较 ∑ i = 1 n − 1 i ≈ n 2 2 \sum_{i=1}^{n-1}i \approx \frac{n^2}{2} i=1n1i2n2次,每次比较后元素都会移动,所以也移动了 n 2 2 \frac{n^2}{2} 2n2次。平均情况下比较次数和移动次数都是 n 2 4 \frac{n^2}{4} 4n2次,时间复杂度 O ( n 2 ) Ο(n^2) O(n2)
  • 空间复杂度 O ( 1 ) Ο(1) O(1),排序过程只需要一个辅助空间temp。
  • 排序稳定,第二个for循环的循环条件可以看出排序前后没有改变相等元素的相对位置。
  • 顺序存储结构、链式存储结构都适用。
  • 由时间复杂度的分析可看出当初始元素基本有序时,时间复杂度趋近 O ( n ) Ο(n) O(n),基本取决于初始元素个数n,当初始元素无序程度高且个数n又较大时,时间复杂度较高,趋近 O ( n 2 ) Ο(n^2) O(n2),不宜采用。

二分插入

描述: 二分插入排序,也叫折半插入排序,只是将上面直接插入中的从后往前顺序查找换为二分查找。

java代码

public void binaryInsertSort(int[] arr) {
	int low, mid, high, temp;
	for (int i = 1; i < arr.length; i++) {			// 从第二个元素开始
		low = 0;
		high = i - 1;								// low、high为初始有序区间上下标
		temp = arr[i];								// 取出待排元素
		// while循坏得到待排元素应该插入的位置下标low,为什么该插入这个位置后面解释
		while (low <= high) {
			mid = (low + high) / 2;
			if (temp < arr[mid]) {					// 通过与有序区中间位置元素的比较重置比较区间
				high = mid - 1;
			} else {
				low = mid + 1;
			}
		}
		// 将前面有序区中下标low及后面的所有元素后移一位
		for (int j = i; j > low; j--) {
			arr[j] = arr[j - 1];					
		}
		arr[low] = temp;							// 插入空出的下标为low的位置
	}
}

关于while循环后下标low处就是元素要插入的位置,是因为while循环条件里的low=high这种情况最后一定会出现,出现时有low=high=mid,这时temp与下标为low的元素比较,如果小于,则执行high=mid-1,low不变,此时low指的位置就是temp要插入的位置。如果大于等于,则执行的是low=mid+1,其实也就是low=low+1,表示将temp插在原来下标low元素的后面,所以无论比较结果如何,返回的low值就是temp要插入的位置。从这儿也可以看出相等元素排序前后相对位置并没有发生相对改变,所以折半插入排序是一种稳定排序。(第11行的判断条件改成temp<=arr[mid]就不属于稳定排序了)

算法分析

  • 时间复杂度 O ( n 2 ) Ο(n^2) O(n2),折半插入与直接插入相比一般情况下只减少了比较次数,而移动次数并没有改变。
  • 空间复杂度 O ( 1 ) Ο(1) O(1),排序过程只需要一个辅助空间temp保存每次取出的元素。
  • 排序稳定
  • 只能用于顺序结构,不能用于链式结构。
  • 适用于初始元素无序且个数n较大的情况。

希尔排序

描述: 希尔排序,也叫缩小增量排序,从上面可以知道,直接插入排序的时间复杂度与两个因素有关,一是初始元素的个数n,二是初始元素的无序程度。当初始元素的无序程度较高时,时间复杂度趋近于 O ( n 2 ) Ο(n^2) O(n2),n越大,时间复杂度越高,所以说直接插入排序不适合初始元素无序程度高且个数n又大的情况。相反,当初始元素基本有序时,时间复杂度趋近于 O ( n ) Ο(n) O(n),此时直接插入排序就是一种很理想的排序算法。而希尔排序正是这样的一种思想。算法步骤如下:
假设选取的增量序列为T4>T3>T2>T1=1

  1. 对初始元素按T4距离逻辑分组,组内直接插入排序。
  2. 按T3距离逻辑分组,组内直接插入排序。
  3. 以此类推,直到增量1,也就是对所有元素进行一次直接插入排序。

java代码

public void shellSort(int[] arr) {
	int[] init = new int[] { 5, 3, 1 }; 			// 保存增量序列
	int temp, j;
	for (int k = 0; k < init.length; k++) { 		// 根据每个增量逻辑分组
		// 组内还是直接插入排序,只是将原来相邻元素的距离“1”换成“init[k]”
		for (int i = init[k]; i < arr.length; i += init[k]) {
			temp = arr[i];
			for (j = i; j >= init[k] && temp < arr[j - init[k]]; j -= init[k]) {
				arr[j] = arr[j - init[k]];
			}
			arr[j] = temp;
		}
	}
}

关于增量的选取, 应该依次递减且相互互质,并且最后一个增量值必须为1。这是为了避免某次分在同一组已经排序过的元素本次又分在同一组,再次排序浪费时间。比如下面这种情况
在这里插入图片描述
算法分析

  • 时间复杂度比直接插入排序 O ( n 2 ) Ο(n^2) O(n2)要低,具体跟选取的增量序列有很大关系。比直接插入要低是因为直接插入每次移动只消除一个逆序对,而希尔排序当增量大于1时每次移动一般消除了多个逆序对,减少了元素的比较和移动次数。当选取Hibbard增量序列 2 k − 1 2^k-1 2k1 时(k=1,2,3…),最坏情况下的时间复杂度为 O ( n 3 / 2 ) Ο(n^{3/2}) O(n3/2),平均时间复杂度猜想是 O ( n 5 / 4 ) Ο(n^{5/4}) O(n5/4)
  • 空间复杂度 O ( 1 ) Ο(1) O(1),排序过程只需要一个辅助空间temp保存取出的元素。
  • 排序不稳定,因为元素都是在自己的逻辑分组内跳跃移动。
  • 只能用于顺序结构,不能用于链式结构。
  • 总的比较次数和移动次数都比直接插入排序要少,初始元素个数越多效果越明显,所以适合初始元素无序且个数n较大的情况。

两个定理

定理1:任意N个不同元素组成的序列平均具有 N ( N − 1 ) 4 \frac{N(N-1)}{4} 4N(N1)个逆序对。
定理2:任何仅以交换相邻两元素来排序的算法,其平均时间复杂度 Ω ( N 2 ) Ω(N^2) Ω(N2)。意思是最好也只能到 N 2 N^2 N2数量级这个复杂度,N是元素个数。
从定理2可以看出,以交换相邻元素排序的时间复杂度与逆序对总数有关,这也解释了希尔排序的比较和移动次数低于直接插入排序的原因。(分组元素逻辑相邻)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值