插入排序
1.直接插入排序
基本思想:直接插入排序是一种最简单的排序方法,它的基本操作是将一个记录插到已排序好的有序表中,从而得到一个新的,记录数增 1 的有序表。
整个排序过程为 n-1 趟插入,即先将序列中第 1 个记录看成是一个有序子序列,然后从第 2 个记录开始,逐个进行插入,直至整个序列有序。
取要插入的元素,直接插在一个有序序列的合适位置。
排序过程:
将第一个元素 r[1] 作为已排序有序序列;
其他元素r[2],r[3],r[4],r[5],...r[n]作为未排序序列;
循环:
从未排序序列中依次取一个元素;(循环从i=2,3,4,5...)
取一个元素后进行判断:
如果所取元素小于当前有序序列的序尾,则:
(1)将所取元素赋值给r[0], r[0] 当作哨兵。
(2)循环:
在当前有序序列中寻找自己的位置,进行插入:
从当前有序序列的序尾开始,倒着往前,将哨兵与相应元素逐个比较:
哨兵比当前元素小,当前元素就往后移一个位置;再往前比,若哨
兵比当前元素还小,当前元素再往后移一个位置 ...直到哨兵大于
等于当前元素,循环结束,将哨兵插入到当前元素的后面。
如果所取元素大于等于有序序列的序尾,则保持原存储位置不变。
其他元素r[2],r[3],r[4],r[5],...r[n]作为未排序序列;
循环:
从未排序序列中依次取一个元素;(循环从i=2,3,4,5...)
取一个元素后进行判断:
如果所取元素小于当前有序序列的序尾,则:
(1)将所取元素赋值给r[0], r[0] 当作哨兵。
(2)循环:
在当前有序序列中寻找自己的位置,进行插入:
从当前有序序列的序尾开始,倒着往前,将哨兵与相应元素逐个比较:
哨兵比当前元素小,当前元素就往后移一个位置;再往前比,若哨
兵比当前元素还小,当前元素再往后移一个位置 ...直到哨兵大于
等于当前元素,循环结束,将哨兵插入到当前元素的后面。
如果所取元素大于等于有序序列的序尾,则保持原存储位置不变。
代码实现
算法:
void Insertsort()
{ for( i=2;i<=L;i++ ) // 依次插入r[2],r[3],……r[n]
{ if ( r[i].key<r[i-1].key )
{ r[0]=r[i]; // 置监视哨
j=i-1;
while(r[0].key<r[j].key) // 查找r[i]的位置
{ r[j+1]=r[j]; // 向后移动记录
j- -;
}
r[j+1]=r[0]; // 插入r[i]
}
}
}
效率分析:
空间效率:仅用了一个辅助单元,辅助空间为O(1)。
时间效率:向有序表中逐个插入记录的操作,进行了n-1趟,每趟操作分为比较关键码和移动记录,而比较的次数和移动记录的次数取决于待排序列按关键码的初始排列。
最好情况下:即待排序列已按关键字有序,每趟操作只需1次比较2次移动。
总比较次数=n-1次
总移动次数=2(n-1)次
最坏情况下:即第j趟操作,插入记录需要同前面的j个记录进行j次关键码比较,移动记录的次数为j+2次。
直接插入排序的时间复杂度为O(n2),辅助空间为O(1)。
直接插入排序是稳定的排序方法。
直接插入排序最适合待排序关键字基本有序的序列。
2.二分插入排序
基本思想:
直接插入算法虽然简单,但当记录数量 n 很大时,比较次数将大大增加,对于有序表(限于顺序存储结构),为了减少关键字的比较次数,可采用二分插入排序。
二分插入排序的基本思想是:
用二分查找法在有序表中找到正确的插入位置,然后移动记录,空出插入位置,再进行插入
二分插入排序的基本思想是:
用二分查找法在有序表中找到正确的插入位置,然后移动记录,空出插入位置,再进行插入
(1)取关键字653,与序列中间位置①的关键字比较,653>275,在后半区继续找;
(2)再与后半区中间位置②的关键字比较,653>512,再继续在后半区找;
(3)再与后半区中间位置③的关键字比较,653<897,经三次比较找到插入位置③,然后插入653。
(2)再与后半区中间位置②的关键字比较,653>512,再继续在后半区找;
(3)再与后半区中间位置③的关键字比较,653<897,经三次比较找到插入位置③,然后插入653。
算法:
void BInsSort()
{ for(i=2;i<=n;i++)
{ r[0]=r[i]; // 将r[i] 暂存到r[0]
while(low<=high) // 在 r[ low...high ] 中折半查找,确定所取元素在有序
序列中的大致插入位置。
当low>high时,循环结束,大致插入位置已确定。
{ m=(low+high)/2; // 折半
if(r[0].key<r[m].key)
high=m-1; // 确定新high,插入点在低半区:
即: 原low,新 high 之间
else
low=m+1; // 确定新low, 插入点在高半区:
即:新low, 原 high 之间
}
for(j=i-1;j>=high+1;- -j)
r[j+1]=r[j]; // 记录后移,直到空出 r[high+1]为止
r[high+1]=r[0];
} // 插入
}
二分插入排序辅助空间和直接插入相同,为O (1) 。
从时间上比较,二分插入仅减少了比较次数,而记录的移动次数不变,时间复杂度仍为O(n2)。
二分插入排序是稳定的排序方法。
二分插入排序是稳定的排序方法。
3.希尔排序(Shell’s Sort)
希尔排序又称“缩小增量排序”,它也是一种插入排序方法,
但在时间上较前两种排序方法有较大的改进。
但在时间上较前两种排序方法有较大的改进。
基本思想:
先将整个待排序记录序列分割成若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序时”,再对全体记录
进行一次直接插入排序。
特点:
子序列不是简单的逐段分割,而是将相隔某个“增量”的记录组成一个子序列, 所以关键字较小的记录不是一步一步地前移,
而是跳跃式前移,从而使得在进行最后一趟增量为 1 的插入排序
时,序列已基本有序,只要做少量比较和移动即可完成排序,时
间复杂度较低。
排序过程:
先取一个正整数 d1<n ,把所有相隔 d1 的记录放一组,
组内进行直接插入排序;然后取 d2<d1,重复上述分组和排序
操作;直至di=1,即所有记录放进一个组中排序为止
组内进行直接插入排序;然后取 d2<d1,重复上述分组和排序
操作;直至di=1,即所有记录放进一个组中排序为止
算法
void Shellsort()
{ gap=n/2; // 初次增量取序列元素个数n的一半为步长
while(gap>0)
{ for ( i=gap+1; i<=n; i++)
{ j=i-gap;
while (j>0)
{ if ( r[j]>r[j+gap] ) // 对子序列作直接插入排序
{ x=r[j];
r[j]=r[j+gap];
r[j+gap]=x;
j=j-gap;
}
else j=0;
}
}
gap=gap/2; // 每次减半,直至步长为 1
}
}
效率分析:
希尔排序可提高排序速度:分组后 n 值减小,n²更小。在大量实验基础上可推出:当 n 在某
个特定范围内希尔排序所需的比较和移动次数约为 n1.3 , 所以其平均时
间复杂度约为O( n1.3)。其辅助空间为O(1)。
关键字较小的记录跳跃式前移,在进行最后一趟增量为 1 的插入排
序时,序列已基本有序。
希尔排序为不稳定排序。
增量序列取法:
没有除1以外的公因子。
最后一个增量值必须为1。