插入排序是一种很容易想到的算法,它的思路和打扑克牌时排列手牌的方法很相似,比如我们现在单手拿牌,然后从左至右、由小到大进行排序。此时我们需要将牌一张张抽出来,分别插入到前面排好序的手牌中的适当位置。重复这一操作指导插入最后一张牌,整个排序就完成了。
伪代码:
insertionSort(A,N)
for i从1到N-1
v = A[i]
j = i-1
while j>=0且A[j]>v
A[j+1] = a[j]
j--
A[j+1] = v;
将开头元素视作已排序
执行下属处理,直至未排序部分消失
1、取出未排序部分的开头元素赋值给变量v。
2、在已排序部分,将所有比v大的元素向后移动一个单位
3、将已取出的元素v插入空位
举个例子,我们对数组A={8,3,1,5,2,1}进行插入排序,整体流程如下图所示
8 | 3 | 1 | 5 | 2 | 1 |
3 | 8 | 1 | 5 | 2 | 1 |
1 | 3 | 8 | 5 | 2 | 1 |
1 | 3 | 5 | 8 | 2 | 1 |
1 | 2 | 3 | 5 | 8 | 1 |
1 | 1 | 2 | 3 | 5 | 8 |
红色字体的是下一次需要插入的数据,绿色字体的是得到该列数据时移动了的数据,字体为灰色的是本次进入有序位的数据。
第一行为初始数据,第二行开始为移动后的数据
在步骤1(第二行)中,将A[0] 视为已排序,所以我们取出A[1]的3,将其插入到已排序部分的恰当位置。首先把原先位于A[0]的8移动至A[1],再把3插入到A[0]。这样第二个元素就完成了排序。
在步骤2中,我们要把A[2]的1插入恰当位置。这里首先将比1大的A[1]和A[0],顺次向后移动一个位置,然后把1插入到A[0]。
在步骤3中,我们要把A[3]的5插入恰当位置。这里首先将比5大的A[2]向后移动一个位置,然后把5插入A[2]。
之后同理,将已排序部分的其中一段向后移动,再把未排序部分的开头元素插入已排序部分的的恰当位置,插入排序的特点在于,只要0到i号元素全部排入已排序部分,那么无论后面如何插入,0到第i号元素都将永远保持排序完毕的状态。
实现插入排序时需要的主要变量如下:
A[N] | 长度为N的整形数组 |
i | 循环变量,表示未排序部分的开头元素 |
v | 临时保存A[i]值得变量 |
j | 循环变量,用于在已排序部分寻找v的插入位置 |
外层循环的i从1开始自增。在每次循环开始时,将A[i]的值临时保存在变量v中。
接下来是内部循环。我们要从已排序部分找出比v大的元素并让他们顺次后移一个位置。j从i-1,开始向前自减,同时将比v大的从A[j]移动到A[j+1],一旦j等于-1或者当前a[j]小于v则循环结束,并将v插入到j+1的位置。
分析:
在插入排序法中,我们只将比v(取出的元素)大的元素向后平移,不相邻的元素不会直接交换位置,因此整个排序算法十分稳定。
然后我们考虑一下插入排序的时间复杂度。这里需要估算每个i循环中A[j]元素向后移动的次数。最坏的情况下,每个i循环都需要执行i次移动,总共需要1+2+3+4+5+......+N-1=(N*N-N)/2次移动。即算法复杂度为N的平方,在N足够大的时候系数可以不计入复杂度
插入排序是一种很有趣的算法,输入数据的顺序能够直接影响时间复杂度。最好的情况就是刚好就是我们所需要的顺序,时间复杂度为N,最坏的情况就是数据有序且刚好我们所需要的顺序相反,时间复杂度为N平方。插入排序的优势就在于能快速处理相对有序的数据。
参考代码:
#include<stdio.h>
void print(int A[],int N){
int i;
for(i = 0;i < N;i++){
if(i > 0){
printf(" ");
}
printf("%d",A[i]);
}
printf("\n");
}
void insertSort(int A[],int N){
int i,j,v;
for(i = 1;i < N;i++){
v = A[i];
j = i -1;
while(j >= 0&&A[j] > v){
A[j+1] = A[j];
j--;
}
A[j+1] = v;
print(A,N);
}
}
int main(){
int N,i,j;
int A[100];
scanf("%d",&N);
for(i = 0;i < N;i++){
scanf("%d",&A[i]);
}
print(A,N);
insertSort(A,N);
return 0;
}