数据结构初阶(12)排序算法—插入排序(插入、希尔)(动图演示)

2. 常见排序算法的实现

2.0 十大排序算法

2.1 插入排序

2.1.1 基本思想

直接插入排序是一种简单的插入排序法:

基本思想

  • 把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中
  • 直到所有的记录插入完为止,得到一个新的有序序列 。

比 + 挪 (+ 插)


实际中我们玩扑克牌时,就用了插入排序的思想。

插入排序的思想:已经有一个有序序列,再新加入一个数据,将这个新加入的数据插入到合适的位置,保持整个序列依旧保持有序。

2.1.2 直接插入排序

基本逻辑

当插入第i(i>=1)个元素时,前面的array[0],array[1],…,array[i-1]已经排好序,此时用array[i]的排序码与array[i-1],array[i-2],…的排序码顺序进行比较(依次进行比较)找到插入位置即将array[i]插入,原来位置上的元素顺序后移。

动图演示

算法步骤

1. 初始化:

  • 将数组的第一个元素(arr[0])视为已排序部分。
  • 其余部分(arr[1]到arr[n-1])视为未排序部分。

2. 遍历未排序部分:

  • 从i=1到n-1(n为数组长度),依次取出arr[i]作为待插入元素 key(key = a[i])。

3. key从右向左比较,key较小则依次向后挪动路径上的已排好序的数据

  • 初始化 j = i-1(即已排序部分的最后一个元素)。
  • 如果 key < arr[j],则arr[j]向后移动一位(arr[j+1] = arr[j]),并继续向左比较(j--)
  • 直到找到arr[j] ≤ key或j<0(即已比较完所有已排序元素)。

4. 将key插入到正确位置:

  • 将key 插入到arr[j+1]的位置。(arr[j+1] = key)

5. 重复上述过程,直到所有未排序元素都被处理。

优化思路:

  • 二分查找优化:在已排序部分使用二分查找快速找到插入位置,减少比较次数
    (但仍需相等的挪动次数,优化程度有限,代码变复杂,不建议)

终止条件 :

  • 外层循环终止:当所有未排序元素(i 从 1 到 n-1)都被处理完毕时,排序结束。
  • 内层循环终止
    • 当 j < 0(即已比较完所有已排序元素)。
    • 或 arr[j] ≤ key(找到正确插入位置)。
代码实现

建议排序算法部分的代码,先实现单趟,再实现整体——更好控制。

一上来就直接进行排序整体控制——不好控制。

先考虑单趟——把一个数插入一个有序数组 [0, end] end+1 。

end的起始位置在0,然后依次把后面一个元素当作end+1插入前面的有序数组

1. 先考虑单趟

  • 先保存待插入数据,再把前面的数据后挪(否则数据被覆盖)
  • 注意比较逻辑,将算法控制为一个稳定算法
  • 两个循环结束条件,循环结束后插入tmp。

2.再考虑整体

  • 每趟外循环给end、tmp赋值。
  • end的结束位置在n-2,外循环的结束条件是i < n-1(若 i < n 会越界访问)
//(直接)插入排序
void InsertSort(int* a, int n)
{//2.再考虑循环for (int i = 0; i < n - 1; i++)	//若i<n则会导致插入一个随机数-858993460作首元素{//1.先考虑单趟——把一个数插入一个有序数组//往[0, end]的有序数组中插入end+1//每趟外循环给end赋值——end的起始位置在0int end = i;//然后依次把后面一个元素当作end+1插入前面的有序数组——end的结束位置在n-2int tmp = a[end + 1];	//先保存待插入数据,再把前面的数据后挪(否则数据被覆盖)while (end >= 0)		//end小于0,循环比较截止,tmp插入到end后面{//如果tmp较小if (tmp < a[end]){//排好的数据就往后挪,给tmp腾位置a[end + 1] = a[end];//迭代--end;}//如果tmp较大或相等,前排数据就不后挪——>就可以做到稳定else{break;			//tmp较大或相等,循环比较截止,tmp插入到end后面}}//tmp比某值大(相等) / 比全部小(end = -1),都tmp插入到end后面a[end + 1] = tmp;}
}

代码测试。

也可以在监视中观察每一趟排序的过程。

性能分析

时间复杂度——简单粗暴,两层循环就是 O(N^2)——错(不能只看循环次数)

最坏情况:逆序——1,2,3,......,n-1——>合计O( N^2 )

最好情况是多少:顺序有序或者接近有序  O(N)

有序为什么不是O(1)——没有排序能做到O(1),最好就是O(N)——极限情况下。

