前言
上一篇在聊时间复杂度和空间复杂度时,没有按指定格式显示(明明预览的时候没问题的),强迫症的我稍微优化了一下重新发布,目的就是让小伙伴看着舒服。
上次聊到的直接插入排序在比较有序数据和待插入数据时,是通过依次遍历的方式进行比较,当数据量比较大时,得考虑进一步优化;折半插入排序就是通过减少有序数据与待插入数据的比较次数,从而提升效率。
正文
1. 先来熟悉一下折半查找
1.1 折半查找算法思想
折半查找又称二分查找,仅适用于有序的顺序表;
思想(假设顺序表是升序的):
-
首先将指定值与顺序表中中间位置元素进行比较;
-
若相等,代表找到,则返回该元素的位置;
-
若不等,需继续查找;
若指定的值小于顺序表中中间元素,则查找前半部分;
若指定的值大于顺序表中中间元素,则查找后半部分;
重复以上过程,直到找到元素为止;或者找完所有数据为止,即查找失败;
1.2 折半查找实现及解析
算法代码如下(在升序顺序表中查数据)
image-20210327174929319执行结果如下:
image-20210327175057909解析查找步骤过程,如下:
图中分别使用红、绿、黄箭头所指的数据分别高、中、低索引位,蓝色为需要在顺序表中查找的数。
能查到数据的步骤:
image-20210328000114746上图步骤说明:
-
第1步将low初始为0,high初始赋值为顺序表中的元素个数减1,这里为5;当循环进来时将mid赋值为(low+high)/2,因为mid为int类型,会取整,这里就得到mid为2;然后将索引位为2的数据66与需要查找的数据92进行比较,发现92大于66,需继续在后半部分查找,所以将low的值改为mid+1,即为3;
-
第2步进入循环,继续将mid的赋值为(low+high)/2,因为上一步low的值为3,high的值不变,还是为5,所以得出的mid值为4,然后将索引位为4的数据92与需要查找的数据92进行比较,两者相等,代表已经找到,返回当时找到的位置mid。
查找失败时的情况:
image-20210328000247255上图步骤说明:
-
第1步将low初始为0,high初始赋值为顺序表中的元素个数减1,这里为5;当循环进来时将mid赋值为(low+high)/2,因为mid为int类型,会取整,这里就得到mid为2;然后将索引位为2的数据66与需要查找的数据921进行比较,发现921大于66,需继续在后半部分查找,所以将low的值改为mid+1,即为3;
-
第2步进入循环,继续将mid的赋值为(low+high)/2,因为上一步low的值为3,high的值不变,还是为5,所以得出的mid值为4,然后将索引位为4的数据92与需要查找的数据921进行比较,发现921大于92,需继续在后半部分查找,所以将low的值改为mid+1,即为5;
-
第3步进入循环,继续将mid的赋值为(low+high)/2,因为上一步low的值为5,high的值不变,还是为5,所以得出的mid值为5(这里高、中、低都指向同一位置),然后将索引位为5的数据100与需要查找的数据921进行比较,发现921大于100,需继续在后半部分查找,所以将low的值改为mid+1,即为6;
-
第4步进入循环时,low在第3步时变为6,high还是没变,依然是5,low大于high,不满足循环条件,代表已经查询完成,但没有查询到数据,跳出循环,返回-1;
1.3 分析折半查找算法性能
时间复杂度
如果传入的数据规模为n,即有n个元素; 第一次在 n/2个元素中查找,第二次在n/(22)个元素中查找,第三次在n/(23)个元素中查找,假如经过x次查找到元素,则得到时间复杂度为O(log2n);
空间复杂度
因为在查找过程中,用到了固定的几个中间变量(low,mid,high),所以算法过程中消耗的内存是一个常量级别的,则空间复杂度为O(1);
稳定性
由于在算法过程中只是查找,不改变元素的位置,则折半查找算法是稳定的。
综上所述,插入排序的时间复杂度为O(log2n),空间复杂度为O(1),是稳定算法;
2. 搞明白折半插入排序
2.1 折半插入排序算法思想
折半插入排序是对直接插入排序的优化,直接插入排序在比较过程中依次遍历有序列表中的元素和待插入数据比较,而折半插入排序是将原来的依次遍历有序列表换成折半查找算法,提升比较效率,找到合适位置之后,对应的元素需要向后移位,然后将待插入元素插入到腾出的空位即可;重复到排序完成为止。
2.2 折半插入排序算法实现与解析
代码实现(升序):
image-20210329104257982运行效果如下:
image-20210329104425770步骤解析如图:
image-20210329123521476步骤说明:
图中绿线框部分代表是已经排好序的列表,箭头指的元素是下一个待插入的元素,黄线框部分为剩下的无序元素。黄方块为每次折半查找到的mid位置,绿方块表示最后有序列表腾出的位置。
-
将原始数据array复制到新数组中arrayb中,这步的主要目的是后续不需要声明额外临时变量,也为了后续核心代码实现逻辑简单易懂,减少过多的判断;
-
第1步将第一个元素作为有序列表(第一元素为2),下一个待插入的元素为5,将5放入哨兵位置,即索引为0的位置;然后折半查找,初始low为1,high为第一次也为1;因为刚开始有序列表中只有一个元素,则找到就是2,与哨兵位的值5比较,2小于5,需要继续在有序列表的后半部分查找,改变low为mid+1,此时为2,大于high,跳出循环;不需要移动位置,保持当前位置不变。
-
第2步时,有序列表中的元素为2、5,下一个待插入的元素为6,将6放入哨兵位置,即索引为0的位置;然后折半查找:
第2-1步 初始low为1,此时计算出high的值为2;根据low和high计算出mid为1(因为是mid是整数,所以3除以2,取整为1),将mid位的值2与待插入元素6比较,2小于6,需要继续在有序列表中的后半部分继续查找,则改变low的值,为mid加1,得到low为2;
第2-1步 上一步得到low为2,high的值仍然为2;根据low和high计算出mid为2,将mid位的值5与待插入元素6比较,5小于6,需要继续在有序列表中的后半部分继续查找,则改变low的值,为mid加1,得到low为3;不满足循环条件,退出折半;不需要移动位置,保持当前位置不变;
-
第3步时,有序列表中的元素为2、5、6,下一个待插入的元素为1,将1放入哨兵位置,即索引为0的位置;然后折半查找:
第3-1步 初始low为1,此时计算出high的值为3;根据low和high计算出mid为2,将mid位的值5与待插入元素1比较,5大于1,需要继续在有序列表中的前半部分继续查找,则改变high的值,为mid减1,得到high为1;
第3-2步 初始low为1,上一步计算出high的值为1;根据low和high计算出mid为1,将mid位的值2与待插入元素1比较,2大于1,需要继续在有序列表中的前半部分继续查找,则改变high的值,为mid减1,得到high为0;继续循环是low大于high,不满足条件,跳出循环;
第3-3步 根据折半查找比较,得出合适位置为high+1,即需要将待插入元素插入到1位置,需要将2、5、6三个元素依次向后移位,腾出索引位1的位置,将待插入元素1插入到此索引位。
-
第4步时,有序列表中的元素为1、2、5、6,下一个待插入的元素为9,将9放入哨兵位置,即索引为0的位置;然后折半查找:
第4-1步 初始low为1,此时计算出high的值为4;根据low和high计算出mid为2(因为是mid是整数,所以5除以2,取整为2),将mid位的值2与待插入元素9比较,2小于9,需要继续在有序列表中的后半部分继续查找,则改变low的值,为mid+1,得到low为3;
第4-2步 上一步计算出low为3,high的值仍然为4;根据low和high计算出mid为3(因为是mid是整数,所以7除以2,取整为3),将mid位的值5与待插入元素9比较,5小于9,需要继续在有序列表中的后半部分继续查找,则改变low的值,为mid+1,得到low为4;
第4-3步 上一步计算出low为4,high的值仍然为4;根据low和high计算出mid为4,将mid位的值6与待插入元素9比较,6小于9,需要继续在有序列表中的后半部分继续查找,则改变low的值,为mid+1,得到low为5;继续循环是low大于high,不满足条件,跳出循环;
-
第5步是,有序列表中的元素为1、2、5、6、9,下一个待插入的元素为3,将3放入哨兵位置,即索引为0的位置;然后折半查找:
第5-1步 初始low为1,此时计算出high的值为5;根据low和high计算出mid为3,将mid位的值5与待插入元素3比较,5大于3,需要继续在有序列表中的前半部分继续查找,则改变high的值,为mid减1,得到high为2;
第5-2步 初始low为1,上一步计算出high的值为2;根据low和high计算出mid为1(3除以2取整得1),将mid位的值1与待插入元素3比较,1小于3,需要继续在有序列表中的后半部分继续查找,则改变low的值,为mid加1,得到low为2;
第5-3步,上一步计算出low为2,第5-1步计算出high为2;根据low和high计算出mid为2,将mid位的值2与待插入元素3比较,2小于3,需要继续在有序列表中的后半部分继续查找,则改变low的值,为mid加1,得到low为3;继续循环,不符合循环条件,循环终止
第5-4步 根据折半查找比较,得出合适位置为high+1,即需要将待插入元素插入到3位置,需要将5、6、9三个元素依次向后移位,腾出索引位3的位置,将待插入元素3插入到此索引位。最终得到排序结果1、2、3 、5 、6 、9;
2.3 折半插入排序算法分析
时间复杂度
在算法过程中有两层循环,第一层需要遍历所有元素,则时间复杂度为O(n);第二层循环中包含两部分算法,第一步是通过折半算法找位置,时间复杂度在刚开始已经分析,为O(log2n);第二步是找到位置之后需要腾出空位,需要将对应元素移位,时间复杂度为O(n);则整体算法的时间复杂度为外层循环的时间复杂度乘以内层循环的时间复杂度,去掉系数和常数,取大的,得出结果为O(n2);
空间复杂度
在算法核心部分只采用了固定的几个中间变量(i,j,low,mid,high,arrayb[0]),所以算法过程中消耗的内存是一个常量,则空间复杂度为O(1);
稳定性
由于在算法过程中采用折半算法找位置的,使用大于符号进行比较值,所以当遇到相等数据时,位置不会受到改变,则折半插入算法是稳定的。
综上所述,折半插入排序的时间复杂度为O(n2),空间复杂度为O(1),是稳定算法;
总结
这里说到两种算法,折半查找(二分查找)算法,折半插入排序算法;最终关于插入排序算法的思想没变,只是在比较有序列表时做了优化; 对于小数据量的排序,感觉不到优化,当数据量大时,比较效率就明显提升啦; 如果不明白的小伙伴调试代码试试,再不行可以留言,有时间会及时回复。
感谢小伙伴的:点赞、收藏和评论,下期继续~~~
一个被程序搞丑的帅小伙,关注"Code综艺圈",跟我一起学~~~
网友评论