数据结构(23)--排序篇之插入排序

参考书籍:数据结构(C语言版)严蔚敏吴伟民编著清华大学出版社

本文中的代码可从这里下载:https://github.com/qingyujean/data-structure

1.直接插入排序

1.1基本思想

    一趟直接插入排序的基本思想: 将记录L.r[i]插入到有序子序列L.r[1..i-1]中,使记录的有序序列从L.r[1..i-1]变为L.r[1..i]。 完成这个“插入”分三步进行:
     1.查找L.r[i]在有序子序列L.r [1..i-1]中的插入位置j;
     2.将L.r [j..i-1]中的记录后移一个位置;
     3.将L.r [i]复制到L.r [j]的位置上。
     整个排序过程进行n–1趟插入,即:先将序列中的第1个记录着成一个有序的子序列,然后从第2个记录起逐个插入,直至整个序列变成接关键字非递减有序序列为止。

1.2代码实现

 

//package sort.insertionSort;
public class StraightSort {
	/**
	 * @param args
	 */
	//对顺序表L做直接插入排序
	public static void InsertSort(int[] L){
		//先将第一个元素看成是一个有序子序列
		for(int i = 2; i <= L.length-1; i++){
			//在已经有序的1->i-1的子序列中插入第i个元素,以保证仍然有序,成为一个1->i的有序子序列
			L[0] = L[i];//监视哨
			int j = i-1;
			/*
			for(; j > 0; j--){//没有利用监视哨,仍然用j>0作为条件以避免数组下标越界
				if(L[0] < L[j])
					L[j+1] = L[j];
				else
					break;
			}
			*/
			for(; L[0] < L[j]; j--)
				L[j+1] = L[j];//利用监视哨
			//当L[0] >= <[j]时跳出循环,由于j做了一次自减,所以是L[j+1] = L[0],
			//当是因为=而跳出循环时(j后来没有自减),L[0]插在L[j]的后面以保证了“稳定” 
			L[j+1] = L[0];
		}
	}
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		int[] test = {0, 53, 27, 36, 15, 69, 42}; //0号单元未使用
		InsertSort(test);
		for(int i = 1; i <= test.length-1; i++)
			System.out.print(test[i]+" ");
	}
}

运行结果:

 

1.3性能分析

    直接插入排序性能分析 ,实现排序的基本操作有: (1)“比较” 关键字的大小 (2)“移动”记录
    对于直接插入排序: 
    最好情况“比较”次数:n-1;“移动”次数:2(n-1)
    最坏的情况“比较”和“移动”的次数均达到最大值,分别为:(n+2)(n-1)/2;(n+4)(n-1)/2
    由于待排记录序列是随机的,取上述二值的平均值。所以直接插入排序的时间复杂度为 O(n^2)
    直接插入排序是“稳定的”:关键码相同的两个记录,在整个排序过程中,不会通过比较而相互交换。

2.折半插入排序

2.1基本思想

    考虑到 L.r[1..i-1] 是按关键字有序的有序序列,则可以利用折半查找实现“ L.r[1…i-1]中查找 L.r[i] 的插入位置”如此实现的插入排序为折半插入排序。折半插入排序在寻找插入位置时,不是逐个比较而是利用折半查找的原理寻找插入位置。待排序元素越多,改进效果越明显。

2.2代码实现

 

//package sort.insertionSort;

public class BinaryInsertionSort {

	/**
	 * @param args
	 */
	//对顺序表L做折半插入排序,利用折半查找快速找到要插入的位置
	public static void binaryInsertSort(int[] L){
		for(int i = 2; i <= L.length-1; i++){		
			//利用折半查找找到要插入的位置
			int low = 1, high = i-1;//在1->i-1的有序子序列中插入第i个元素,使之成为1->i的有序子序列
			L[0] = L[i];//暂存要插入的元素
			while(low <= high){
				int mid = (low+high)/2;
				if(L[0] < L[mid])
					high = mid -1;
				else
					//L[0] >= L[mid]
					low = mid+1;//等于当成大于处理,这样后出现的相等值就会排在后面,从而到达“稳定”
			}
			//此时high = low-1,且high+1即low的位置即为要插入的位置
			for(int j = i-1; j >= low; j--)
				L[j+1] = L[j];
			L[low] = L[0];
		}
	}
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		int[] test = {0, 53, 27, 36, 15, 69, 42}; //0号单元未使用
		binaryInsertSort(test);
		for(int i = 1; i <= test.length-1; i++)
			System.out.print(test[i]+" ");
	}
}

