插入排序算法

一 概述

        插入排序是一种简单直观的排序方法,其基本思想是每次将一个待排序的记录按其关键字大小插入到前面已经排好序的子序列中,直到全部记录插入完成。由插入排序的思想可以引申出三个重要的排序算法:直接插入排序,折半插入排序和希尔排序。

二 直接插入排序

在排序过程中,待排序表L[1...n]在某次排序过程中的状态如下:

                       

此时将元素L[i]插入到已经有序的子序列L[1...i-1]中,需要执行下列操作:

  1. 查找出L[i]在L[1...i-1]中的插入位置k;
  2. 将L[k...i-1]中的所有元素都要依次往后移动一个位置;
  3. 将L[i]插入位置k。                             

        为了实现n个数字L[1,n]的排序,可以将L(2)~L(n)依次插入到前面已经排好序的子序列中,初始值L[1]可以视为是一个已经排好序的子序列。从而执行n-1次插入就能得到一个有序的表。插入排序在实现上通常采用就地排序(空间复杂度O(1)),因此在从后向前的比较过程中,需要反复将已排好序的元素逐步向后挪位,从而为新元素提供插入空间。

        直接插入排序算法适用于顺序存储和链式存储的线性表。为链式存储时,可以从前往后查找指定元素的位置。

排序过程图解

顺序存储代码实现

#include<stdio.h>

void InsertSort(int a[], int n){
	int i, j;
	for (i = 2; i <= n; i++) {//依次将a[2]~a[n]插入到前面已经排序的序列中
		if(a[i] < a[i-1]) {
			a[0] = a[i];//将待插入从数复制为哨兵
			for (j = i-1;a[j] > a[0]; j--) //从后往前查找待插入位置
				a[j+1] = a[j];
			a[j+1] = a[0];
		}
	}
}

int main(){
	
	int b[] = {-1,49,38,65,97,76,13,27,49}; //b[0]为辅助内存空间
	InsertSort(b, 8);
	for(int i = 1; i < 9; i++) {
		printf("%d ",b[i]);
	}
	return 0;
}

结果展示:

        

从直接插入排序算法可得,每趟插入的过程中都会进行两项工作:

  1. 从前面的有序子表中查找出待插入元素应该被插入的位置;
  2. 将待插入数据的位置的数据移出,并将待插入数据移入。

算法性能分析

算法最好时间最坏时间平均时间额外空间/空间复杂度稳定性
直接插入排序O(n)O(n^2)O(n^2)1(常数)/O(1)稳定

二 折半插入排序

由直接插入排序可知,总是一边比较一边移动元素,除此之外,我们可以折半查找找出元素待插入的位置,然后统一地移动待插入位置之后的所有元素。

实际上,折半插入排序通过对有序序列部分进行折半查找来减少比较的元素的次数,约为O(nlog2^n),该比较次数与待排序表的初始化状态无关,进取决于表中的元素个数n;当时元素的移动次数并未改变,它依赖于待排序的初始状态。因此,折半插入排序的时间复杂度仍为O(n^2),但对于数据量不很大的排序表,折半插入排序往往性能优越,所以折半插入排序是一种稳定的排序方法。因为基于数组下标实现查找排序所以折半插入排序适用于顺序存储结构。

代码实现:

#include<stdio.h>

void halfInsertSort(int a[], int n){
	
	int i , j;
	int low , high, mid;
	
	for(i = 2; i <= n ; i++) {
		if(a[i] < a[i-1]){
			a[0] = a[i];
			low = 1;
			high = i-1;
			while(low <= high){ //对有序的部分进行折半查找
				mid = (low + high)/2;
				if(a[mid] > a[0])
					high = mid - 1; //继续查找左半子表
				else low = mid + 1; //继续查找右半子表
			}
			
			for(j = i - 1; j >= high + 1; --j)
				a[j+1] = a[j]; //统一后移元素,空出插入位置
			//a[high+1] = a[0]; 
			a[j+1] = a[0];			
		}
	}
}

int main() {

	int b[] = {-1,49,38,65,97,76,13,27,49}; //b[0]为辅助内存空间
	halfInsertSort(b, 8);
	for(int i = 1; i < 9; i++) {
		printf("%d ",b[i]);
	}
	return 0;
}

结果展示:

       

算法性能分析:  

算法最好时间最坏时间平均时间额外空间/空间复杂度稳定性
折半插入排序O(n)O(n^2)O(nlog2^n)1(常数)/O(1)稳定

三 希尔排序

直接插入排序算法的时间复杂度为O(n2),但若待排序的序列为“正序”时,其时间复杂度可以提高值O(n),由此可见,它更适合于基本有序的排序表和数据量不大的排序表。希尔排序正是基于这两点对直接插入排序进行改进而得来,又称缩小增量排序。

先将待排序表分割成若干个形如L[i,i + d,i + 2d,...,i + kd]的“特殊子表”,即把相隔某个"增量"的记录组成一个子表,对各个子表分别进行直接插入排序,当整个表中的元素已呈“基本有序”时,再对全体记录进行一次直接插入排序。

希尔排序算法仅适用于线性表为顺序存储的情况。

希尔排序的过程如下:

  1. 先取一个小于n的步长d1,把表中的全部记录分成d1组,所有距离为d1的倍数的记录放在同一组,在各组内进行直接插入排序;
  2. 取第二个步长d2<d1,重复上述过程,直到所取到的dt = 1,即所有记录放在同一组中,再进行直接插入排序,由于此时已经具有较好的局部有序性,故可以很快的得到最终结果。目前为止,增量序列还没有一个最合适的方案,希尔提出的方法是d1=n/2,di+1 = di/2,并且最后一个增量等于1。 

如对于数据序列:49 38 65 97 76 13 27 49 55 04

            

代码实现:

#include<stdio.h>

void ShellSort(int a[], int n) {
	
	int d,i,j;
	for(d = n/2; d >= 1; d = d/2) {
		
		for(i = d+1; i <= n; i++ ) {
			
			if(a[i] < a[i-d]) { //需将a[i]插入有序增量子表
				
				a[0] = a[i];	//暂存a[0]
				for(j = i-d; j>0 && a[0]<a[j]; j-=d)
					a[j+d] = a[j];//记录后移,查找插入的位置
				a[j+d] = a[0];//插入
			} 
		}
	}
}

int main(){
	
	int b[] = {-1,49,38,65,97,76,13,27,49,55,04};//b[0]为辅助内存空间
	
	ShellSort(b,10);

	for(int i = 1; i < 11; i++) {
		printf("%d ",b[i]);
	}
	return 0;
}

结果展示:

算法性能分析:  

算法最好时间最坏时间平均时间额外空间/空间复杂度稳定性
希尔排序O(n)O(n^2)O(n^1.3)1(常数)/O(1)不稳定

空间效率:仅使用了常数个辅助单元,因而空间复杂度为O(1)。

时间效率:由于希尔排序的时间复杂度依赖于增增量序列的函数,这是个数学上尚未解决的难题,当n在某个特定的范围时,希尔排序的时间复杂度约为O(n^1.3)。最坏的情况下希尔排序的时间复杂度为O(n^2)。

稳定性:当相同关键字被划分到不同的子表中,会存在改变它们之间的相对次序,因此希尔排序十一总不稳定的排序。

适用性:希尔排序算法仅适用于线性表为顺序存储的情况。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值