三种插入排序详解和代码实现(直接插入排序、折半插入排序和希尔排序)

2024-08-25 11:20

本文主要是介绍三种插入排序详解和代码实现(直接插入排序、折半插入排序和希尔排序),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

  • 基本思想
  • 直接插入排序
    • 排序方法
    • 代码实现
    • 复杂度分析
  • 折半插入排序
    • 排序方法
    • 代码实现
    • 复杂度分析
  • 希尔排序
    • 排序方法
    • 代码实现
    • 复杂度分析
      • 最佳情况
      • 平均情况
      • 最坏情况
      • 增量序列的影响

基本思想

插入排序的基本思想是:每一趟将一个待排序的元素按照其关键字的大小插入到已经排好序的一组数据的适当位置,直到所有的待排序元素全部插入为止。
就像我们在打扑克时,每抓取一张牌,都将其插入到合适的位置,直到所有的牌摸完,我们就得到了一个有序的序列。

本文介绍三种插入排序的方法:直接插入排序折半插入排序希尔排序

直接插入排序

排序方法

  1. 将待排序的元素存放在数组中,数据元素的数量为 n n n
    1

  2. 每次选择一个元素将其放到前面的有序数组中,使得其仍然保持有序。共循环 n − 1 n-1 n1 次,第 k k k 次循环之后有序数组的长度为 k + 1 k+1 k+1
    2

代码实现

public class Test {public static void insertSort(int[] a) {for (int i = 1; i < a.length; i ++) {int key = a[i];int j = i - 1;// 数组元素后移直到key找到合适的位置while (j >= 0 && a[j] > key) {a[j + 1] = a[j];j --;}a[j + 1] = key;}}public static void main(String[] args) {int[] data = {5, 7, 4, 2, 0, 3, 1, 6};insertSort(data);for (int i : data) {System.out.print(i + " ");}}
}

复杂度分析

  1. 外层循环从 i = 1 i=1 i=1 开始,共执行了 n − 1 n-1 n1 次循环,内层循环中最坏情况下每次需要移动的次数为 O ( n ) O(n) O(n),因此时间复杂度为 O ( n 2 ) O(n^2) O(n2)
  2. 排序过程中只需要 O ( 1 ) O(1) O(1) 数量级的变量来记录状态,因此空间复杂度为 O ( 1 ) O(1) O(1)
  3. 直接插排由于在移动元素时可以是从后向前遍历移动,因此算法属于稳定排序。

折半插入排序

排序方法

  • 直接插入排序的基础上进行优化,在进行查找待插入元素位置操作的时候,我们可以利用二分法来实现(即折半查找)。

代码实现

public static void binaryInsertSort(int[] array) {for (int i = 1; i < array.length; i++) {int key = array[i];int left = 0;int right = i - 1;// 二分查找while (left <= right) {int mid = left + (right - left) / 2;if (key < array[mid]) {right = mid - 1;} else {left = mid + 1;}}int j = i - 1;while (j >= left) {array[j + 1] = array[j];j--;}array[left] = key;}
}

复杂度分析

  1. 折半插入排序的对象移动次数和直接插入排序相同,依赖于对象的初始排列方式。由于折半插入排序只是减少了元素之间的比较次数,并没有改变元素的移动次数。因此折半插入排序的时间复杂度仍为 O ( n 2 ) O(n^2) O(n2)
  2. 其空间复杂度也和直接插入排序相同,为 O ( 1 ) O(1) O(1)
  3. 折半插入排序也属于稳定排序,但是由于用到了二分查找,所以只能应用于顺序结构,不能用于链式结构。

希尔排序

排序方法

希尔排序实质上是采用分组插入的方法,将整个待排序的数组分为若干组,从而减少直接插入排序的数据量,对每个分组分别进行直接插入排序,然后增加每个分组的数据量重新进行排序。直至所有分组合并为一组。

  1. 确定一个增量 d 1 d_1 d1 ,并把间隔为 d 1 d_1 d1 的数据分入同一组中,在各组进行直接插入排序。
  2. 第二趟取增量 d 2 d_2 d2 ,重复上述分组和排序。
  3. 以此类推,直到所取的增量 d i = 1 d_i=1 di=1 d t < d t − 1 < . . . < d 2 < d 1 d_t<d_{t-1}<...<d_2<d_1 dt<dt1<...<d2<d1),所有数据在同一组中直接插入排序为止。

  1. 如图所示,第一趟取增量 d 1 = 4 d_1=4 d1=4,所有间隔为 4 4 4 的元素分为同一组,并进行直接排序。
    1

  2. 第二趟取增量 d 2 = 2 d_2=2 d2=2,所有间隔为 2 2 2 的元素分为同一组,并进行直接排序。
    2

