插入排序
插入排序是最简单的排序算法之一,对于N个元素的序列,需要进行N-1次的插入来完成排序。插入排序的算法:
(1)对于位置P,0到P-1位置上的元素已经是有序的,P从1开始;
(2)将P指向的元素放到[0,P]正确的位置,这样0到P位置上的元素也是有序的。
插入排序确实很简单,不需要过多的介绍,直接用一个示例来演示其过程,待排序列:2 4 6 1 3 5 总共有6个元素,所以需要5趟排序,P从1开始
(1)2是一个有序序列
(2)P=1,将4插入到正确位置,排序后:2 4
(3)P=2,将6插入到正确位置,排序后:2 4 6
(4)P=3,将1插入到正确位置,排序后:1 2 4 6
(5)P=4,将3插入到正确位置,排序后:1 2 3 4 6
(6)P=5,将5插入到正确位置,排序后:1 2 3 4 5 6
插入排序代码:
void insertsort(int *A,int n) {
int i,j;
int x;
for(i = 1; i < n; i++) {
x = A[i];//待插入元素
for(j = i; j > 0 && A[j - 1] > x; --j)
A[j] = A[j - 1];
A[j] = x;
}
}
上面的代码实现是比较好的,它移动实现了元素移动,却没有明显的使用交换,而是使用x保存待插入元素,不断将x之前的元素往右移动,直到空出适合x插入的位置。
时间复杂度分析
对于P每一个值,代码中第6行的内层for循环最多执行P+1次比较,对所有的P求和:
T(N) = 2 + 3 + 4 + .... + N = O(N^2) = o(N^2)
如果输入是有序的,那么内层for循环就不会执行,因此时间复杂度是O(N),这是最好的情形。平均时间复杂度是:O(N^2) = o(N^2)
简单排序的时间下界
数组的一个逆序:数组中i < j,但A[i] > A[j]的序偶(A[i],A[j])。
在上面的序列中存在(2,1),(4,1),(4,3),(6,1),(6,3),(6,5)这6个逆序。想一下插入排序的过程,如果待插入元素与前面的元素存在逆序,那么就需要交换数据,每次都是和相邻的元素交换,交换一次只能消除一个逆序。也就是说数组中的逆序个数就是排序时的交换次数。 插入排序时,除了交换外还有其他O(N) 项工作,设I为原数组中的逆序数, 因此插入排序的时间复杂度是O(I + N)。如果能求出I的平均值,也就求出了插入排序的平均时间复杂度。
定理:N个互异的数组的平均逆序数是N(N - 1) / 4。
证明:对于任意序列L,其反序是~L,对于L中的任意两个数(x,y),且y > x。如果x在y的前面,那么对于~L而言,x就在y的后面,(x,y)就是~L的逆序:
L:..... x ...... y ......
~L:....... y ....... x .......
反之,如果x在y的后面,(x,y)就是L的逆序。总之,对于任意(x,y),它要么是L的逆序,要么是~L的逆序。这样L + ~L的逆序总和是:C(N,2) = N(N - 1) / 2。L的平均逆序就应该是总量的一半:N(N-1) / 4。
序列的平均逆序数是二次的,交换相邻元素只能消除一个逆序。因此,凡是通过只交换相邻元素的排序算法的平均时间是二次的,这是这类算法一个很强的下界。因此我们又得到一个定理:通过交换相邻元素进行排序的任何算法平均需要Ω(N^2)。
这个下界告诉我们,为了以亚二次时间运行,必须要对相距较远的元素进行交换,这样的一次交换可能会消除多个逆序。
转载请注明出处:喻红叶《排序算法的时间下界》