运行结果:

 

2.3性能分析

    折半插入排序减少了关键字的比较次数,但记录的移动次数不变,其时间复杂度与直接插入排序相同,时间复杂度为O(n^2) 。折半插入排序是“稳定的”。

3.希尔排序(缩小增量排序)

3.1基本思想

    希尔排序(Shell Sort)又称为“缩小增量排序”。其基本思想是:先将整个待排元素序列分割成若干个子序列(由相隔某个“增量”的元素组成的)分别进行直接插入排序,待整个序列中的元素基本有序(增量足够小)时,再对全体元素进行一次直接插入排序。因为直接插入排序在元素基本有序的情况下(接近最好情况),效率是很高的,因此希尔排序在时间效率上比前两种方法有较大提高。

3.2代码实现

 

//package sort.insertionSort;

public class ShellSort {

	/**
	 * @param args
	 */
	//对顺序表L做一趟希尔排序,本算法和一趟直接插入排序相比,做了如下修改:
	//1.前后记录位置的增量式dk,而不是1
	//2.L[0]只是暂存单元,而不再是哨兵
	public static void shellInsert(int[] L, int dk){
		/*对于下面for循环的i = i+dk和i++的分析:
		一趟希尔排序里有L.length/dk个子序列,每个子序列要进行直接插入排序,即要进行L.length/dk个直接插入排序
		子序列轮着来(有点并发的感觉),即第一个子序列的第2个数排完序,然后是第2个子序列的第2个数排序,然后是第3个子序列。。。
		i继续自增,然后是第1个子序列的第3个数往第一个子序列的1->2的有序子序列里插入并排序,然后是第2个子序列的第3个数
		往第2个子序列的1->2的有序子序列里插入并排序,然后是第3个子序列的第3个数往第3个子序列的1->2的有序子序列里插入并排序。。。
		i继续自增,然后是第1个子序列的第4个数往第一个子序列的1->3的有序子序列里插入并排序,接着
		第2个子序列的第4个数往第2个子序列的1->3的有序子序列里插入并排序.。。。。。以此类推,直到所有的完成
		 */
		//相当于每个子序列的第一个数都被看成是每个子序列的有序子序列
		for(int i = 1+dk; i <= L.length-1; i++){
			L[0] = L[i];
			//找L[i]应该插入的位置
			int j = i-dk;
			for(; j>0&&L[0]<L[j]; j-=dk)
				L[j+dk] = L[j];
			L[j+dk] = L[0];
		}
	}
	//按增量dlta[0......len(dlta)-1]对顺序表L做希尔排序
	public static void shellSort(int[] L, int[] dlta){
		for(int i = 0; i < dlta.length; i++)
			shellInsert(L, dlta[i]);
	}
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		int[] test = {0, 65, 49, 97, 25, 25, 13}; //0号单元未使用
		int[] dlta = {3, 2, 1};//应使增量序列中的值没有除1之外的公因子,并且最后一个增量值必须等于1。
		shellSort(test, dlta);
		for(int i = 1; i <= test.length-1; i++)
			System.out.print(test[i]+" ");
	}
}

 

运行结果:

3.3性能分析

    虽然我们给出的算法是三层循环,最外层循环为log2n数量级,中间的for循环是n数量级的,内循环远远低于n数量级,因为当分组较多时,组内元素较少;此循环次数少;当分组较少时,组内元素增多,但已接近有序,循环次数并不增加。因此,希尔排序的时间复杂性在O(nlog2n)和O(n^2 )之间,大致为O(n^1. 3)
    希尔排序的时间复杂度较直接插入排序低。希尔排序的分析是一个复杂的问题,因为它的时间是和所取“增量”序列的函数密切相关。到目前为止,还没有求得一种最好的增量序列,但有大量的局部结论。 
    注意:应使增量序列中的值没有除1之外的公因子,并且最后一个增量值必须等于1。
    由于希尔排序对每个子序列单独比较,在比较时进行元素移动,有可能改变相同排序码元素的原始顺序,因此希尔排序是不稳定的。

 

本文中的代码可从这里下载:https://github.com/qingyujean/data-structure

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值