  3. 第三趟取增量 d 3 = 1 d_3=1 d3=1,所有元素分为同一组,并进行直接排序,排序完成。
    3

代码实现

public static void shellSort(int[] array) {int n = array.length;// 每次间隔折半for (int gap = n / 2; gap > 0; gap /= 2) {// 同组元素直接插入排序for (int i = gap; i < n; i++) {int key = array[i];int j = i;while (j >= gap && array[j - gap] > key) {array[j] = array[j - gap];j -= gap;}array[j] = key;}}
}

复杂度分析

希尔排序的空间复杂度与上述两种排序相同,均为 O ( 1 ) O(1) O(1)
记录跳跃式的移动导致了希尔排序算法是不稳定的。

希尔排序的时间复杂度取决于所使用的增量序列(gap sequence)。以下是对希尔排序时间复杂度的详细分析

最佳情况

  • 时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)(在某些特定的增量序列下,如Hibbard增量序列)
  • 描述:当增量序列优化得当时,希尔排序的性能接近于 O ( n l o g n ) O(nlogn) O(nlogn)。这意味着希尔排序在最优情况下的性能与归并排序相近。

平均情况

  • 时间复杂度 O ( n 3 2 ) − O ( n 5 4 ) O(n^\frac{3}{2})-O(n^\frac{5}{4}) O(n23)O(n45)
  • 描述:对于大多数常用的增量序列(如Shell增量序列、Knuth增量序列等),希尔排序的平均时间复杂度通常介于 O ( n 3 2 ) O(n^\frac{3}{2}) O(n23) O ( n 5 4 ) O(n^\frac{5}{4}) O(n45) 之间。

最坏情况

  • 时间复杂度 O ( n 2 ) O(n^2) O(n2)(例如,使用简单的增量序列,如 n 2 , n 4 , . . . , 1 \frac{n}{2},\frac{n}{4},...,1 2n4n...1
  • 描述:在使用不优化的增量序列时,希尔排序的时间复杂度可能会退化到 O ( n 2 ) O(n^2) O(n2),类似于简单插入排序的时间复杂度。

增量序列的影响

  1. Shell增量序列:初始增量为 n 2 \frac{n}{2} 2n,然后逐步减小到 1 1 1。时间复杂度为 O ( n 2 ) O(n^2) O(n2)
  2. Hibbard增量序列:增量为 1 , 3 , 7 , 15 , . . . , 2 k − 1 1, 3, 7, 15, ..., 2^k-1 1,3,7,15,...,2k1。时间复杂度为 O ( n 3 2 ) O(n^\frac{3}{2}) O(n23)
  3. Knuth增量序列:增量为 1 , 4 , 13 , 40 , 121 , . . . , 3 k − 1 2 1, 4, 13, 40, 121, ..., \frac{3^{k-1}}{2} 1,4,13,40,121,...,23k1。时间复杂度为 O ( n 5 4 ) O(n^\frac{5}{4}) O(n45)
  4. Sedgewick增量序列:例如, 1 , 5 , 19 , 41 , 109 , . . . 1, 5, 19, 41, 109, ... 1,5,19,41,109,... 时间复杂度为 O ( n 4 3 ) O(n^\frac{4}{3}) O(n34)

这篇关于三种插入排序详解和代码实现(直接插入排序、折半插入排序和希尔排序)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1105416

相关文章

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

OpenHarmony鸿蒙开发( Beta5.0)无感配网详解

1、简介 无感配网是指在设备联网过程中无需输入热点相关账号信息,即可快速实现设备配网,是一种兼顾高效性、可靠性和安全性的配网方式。 2、配网原理 2.1 通信原理 手机和智能设备之间的信息传递,利用特有的NAN协议实现。利用手机和智能设备之间的WiFi 感知订阅、发布能力,实现了数字管家应用和设备之间的发现。在完成设备间的认证和响应后,即可发送相关配网数据。同时还支持与常规Sof

【数据结构】——原来排序算法搞懂这些就行,轻松拿捏

前言:快速排序的实现最重要的是找基准值,下面让我们来了解如何实现找基准值 基准值的注释:在快排的过程中,每一次我们要取一个元素作为枢纽值,以这个数字来将序列划分为两部分。 在此我们采用三数取中法,也就是取左端、中间、右端三个数,然后进行排序,将中间数作为枢纽值。 快速排序实现主框架: //快速排序 void QuickSort(int* arr, int left, int rig

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

活用c4d官方开发文档查询代码

当你问AI助手比如豆包,如何用python禁止掉xpresso标签时候,它会提示到 这时候要用到两个东西。https://developers.maxon.net/论坛搜索和开发文档 比如这里我就在官方找到正确的id描述 然后我就把参数标签换过来

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

usaco 1.3 Mixing Milk (结构体排序 qsort) and hdu 2020(sort)

到了这题学会了结构体排序 于是回去修改了 1.2 milking cows 的算法~ 结构体排序核心: 1.结构体定义 struct Milk{int price;int milks;}milk[5000]; 2.自定义的比较函数,若返回值为正,qsort 函数判定a>b ;为负,a<b;为0,a==b; int milkcmp(const void *va,c