基本思想
- 将待排序列分成 已排 、未排 两部分,初始时,已排序列仅含1个元素(待排序列的第1个元素);
- 将未排序列的元素逐个插到前面已排序列
直插法图示
代码
// 升序的直接插入排序
void InsertSort(int A[],int n){
int i,j,temp;
//逐个遍历未排序列的元素,将其插到已排序列中
for(i=1;i<n;i++){
if(A[i]<A[i-1]){
temp = A[i]; //A[i]暂存于temp
for(j=i-1;j>=0 && A[j]>temp;--j)
//若待插temp < A[j],则元素后移,否则跳出
A[j+1] = A[j];
//若A[j] < temp,升序排=>待插元素temp放到A[j]后面
A[j+1] = temp;
}//if
}//for
}
图示(结合代码看)
带哨兵版
区别
A[0]空出来,代替temp的作用(暂存待插元素),初始元素从A[1]开始存。
代码
// 升序的直接插入排序
void InsertSort(int A[],int n){
int i,j;
//将A[2]~A[n]插到前面已排序列
for(i=2;i<=n;i++){
if(A[i]<A[i-1]){ //若A[i]<前驱,(升序排)则A[i]为待插元素
A[0] = A[i]; //A[i]暂存于A[0]——哨兵
for(j=i-1;A[0]<A[j];--j)
//若待插A[0] < A[j],则已排序列中A[j]及其以后的元素后移,否则跳出
A[j+1] = A[j];
//若A[0] < A[j],升序排=>待插元素A[0]放到A[j]后面
A[j+1] = A[0];
}//if
}//for
}
图示(结合代码看)
算法效率
1、空间复杂度
O(1) —— 因为原地排序,且未借助其他数据结构作辅助空间
2、时间复杂度:
来自关键代码,可理解为关键字对比次数、元素移动次数
若有n个元素,则要进行 n-1 次趟处理
每趟含 关键字对比 与 元素移动 ,即插完一个待查元素算1趟,所以从 A[1] 至 A[n-1] 或哨兵版的 A[2] 至 A[n],共 n-1 个待插元素,即n-1趟处理
- 最好情况:初始顺序与要求排好的顺序一致,即原本升序,要求排成升序;原本逆,要求逆。共n-1趟处理,每趟对比关键字1次,无需移动。——关键代码执行n-1次,时间复杂度为O(n)
- 最坏情况:初始顺序与要求排好的顺序相反,即原本升序,要求逆序。共 n-1 趟处理,时间复杂度为 O(n²)
- 平均时间复杂度: O(n²) ——这个还没搞懂,书上说,取最好最坏的平均,总比较与总移动次数均约 n²/4
最坏情况要看代码分析,if条件 和 if 里面 for循环的条件 都涉及关键字对比。而赋哨兵或者说暂存待插元素也算一次元素移动,再加上原本所需的元素移动共同构成了关键代码的执行次数,关键代码的重复执行次数即频度即时间复杂度。
void InsertSort(int A[],int n){
int i,j;
//走 n-1 趟
for(i=2;i<=n;i++){
if(A[i]<A[i-1]){ //每趟对比 1 次
A[0] = A[i]; //赋哨兵,每趟执行 1 次,算在元素移动里面
//最坏情况下,A[1] 到 A[i-1] 均需与哨兵A[0]做对比、均要移动
for(j=i-1;A[0]<A[j];--j) //每趟执行 i-1 次对比
A[j+1] = A[j]; //每趟执行 i-1 次元素移动
A[j+1] = A[0]; //每趟执行 1 次,算在元素移动里面
}//if
}//for
}
分析如下:
第1趟,i = 2,对比 1+(i-1)=i = 2次;移动 1+(i-1)+1=i+1 =3次
第2趟,i = 3,对比 3 次,移动 4 次
第 k 趟,i = k+1 ,对比 k+1 次,移动 k+2 次
第 n-1 趟,i = n ,对比 n 次,移动 n+1 次
对比次数求和 + 移动次数求和 => 时间复杂度为 O(n²)
算法分析
1、算法稳定
2、适用于顺序、链式存储的线性表(链式—从后向前查元素位置)
3、原地排序,空间复杂度 = O(1)
4、时间复杂度,最好O(n),最坏、平均为O(n²)
上述图片取自王道课件。内容用于自学,如有错误,还请各位指正!