有序也得O(N)比完才知道有序。

正常最快就是O(N*logN)。

时间复杂度:

情况时间复杂度说明
最优情况(已排序数组)O(N)每趟只需比较一次,无需移动元素
最坏情况(完全逆序)O(N^2)每次插入需比较和移动所有已排序元素
平均情况(随机数组)O(N^2)平均需要 (N^2) / 4次比较和移动

空间复杂度:

  • 空间复杂度:需常数额外空间O(1)。

稳定性:

  • 稳定 
特性总结

直接插入排序的特性总结:

  1. 元素集合越接近有序,直接插入排序算法的时间效率越高。
  2. 时间复杂度:O(N^2)。
  3. 空间复杂度:O(1),它是一种稳定的排序算法。
  4. 稳定性:稳定。

2.1.3 希尔排序

基本逻辑(算法步骤)

希尔排序是以人名命名的。希尔发现插入排序虽然在O(N^2)这个量级,但是插入排序显著地快,因为插入排序具有很强的适应性,几乎每次都不是最坏,甚至都还不错。

而且要是有序或者接近有序,插入排序能接近线性时间复杂度——O(N)

希尔排序就是让数据接近有序的插入排序

故而希尔排序把排序分为两步:

1. 预排序:目标就是让数据接近有序——gap > 1

  • 分组预排插入——目标:让在前面的大的数据更快换到后面的位置,在后面的小的数据更快地换到前面的位置。

2. 全排序:目标就是让数据全部有序——gap == 1

希尔排序法又称缩小增量排序法(递减增减排序)。

希尔排序是插入排序的一种更高效的改进版本。但希尔排序是非稳定排序算法。

希尔排序是基于插入排序的以下两点性质而提出改进方法的:

  • 插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率;
  • 但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位;

希尔排序法的基本思想是:

基本逻辑

  • 先选定一个整数gap,把待排序文件中所有记录分成个组。
  • 所有距离为gap的记录分在同一组内(gap也相当于组数)
  • 然后对每一组内的记录进行插入排序。(组内插入排序)
  • 然后取gap = gap / 2,重复上述分组和排序的工作。
  • 当到达gap = 1 时,所有记录在同一组内,进行最后一次插入排序

误区

  • gap=1并不是说1个元素一组,而是说相隔为1的所有元素组成一组,即整个数组
  • 可以认为gap代表组数:
    • gap越小,组数越少
    • gap越大,分组越多

希尔排序的优势

第一趟,gap == 5,这样同组内的数据,挪动一次,就能相当于外面的5步。

第二趟结合第一趟,在最开头的数据9,挪动3次就来到最后了。

动图演示

代码实现

排序算法先完成单趟,再实现整体控制。

通过控制起始位置,完成不同颜色组别的插入排序的控制。

3层循环,很多人很怀疑这里的执行效率。

实际上考虑时间复杂度不能只看循环层数,主要看的还是算法思想。


每趟:3层循环→优化成2层循环。(效率没有差别)

1. 先考虑单趟

  • 某一个gap值
  • 中层for循环,遍历end = [0,n-gap),end是已排好序的最后一个元素
  • 两次循环遍历的end = i,是两个不同的组。
  • 组内执行a[end+gap]这个数的直接插入排序,过程中end每次往前迭代gap个元素。

2. 再考虑整体

  • 每趟给不同的gap值。
  • 最后一趟gap==1,全排序。
//希尔排序
void ShellSort(int* a, int n)
{//给gap赋值数组长度——1个数据一组int gap = n;//3.多次逐次增加精度的预排序——>每一次都更接近有序while (gap > 1){//迭代——log2(N)次、log3(N)次——最外层while循环时间复杂度//gap /= 2;//选择除2:但是这样会导致预排序次数过多,就选择除3//选择除3:但不能保证最后为1,就手动+1gap = gap / 3 + 1;//2.一组的直接插入排序 和 全数组预分组排序for (int i = 0; i < n - gap; i++)        //留出一个n-gap的空间{//1.一个数tmp的直接插入排序//控制end 元素的位置 —— 一开始在0int end = i;//控制插入元素的位置 —— end+gap//保存在临时变量中,避免挪动覆盖int tmp = a[end + gap];//直接插入排序while (end >= 0){//比较if (tmp < a[end]){//1.挪动a[end + gap] = a[end];//2.end往前跳end -= gap;}else{break;}}//赋值a[end + gap] = tmp;}//PrintArray(a, n);    每个gap,预排序后都打印观察一下效果——>越来越有序}
}

