【考研】插入排序(直接插入+折半插入+希尔)

概述

排序

  1. 使关键字有序
  2. 设计排序算法指标:时间复杂度,空间复杂度

1、算法的稳定性

  1. 关键字相同的元素
  2. 相对位置不变
  3. 不能衡量算法优劣,需要根据特定的场景,

2、内排外排

  1. 排序时数据元素在内存中
  2. 排序时数据元素无法同时在内存中,需要内外移动,数据太多放不下
  3. 外排需要关注如何读写次数最少,因为需要读进去内存,
  4. 内排方法:比较移动
  5. 基数排序:不比较

推荐一个有用的网站

https://www.cs.usfca.edu/~galles/visualization/Algorithms.html

3、 注意点

  1. 同一线性表不同算法排序,结果可能不同
  2. 链表也可以排序
  3. 对任意序列基于比较的排序,求最少次数应该考虑最坏的情况
  4. 对任意n个关键字排序的次数至少log2(n!)向上取整

2、插入排序

  1. 基本思想:将待排序的元素根据大小插入有序序列
  2. 分为:直接插入,折半,希尔

2、1直接插入

2.1.1 过程概述
  1. 某一时刻状态:有序:L[i]:无序,
  2. 要将li插入有序序列:1、查找出li在有序序列中应该插入的位置k,2、将k到i-1的元素全部后移一个。3、将li赋值到lk
  3. 过程:l1看作有序,从l2开始插入
  4. 空间复杂度为O1
2.1.2代码:
void InsertSort(Element A[],int n){
int i,j;
for(i=2;i<=n;++i){//从第二个元素开始排序,0下标做了哨兵
if(A[i].key<A[i-1].key){//i小于前驱,需要往前找,插入到合适的位置
A[0]=A[i];//带插入元素赋值为哨兵,避免后移时被覆盖了
for(j=i-1;A[0].key<A[j].key;--j)//从后往前查找带插入的位置
A[j+1]=A[j];//向后挪
A[j+1]=A[0];//赋值到插入位置
}
}
2.2.3算法注意点
  1. 设置排序变量i从2开始,到n结束
  2. 第一个if语句时比较i和i-1的大小
  3. 移动前要复制到哨兵
  4. 这里你看到了哨兵的作用,没有哨兵还需呀判断j>0
  5. 只有带插入元素比前面的元素小才需要移动
  6. 移动过程:设置移动变量j,j从i-1开始,一直到带插入的哨兵大于j对应的元素,就把j后面一个元素的位置j+1腾出来,插入
2.2.4性能分析
2.2.4.1空间效率:

仅仅使用了常数个辅助单元,和问题规模没有关系
复杂度是O1

2.2.4.1时间效率:
  1. 向有序表中插入元素进行了n-1次
  2. 最好的情况:表中元素有序,每次插入,只需对比一次关键字不用移动,时间复杂度是On
  3. 最坏的情况:表中元素逆序:总的比较次数i从2到n相加求和,总的移动次数:i从三到n+1求和,
  4. 最坏时间复杂度On2
  5. 平均:n的平方除以4
  6. 与折半插入的时间复杂度相同On2,但是对于数量小的排序表,折半插入更好
  7. 排序稳定,
  8. 适用于顺序存储和链式存储的线性表,
  9. 链式存储时,可以从前往后查找元素的位置

2.2折半插入排序

2.2.1思路:
  1. 因为前一部分已经是有序的了,所以可以使用折半查找找到带插入的位置,然后在移动元素
  2. 只能用顺序存储来实现,涉及到折半的都用顺序存储来实现
2.2.2步骤
  1. 首先使用a0保存带插入的元素,为了防止被覆盖,折半查找就没有使用这个,他是查找的关键字直接在函数中给出的
  2. 举例:
    55(下标为0的哨兵) 20 30 40 50 60 70 80 55 60 90 10
    第一次:low=1,A[low]=20,high=7,A[high]=80
    在这里插入图片描述
    第二次
    在这里插入图片描述
    high=mid-1、low=mid+1
    在这里插入图片描述
    在这里插入图片描述
    注意:
    当折半查找找到了与带插入元素相同的元素时,以往的折半查找时停了下来,但是插入排序不可以停下来,需要继续往右边查找,为什么不是左边,因为需要保证稳定性
  3. 当low大于high时停止折半查找,
  4. low就是需要插入的位置
  5. 将low到i-1的元素全部往右边开始移动,
2.2.3代码
//折半查找排序
void InsertSort(int A[],int n){
int i,j,low,high,mid;
for(i=2;i<=n;i++){//依次将2到n插入到前面的有序序列中
A[0]=A[i];//将A[0]暂存到A[i];
low=1;high=i-1;//折半查找的范围
while(low<=high){
mid=(low+high)/2;
if(A[mid]>A[0])high=mid-1//带插入元素在mid的左边
else low=min+1//带插入元素在中间元素的右边
}
for(j=i-1;j>=high+1;--j){//为什么时high+1,因为最终的停止位之前就是high+1=low
A[j+1]=A[j];
A[high+1]=A[0];
2.2.4复杂度分析

关键字比较的次数少了,但是移动的次数不变,所以还是On2

2.2.5对链表的插入排序

移动的次数少了,,但是比较的次数不变,所以还是On2

2.3、希尔排序

2.3.1、基本思想

  1. 先追求元素的部分有序,在全局有序
  2. 将表拆分成几个子表
  3. 对各个子表进行直接插入排序
  4. 缩小增量d,重复上述过程
  5. 直到d=1结束

2.3.2、过程分析

1、初始状态在这里插入图片描述
第一趟:d1=4,分成四个表,间距为4的分成一组
在这里插入图片描述
然后、子表进行直接插入排序
在这里插入图片描述
第二趟:
在这里插入图片描述
d2=2;相聚距离为2的继续划分成子表
在这里插入图片描述
插入排序得到
在这里插入图片描述
第三趟:d3=1
最后这个表进行插入排序
得到最后结果
过程综述
在这里插入图片描述

// 插入排序
void shellsort(int A[],int n){
for(int dk=n/2;dk>=1;dk=dk/2){//确定分组长度
for(i=dk+1;i<=n;++i){//每一个分组的第二个元素,和直接插入排序中从第二个元素开始插入排序时一样的原理
A[0]=A[i];//i就是带插入元素
for(int j=i-dk;j>0&&A[0]<A[j];j-=dk)//和直接插入排序一样,只不过跨度变成了dk
A[j+dk]=A[0];
}
}

2.3.3、复杂度分析

  1. 使用了常数个辅助单元,复杂度O1
  2. 时间效率:一般On1.3,最坏On2
  3. 稳定性:划分到不同子表时可能改变相对次序
  4. 实用性:仅仅适用于线性表,顺序存储
  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值