考研数据结构排序算法|折半插入排序、希尔排序

691 阅读6分钟

这是我参与8月更文挑战的第27天,活动详情查看:8月更文挑战

折半插入排序

对直接插入排序,每次找待插入位置k时,需依次从后向前查找,效率较低 可用折半查找改进此查找过程,即折半插入排序

image.png

折半插入排序代码

void  InsertSort(ElemType  A[],int  n){
int  i,j,low,high,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].key>A[0].key)
high=mid-1;
else
low=mid+1;
for(j=i-1; j>=high+1; --j)
A[j+1]=A[j]; 
A[high+1]=A[0];
}
}

注意:

  • 折半插入相比直接插入仅仅减少了比较元素的次数,对任一元素平均 比较次数从原来的O(n)变为O(logn)
  • 折半插入排序算法的每个元素平均 移动次数没有变化,依然是O(n)
  • 因此折半插入的平均时间复杂度为(logn+n)*n=O(𝑛2)
  • 折半插入排序是一个稳定的排序方法
  • 折半插入只适合排序表是顺序存储的情况

希尔排序

希尔排序又称为缩小增量排序,基本思想是:

① 取一个小于n的步长d1,将待排序表分割成d1个特殊子表L[i],L[i+d],...,L[i+kd]

② 对每个子表用直接插入排序,保证各个子表有序

③缩短步长d1到d2,重读①②

④ 重复③若干次后,此时整个表元素呈“基本有序”,只需对全体记录进行一次直接插入即可

image.png

希尔排序代码

void  ShellSort(ElemType  A[],int  n){
for(dk = n/2;dk>=1;dk=dk/2)     //步长变化
for(i =dk+1;i<=n;++i)
if(A[i].key<A[i-dk].key){    //将A[i]插入有序增量子表
A[0]=A[i];
for(j=i-dk; j>0&&A[0].key<A[j].key; j = j-dk)
A[j+dk]=A[j];         //记录后移,查找待插入位置 A[j+dk]=A[0];               //插入
}
}

注意:

  • 空间效率:仅使用常数个复制单元,空间复杂度为O(1)
  • 时间效率:由于步长d会影响时间效率,对应时间复杂度的严格证明数学上尚无定论,经试验一般认为约为O(𝑛1.3),最坏时间复杂度为O(𝑛2)
  • 适用性:希尔排序仅适用于线性表为顺序存储且不要求稳定性的情况

课外延申

9.2.2 折半插入排序

直接插入排序的基本操作是采用顺序查找当前记录在有序表中的插入位置,当然也可以采用折半查找(即二分查找)的方法来定位,相应的排序法称为折半插入排序。

将待插入记录与有序表中居中的记录按关键字比较,则将有序表一分为二,下次比较在其中一个有序子表中进行,将子表又一分为二。这样继续下去,直到要比较的子表中只有一个记录时,比较一次便确定了插入位置。其算法如下。

【算法9-2】折半插入排序的算法

img

算法分析如下。

(1)时间复杂度。

从时间复杂度看,采用折半插入排序可减少关键字的比较次数。确定插入位置所进行的折半查找,定位一个关键字的位置需要比较次数至多为折半判定树的深度,所以比较次数时间复杂度为O(nlog2n);折半插入排序移动记录的次数和直接插入排序相同,为O(n2)。

在平均情况下,折半插入排序仅减少了关键字的比较次数,而记录的移动次数不变。因此,折半插入排序的时间复杂度为O(n2)。

(2)空间复杂度。

折半插入排序所需辅助空间与直接插入排序相同,只用了一个辅助单元r[0],其空间复杂度为O(1)。

算法特点如下。

(1)是稳定的排序方法;

(2)只适用于顺序存储结构,不能用于链式结构;

(3)适用于初始记录无序且n较大时的情况。

9.2.3 希尔排序

直接插入排序算法简单,在n值较小时,效率比较高;在n值很大时,若待排序序列按关键字基本有序,效率依然较高,其时间效率可提高到O(n)。希尔排序正是从“减少记录个数”和“序列基本有序”这两点出发,给出插入排序的改进方法。

希尔排序又称缩小增量排序,是1959年由D.L.Shell提出来的,希尔排序的思想是:先选取一个小于n的整数di(称为步长),然后把排序表中的n个记录分为di个组,从第一个记录开始,间隔为di的记录为同一组,各组内进行直接插入排序。一趟之后,间隔di的记录有序,随着有序性的改善,减小步长di,重复进行,直到di=1,使得间隔为1的记录有序,也就是排序表达到了有序。

【例9-2】已知待排序序列为{39,80,76,41,13,29,50,78,30,11,100,7,41*,86},步长因子di分别取5,3,1,给出用希尔排序算法执行的过程。

希尔排序过程如图9-2所示。

img

图9-2 希尔排序过程示例

希尔排序的算法如下。

【算法9-3】希尔排序的算法

img

img

算法分析如下。

(1)时间复杂度。

从时间复杂度看,当增量大于1时,关键字较小的记录是跳跃地移动,从而使得在最后一趟增量为1的插入排序时,序列已基本有序,只需进行少量的比较和移动即可完成排序,因此希尔排序的时间复杂度比直接插入排序低。

希尔排序时间效率分析很困难,关键字的比较次数与记录移动次数依赖于步长因子di序列的选取,特定情况下可以准确估算出关键字的比较次数和记录的移动次数。目前尚未得到选取最好的步长因子序列的方法。

通过大量的实验基础推出:当n在某个特定范围内,希尔排序所需的比较和移动次数约为n1.3,当n→∞时,可减少到O(nlog2n)2。

(2)空间复杂度。

希尔排序所需辅助空间与直接插入排序相同,只用了一个辅助单元r[0],其空间复杂度为O(1)。

算法特点如下。

(1)是不稳定的排序方法;

(2)只适用于顺序存储结构,不能用于链式结构;

(3)步长因子di可以有各种取法,有取奇数的,也有取质数的,但需要注意:步长因子中除1外应没有公因子,且最后一个步长因子必须为l;

(4)算法中总的比较次数和移动次数都比直接插入排序少,n越大时,算法的时间效率效果越明显。适用于初始记录无序且n较大时的情况。