代码测试。

这样一共3层循环(优化后),效率受到一些质疑。

性能分析

时间复杂度:

  • 本实现的 gap / 3 + 1 序列平均性能优于传统的 n / 2 序列

  • 最坏情况仍为O(n²),但实际运行中很少出现

  • 当数据部分有序时,性能接近O(n log n)

空间复杂度:

  • 仅使用常数个临时变量 O(1)

稳定性:

  • 不稳定

简单分析时间复杂度:

外层while循环的时间复杂度——gap迭代次数。

log2(N)次、log3(N)次。

中层for循环的时间复杂度——第一个gap值下,基本上是n次,最后一个gap值下,基本上是n次,中间是一个变化的过程。

N——>a*N((a>1) ——>N。

总的时间复杂度

log3(N) * N——>log3(N) * a*N (a>1) ——>log3(N) * N。

总结:比log3(N) * N大,大约是 N ^ 1.3 = N^(0.3) * N。

对数增长 VS 指数增长。


所以严格来说,希尔排序时间复杂度比堆排、快排慢一个档。

但是只是在10万,100万慢,1000万个数据希尔比堆排快。

因为时间复杂度只反映一个最坏的情况,时间复杂度看的是最坏,如果它有多个项,它看的是影响最大的那个项,看的是它的量级。

但是很多具体细节会影响具体时耗——具体细节:

① 具体的算法思想:会不会每次都最坏(例子:插入 VS 冒泡)。

② 算法的其他消耗:比如数据量足够大时,建堆的时耗O(N)也不小。

③ 不同的数据样本:插入排序在接近有序时,效率高于堆排序。
(处理随机数据当然堆排序好用)


性能测试

void TestOP()
{srand(time(0));//要产生随机需要一个种子,否则随机是写死的伪随机const int N = 100000;int* a1 = (int*)malloc(sizeof(int) * N);    //创建7个无序数组,每个数组10万个元素int* a2 = (int*)malloc(sizeof(int) * N);int* a3 = (int*)malloc(sizeof(int) * N);int* a4 = (int*)malloc(sizeof(int) * N);int* a5 = (int*)malloc(sizeof(int) * N);int* a6 = (int*)malloc(sizeof(int) * N);int* a7 = (int*)malloc(sizeof(int) * N);for (int i = 0; i < N; ++i){a1[i] = rand();                         //随机生成10万个元素,给7个数组a2[i] = a1[i];a3[i] = a1[i];a4[i] = a1[i];a5[i] = a1[i];a6[i] = a1[i];a7[i] = a1[i];}int begin1 = clock();    //系统启动到执行到此的毫秒数InsertSort(a1, N);int end1 = clock();      //系统启动到执行到此的毫秒数int begin7 = clock();//BubbleSort(a7, N);int end7 = clock();int begin3 = clock();//SelectSort(a3, N);int end3 = clock();int begin2 = clock();ShellSort(a2, N);int end2 = clock();int begin4 = clock();HeapSort(a4, N);int end4 = clock();//int begin5 = clock();//QuickSortNonR(a5, 0, N - 1);//int end5 = clock();//int begin6 = clock();//MergeSortNonR(a6, N);//int end6 = clock();printf("InsertSort:%d\n", end1 - begin1);//printf("BubbleSort:%d\n", end7 - begin7);printf("ShellSort:%d\n", end2 - begin2);//printf("SelectSort:%d\n", end3 - begin3);printf("HeapSort:%d\n", end4 - begin4);//printf("QuickSort:%d\n", end5 - begin5);//printf("MergeSort:%d\n", end6 - begin6);free(a1);free(a2);free(a3);free(a4);free(a5);free(a6);free(a7);
}int main()
{//TestInsertSort();//TestBubbleSort();//TestShellSort();//TestSelectSort();//TestQuickSort();//TestMergeSort();//TestCountSort();TestOP();//MergeSortFile("sort.txt");return 0;
}

测试结果——10万个数据下:

(gap /= 2 效率大概在12ms左右,比gap = gap/3+1稍微慢一些)

希尔排序的效率接近与堆排序O(N*logN)。

希尔排序远优于直接选择排序。


1000万个数据,希尔排序的效率就已经要优于堆排序了。

(时间复杂度只代表一个大概的量级,具体快慢还取决于一些细节因素)

n→∞,希尔排序的时间复杂度→O(N * (logN)^2),相较与堆排序的O(N^log^N)。

大概而言,希尔排序的时间复杂度在O(N^1.3)。

特性总结

希尔排序的特性总结:

1. 希尔排序是对直接插入排序的优化。

2. 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就会很快。这样整体而言,可以达到优化的效果(性能测试的对比)。

3. 希尔排序的时间复杂度不好计算,因为gap的取值方法很多,导致很难去计算,因此在好些树中给出的希尔排序的时间复杂度都不固定:

《数据结构(C语言版)--- 严蔚敏

《数据结构-用面相对象方法与C++描述》--- 殷人昆

4. 稳定性:不稳定。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/93045.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/93045.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MySQL优化常用的几个方法

本实例是对慢sql从2万优化到5千优化方法的汇总。 首先贴上优化效果&#xff1a;1、更新数据时使用ID更新&#xff1b;2、"分页/轮询"查询时先获取符合数据要求主键的最大和最小ID&#xff0c;然后WHERE条件增加ID步增查询&#xff1b;3、检查SQL是否命中WHERE条件&am…

深入解析 AUTOSAR:汽车软件开发的革命性架构

引言在汽车智能化、网联化、电动化浪潮席卷全球的今天&#xff0c;汽车电子系统的复杂性与日俱增。传统“烟囱式”的 ECU 开发模式&#xff08;各供应商独立开发软硬件&#xff09;带来了巨大的兼容性、复用性和维护成本挑战。AUTOSAR&#xff08;AUTomotive Open System ARchi…

计算机视觉(opencv)实战一——图像本质、数字矩阵、RGB + 图片基本操作(灰度、裁剪、替换等)

OpenCV 入门教程&#xff1a; OpenCV&#xff08;Open Source Computer Vision Library&#xff09;是一个开源的计算机视觉库&#xff0c;广泛应用于图像处理、视频分析、机器学习等领域。 在 Python 中&#xff0c;cv2 是 OpenCV 的主要接口模块。本文将带你一步步掌握 cv2…

《探索C++ set与multiset容器:深入有序唯一性集合的实现与应用》

前引&#xff1a;在STL的关联式容器中&#xff0c;set以其严格的元素唯一性和自动排序特性成为处理有序数据的核心工具。其底层基于红黑树&#xff08;Red-Black Tree&#xff09;实现&#xff0c;保证了O(log n)的查找、插入与删除复杂度&#xff01;本文将从底层原理切入&…

各测试平台功能对比分析(ITP,Postman,Apifox,MeterSphere)

对比ITP与Postman,Apifox,MeterSphere 功能特性ITPPostmanApifoxMeterSphere接口测试✅ 可视化接口调试&#xff0c;支持多种请求方式✅ 支持✅ 支持✅ 支持场景测试✅ 多接口串联测试&#xff0c;支持前后置脚本✅ Collections功能✅ 支持✅ 支持定时任务✅ 基于Celery的定时…

开源日志log4cplus—如何将 string类型转为tstring类型,又如何将char*类型转换为tstring类型?

文章目录&#x1f527; 一、理解 log4cplus::tstring 的本质⚙️ 二、std::string 转 tstring 的三种方法✅ 1. 使用内置宏 LOG4CPLUS_STRING_TO_TSTRING&#xff08;推荐&#xff09;✅ 2. 手动条件编译转换&#xff08;精细控制&#xff09;✅ 3. 多字节模式下的直接赋值⚙️…

深度学习之CNN网络简介

CNN网络简单介绍 1.概述 卷积神经网络&#xff08;Convolutional Neural Network&#xff0c;CNN&#xff09;是一种专门用于处理具有网格状结构数据的深度学习模型。 ​ CNN网络主要有三部分构成&#xff1a;卷积层、池化层和全连接层构成&#xff0c;其中卷积层负责提取图像中…

【微实验】基频提取的MATLAB实现(优化版)

前情提要&#xff1a; 【超详细】科普&#xff1a;别再只会用自相关&#xff01;YIN 和 PYIN 如何破解音频隐藏密码&#xff1f;-CSDN博客 【微实验】妈妈我的MATLAB会识别声音的基频了&#xff01;-CSDN博客 今天用MATLAB把算法封装成函数&#xff0c;然后调用对比结果。 …

开发 npm 包【详细教程】(含发布 npm 包,版本号升级,修改包后重新发布等)

1. 给 npm 包取个【唯一】的名字&#xff01; npm 包命名规范 只能包含小写字母&#xff08;a-z&#xff09;、数字&#xff08;0-9&#xff09;、连字符&#xff08;-&#xff09; 和 下划线&#xff08;_&#xff09;&#xff0c;不能包含空格、大写字母、标点符号&#xff…

Secure 第三天作业

实验需求&#xff1a;1.参考以上拓扑所示&#xff0c;完成以下需求&#xff1a;1&#xff09; 配置各设备 IP 地址2&#xff09; 配置 ZBFW&#xff0c;Inside-1 和 nside-2 属于内部 Zone&#xff0c;Outside-1 属于外部 Zonezone security insidezone security outsidezone-p…

Linux应用层-5.计算机网络(菜鸟学习笔记)

计算机网络的核心是连接与通信&#xff0c;从底层的物理信号到上层的应用服务&#xff0c;各层协议协同工作---------------------------------------------------------------------------------------一.计算机网络分类&#xff08;按范围&#xff09;1•个人区域网&#xff…

[论文阅读] 人工智能 + 软件工程 | 大型语言模型对决传统方法:多语言漏洞修复能力大比拼

大型语言模型对决传统方法&#xff1a;多语言漏洞修复能力大比拼 论文阅读&#xff1a;On the Evaluation of Large Language Models in Multilingual Vulnerability RepairarXiv:2508.03470 On the Evaluation of Large Language Models in Multilingual Vulnerability Repair…

计算机网络2-3:传输方式

目录 串行传输和并行传输 同步传输和异步传输 单工、半双工以及全双工通信 总结 串行传输和并行传输 并行传输的优点是速度为串行传输的n倍&#xff0c;但也存在一个严重的缺点即成本高 同步传输和异步传输 单工、半双工以及全双工通信 总结

文档生成PPT软件哪个好?深度测评8款word转ppt生成工具

在日常办公与教学场景中&#xff0c;如何高效地将Word文档内容转化为专业PPT&#xff0c;一直是职场人士、教育工作者及内容创作者的共同痛点。随着AI技术的普及&#xff0c;一键式转换工具应运而生&#xff0c;它们不仅能精准识别Word中的标题与段落结构&#xff0c;还能自动套…

Azimutt:一款免费开源的多功能数据库工具

Azimutt 是一款支持数据库设计、表结构探索与分析、数据查询以及数据库文档生成功能的全栈工具。 Azimutt 是一个免费开源的项目&#xff0c;源代码托管在 GitHub&#xff1a; https://github.com/azimuttapp/azimutt 功能特性 多数据库支持&#xff1a;包括主流数据库 MySQ…

智算赋能:移动云助力“世界一流数据强港”建设之路

2024年5月&#xff0c;某创新产业园区智算中心正式揭牌成立。台下响起的掌声不仅是对一个项目的祝贺&#xff0c;更是客户对未来的期许—— 推动产业结构优化升级&#xff0c;领跑数字经济转型发展。5家500强企业、8家上市企业、17家独角兽企业……该创新产业园区在成为“世界一…

达梦自定义存储过程实现获取表完整的ddl语句

--导出表的ddl CREATE OR REPLACE PROCEDURE show_create_table( db IN varchar(255), tb IN varchar(255)) ASsql1 text;ret text : ;cmt text :;sql2 text :; BEGINFOR WSX IN (select TABLEDEF(db,tb) as ddl from dual) LOOPret: ret||WSX.DDL;END LOOP;ret : ret||chr(10…

【ARM】keil提示UVISION: Error: Encountered an improper argument

1、 文档目标 解决MDK退出debug模式后&#xff0c;提示UVISION: Error: Encountered an improper argument。 2、 问题场景 在退出Debug模式的时候&#xff0c;弹出提示窗口&#xff0c;提示&#xff1a;UVISION: Error: Encountered an improper argument。&#xff08;如图…

【2025最新版】PDF24 Creator,PDF编辑,合并分割,格式转换全能工具箱,本地离线版本,完全免费!

软件介绍&#xff08;文末获取&#xff09;这款软件于1999年开发&#xff0c;至今已经有26年了&#xff0c;这26年里它都完全免费&#xff01;简洁的操作界面&#xff0c;让用户轻松上手&#xff0c;高效完成 PDF 文件的处理&#xff0c;方便又实用。这次给大家带来的是一个本地…

如何使用VLLM进行openai/gpt-oss系列推理与支持工具调用

OpenAI时隔6年再次推出开源模型gpt-oss系列&#xff0c;本次gpt-oss系列包含两个模型gpt-oss-120b与gpt-oss-20b。由于模型原生支持一种新的量化技术MXFP4&#xff0c;所以模型的部署所需的显存也显著的降低。openai/gpt-oss-20b 只需要大概16GB的显存openai/gpt-oss-120b 需要…