直接插入排序和折半插入排序
直接插入排序
直接插入排序属于插入排序。插入排序的思想是:每次将一个待排序的元素按关键字大小插入前面已经排好序的子序列,初始时假定第一个元素是已排序的子序列。
直插排序的思想
- 查找出L(i)在L[1…i-1]中的插入位置k;
- 将L[k…i-1]中所有元素依次后移一个位置;
- 将L(i)复制到L(k);
实现代码
直插排序的实现借助哨兵
void InsertSort(ElemType A[],int n){
int i,j;
for(i=2;i<n;i++){
if(A[i]<A[i-1]){
A[0] = A[i]; //设置哨兵
for(j=i-1;A[0]<A[j];j--){//查找待插入位置
A[j+1] = A[j];//元素后移
}
A[j+1] = A[0];
}
}
}
性能分析
-
空间效率
仅使用常数个辅助空间,空间复杂度为O(1)。
-
时间效率
向有序子表里执行插入操作一共进行了n-1次,每次的插入操作分为比较关键字和移动元素,比较次数和移动次数取决于表的初始状态。最好情况下,数组序列已经有序,只需要比较n-1次,不需要移动元素,时间复杂度为O(n);最坏情况下,数组序列从大到小排列,此时总的比较次数和移动次数都达到最大,
总的比较次数为:
∑ i = 2 n i \sum _{i=2}^ni i=2∑ni
总的移动次数为:
∑ i = 2 n ( i + 1 ) \sum _{i=2}^n(i+1) i=2∑n(i+1)
平均情况下,取最好和最坏情况下的平均值作为平均情况下的时间复杂度,总的比较次数和移动次数均约为n*n/4
因此直插算法的时间复杂度为O(n^2)。
-
稳定性
直插排序是一个稳定的排序算法
-
适用性
直插排序适用于顺序存储和链式存储的线性表,为链式存储时,可以从前往后查找指定元素的位置。
折半插入排序
直接插入排序是一边比较元素大小一边移动元素,折半插入排序是将比较和移动两个操作分离,即先折半查找元素的插入位置,再统一移动待插入插入位置之后的元素。折半查找是在排序表为顺序表时对直接插入排序算法的改进,仅对查找插入位置进行改进(使用折半查找)。
实现代码
void InsertSort(ElemType A[],int n){
int i,j,high,low,mid;
for(i=2;i<n;i++){
A[0] = A[i];
low = 1; high = i-1;
while(low <= high){
mid = (low+high)/2;
if(A[mid]<A[0]) low = mid+1;
else high = mid-1;
}
for(j=i-1;A[0]<A[j];j--){
A[j+1] = A[j];
}
A[j+1] = A[0];
}
}
性能分析
-
空间复杂度
与直接插入排序一样为O(1)。
-
时间复杂度
折半插入排序的查找次数的时间复杂度为O(nlog_2(n)),查找次数与初始状态无关,仅取决于表中元素的个数移动次数的复杂度与
直接插入排序相同,依赖于排序表的初始状态;平均复杂度为O(n^2)。
-
适用性
适用于数据量不多的排序表。
-
稳定性
折半插入排序是一种稳定的排序方法。