插入排序以及希尔排序; 先学会插入,希尔会更简单喔

1.前言

  1. 首先肯定是要学会插入排序再学习希尔排序会更简单,因为代码部分有很多相似之处;如果你觉得你很强,可以直接看希尔排序的讲解。
  2. 哈哈哈!,每天进步一点点,和昨天的自己比

2.插入排序

  1. 让我们先来看看插入排序的动图,可能第一遍看不懂,最好结合解释一起看

  1. 核心思想:end + 1;去0 到 end 之前去找,如果tmp(end+1)小于end位置,end位置往后挪到end + 1位置上
  2. 挪动后tmp要继续找0 到 end的其他位置,如果tmp 比end位置大,就插入在end + 1的位置
  3. 需要注意的是:假如tmp大于0到end之间的某一个数据,直接跳出循环,在while循环外面插入
  4. for循环的i < n - 1为什么是这样? 因为是拿end + 1的位置和前面的比较;
    1. 假设 i < n;那你执行代码的时候,最后一个是end是n - 1位置,那么你要取tmp位置的就会导致越界,这个tmp就会取到n位置上的数,不就越界了吗
    2. 0 到 end 之间的区间数据是逐渐增长的,最开始是 0 到 1,第二次范围就是0 到 2
//插入排序 核心思想:end + 1;去0 end 之前去找,如果tmp小于end位置,就往end位置往后挪
void InsertSort(int* arr, int n)
{
    //n的位置不可访问,所以要 < n; < n - 1为什么? 因为是拿end + 1的位置和前面的比较
    for (int i = 0; i < n - 1; i++)
    {
        int end = i;
        int tmp = arr[end + 1];
        while (end >= 0)//去查找出0 end范围的值
        {
            if (tmp < arr[end])
            {
                arr[end + 1] = arr[end];//如果tmp小于end位置的值,就向后挪,知道大于end位置的值就可以停下插入
                end--;
            }
            else
            {
                break;//此时说明tmp大于end位置,在end+1位置插入
            }
        }
        arr[end + 1] = tmp;//防止都比0 end之间要小,小于0跳出循环,-1 + 1;刚好是第一个位置
    }
}
  1. 下面说一种常规思路的写法,这样写可以,不过上面的写法更好
  2. 而且最主要就是防止tmp比 0 到 end 区间内的值都要小

void InsertSort(int* arr, int n)
{
    //n的位置不可访问,所以要 < n; < n - 1为什么? 因为是拿end + 1的位置和前面的比较
    for (int i = 0; i < n - 1; i++)
    {
        int end = i;
        int tmp = arr[end + 1];
        while (end >= 0)//去查找出0 end范围的值
        {
            if (tmp < arr[end])
            {
                arr[end + 1] = arr[end];//如果tmp小于end位置的值,就向后挪,知道大于end位置的值就可以停下插入
                end--;
            }
            else
            {
                arr[end + 1] = tmp;
                break;//此时说明tmp大于end位置,在end+1位置插入
            }
        }
        if(end == -1) 
        {
            arr[end + 1] = tmp;
        }

2.1插入排序的时间复杂度

  1. 最坏情况:
    1. O(N^2);当排序为逆序时,你想想0 到 end位置的数,假设是升序;竟然是升序但是end + 1这个数据是比0 到 end位置的数还要小那么就会交换很多次
    2. 假如上述的情况每次都这样呢?,那不是最坏的情况了
    3. 但是逆序的概率很低,反而乱序的概率大些
  2. 最好情况:O(N),已经非常接近有序了
  3. 还有一点,主要是插入排序适应能力强,在查找的过程中可以直接插入数据,比冒泡排序省去很多查找
  4. 这里也顺便提一下冒泡排序吧,做个比较

2.2冒泡排序

  1. 最坏情况:O(N^2);
  2. 最好情况:O(N),已经有序了
  3. 虽然冒泡和插入排序时间复杂度都是一样的,但是在同一所学校,同一个公司;但是它两之间仍然有好坏之分
  4. 为啥呢? 因为冒泡排序的最坏情况很容易达成,几乎每次交换都是一个等差数列,最好情况的情况概率太低了
  5. 竟然这么菜,有啥用呢 ? 实践中没有意义;适合教学
//冒泡排序
void BubblSort(int* arr, int sz)
{
	for (int i = 0; i < sz - 1; i++)
	{
        int flag = 0;//假设这一趟没有交换已经有序
		for (int j = 0; j < sz - i - 1; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				int tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
                flag = 1;//发生交换,无序
			}
		}
        if(flag == 0)//经过一趟发现有序,没有交换
            break;
	}
}

3.希尔排序

  1. 希尔排序分为两步
    1. 预排序(让数组接近有序)
    2. 插入排序
  2. 插入排序思想不错,可不可以优化一下,变得更好,甚至更强呢?
  3. 插入排序就是怕逆序的情况,所以通过预排序让数组接近有序,让大的值跳gap步可以很快的到后面
  4. 那么什么是gap,gap就是把数据分为gap组,每个数据间隔为gap的

3.1预排序

  1. 下面这样图,看不懂没关系细细给你分析。可以看到gap = 3,分成了红绿蓝三组数据
  2. 每组的每个数间隔为gap,假如说红色这一组它有4个数,其实你可以看成插入排序的一趟排序,只不过就是间隔为gap而已
  3. 而下面这样图就是,每组数据都进行一趟插入排序,可以自己画图试试看看,一不一样;当然下面也有动图的解释

3.2单趟排序

  1. 其实和插入排序思路差不多,只不过是end位置 和 tmp(end + gap)位置进行比较
  2. 如果tmp值小于end位置的值,就让end位置移动到 end + gap位置
  3. break跳出证明tmp大于 arr[end],此时tmp就放到end + gap位置即可
  4. 放在while循环外,和上面讲的插入排序的解决方法一样,当小于0 到 end时;防止越界
    1. 动图来解释一下,这个动图忘记设置循环了,所以要观看就重进一下

讲完单趟排序,再来说说应该注意的点

  1. 使用for来完成一趟排序的结束条件
  2. 红色这组举例:因为你每次跳gap步,如果 i 循环到了 数字为4的位置,此时进入循环,int tmp = arr[end + gap];这里面的 + gap会越界

最后是for循环是单趟的代码

for (int i = j; i < n - gap; i += gap)
		{
			int end = i;//对应一趟排序的第几次
			int tmp = arr[end + gap];
			while (end >= 0)//比较交换
			{
				//如果小于arr[end]挪到arr[end + gap]
				if (tmp < arr[end])
				{
					arr[end + gap] = arr[end];
				}
				else
				{
					break;
				}
			}
			arr[end + gap] = tmp;
		}

3.3每组都进行预排序

  1. 外面再放一层for循环是什么意思呢?
  2. 意思是通过分组来排序,先是红色然后绿色蓝色,当j = 0是就是红色这一组
//希尔排序的预排序
void ShellSort(int* arr, int n)
{
	//假设gap为3
	int gap = 3;
	for (int j = 0; j < gap; j++)
	{
		for (int i = j; i < n - gap; i += gap)
		{
			int end = i;//对应一趟排序的第几次
			int tmp = arr[end + gap];
			while (end >= 0)//比较交换
			{
				//如果小于arr[end]挪到arr[end + gap]
				if (tmp < arr[end])
				{
					arr[end + gap] = arr[end];
				}
				else
				{
					break;
				}
			}
			arr[end + gap] = tmp;
		}
	}
}
  1. 当然上面那一种在外面再放一层for也可以
  2. 这里我们可以优化一下,变成两层循环;下面是优化后一小段过程

  1. 上面的是一组一组排序,而这里是多组一起排序
  2. 而通过整过程发现,红色会预排序3次,绿色2次,蓝色2次;和优化后的总次数一样 ,假设n = 10,10 - 3 = 7;刚好是7次了
  3. 这里并不能通过数循环来判断时间复杂,上面三层循环和这里的两次循环一样的
  4. 执行次数一样,排序的过程不一样

3.3.1优化后的代码

void ShellSort(int* arr, int n)
{
	//假设gap为3
	int gap = 3;
	for (int i = 0; i < n - gap; i++)
	{
		int end = i;//对应一趟排序的第几次
		int tmp = arr[end + gap];
		while (end >= 0)//比较交换
		{
			//如果小于arr[end]挪到arr[end + gap]
			if (tmp < arr[end])
			{
				arr[end + gap] = arr[end];
				end -= gap;
			}
			else
			{
				break;
			}
		}
		arr[end + gap] = tmp;
	}
}

3.4预排序要进行多次

  1. 这里不是直接预排序完,然后就直接排序;要进行多次预排序才行
  2. 那么gap取多少才好呢?,最早有人研究 gap = gap / 2;但是这么最后每组只有两个,这么分不太好
  3. gap = gap / 3 + 1,更加合理一点

  1. 这为什么 + 1,因为刚好可以避免被3整除的时候等于了0,带入值可以想象一下8 / 3 = 2 + 1;3 / 3 = 0 + 1;
  2. 也就是说大于3的数除最后都会小于3,+ 1保证最后一个是 1
  3. 下面就是进行多次预排序,让大的数据更快到后面,小的数据更快到前面
  4. 弄完预排序,也就完成整个希尔排序,因为gap == 1的时候就是插入排序了
//希尔排序
void ShellSort(int* arr, int n)
{
	int gap = n;
	while (gap > 1)
	{
		//gap > 1 就是预排序
		//gap == 1 就是插入排序
		gap = gap / 3 + 1;
		for (int i = 0; i < n - gap; i++)
		{
			int end = i;//对应一趟排序的第几次
			int tmp = arr[end + gap];
			while (end >= 0)//比较交换
			{
				//如果小于arr[end]挪到arr[end + gap]
				if (tmp < arr[end])
				{
					arr[end + gap] = arr[end];
					end -= gap;
				}
				else
				{
					break;
				}
			}
			arr[end + gap] = tmp;
		}
	}
}

3.5预排序总结

  1. gap越大,大的可以越快的跳到后面,小的可以更快跳到前面,但是就会不接近有序
  2. gap越小,跳的越慢,但是更加接近有序,gap == 1相当于插入排序了
  3. 所以为了结合两者优点,直接让gap慢慢变小

3.6希尔排序的时间复杂度

  1. 直接给出结论,希尔排序的时间复杂度是O(N^1.3);下面的分析仅供参考!!!
  2. 不过下面有稍微推了一下过程,可能不太好
    1. gap大 :数据量少,交换的次数少;
    2. gap小:数据量大,交换的次数多;
    3. gap表示多少组和数据之间的间隔(gap),gap大,组就多,每组数据就少,gap小 ,组就少,每组数据就多

4.总结

  1. 希尔排序的思想非常好,也非常奇妙,估计大部分普通人想不到这个方法;
  2. 当然可以学习到这么好的思路,也是很大的进步了;万一你以后也发明了一种很厉害的算法呢
  3. 如果你最近在苦恼要不要写博客,我的建议是一定写,个人感觉这个加分项还是很有必要握在手里的

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Hive SQL 每日一题】统计每月用户购买商品的种类分布

文章目录 测试数据需求说明需求实现 测试数据 -- 创建 orders 表 DROP TABLE IF EXISTS orders; CREATE TABLE orders (order_id INT,user_id INT,product_id INT,order_date STRING );-- 插入 orders 数据 INSERT INTO orders VALUES (101, 1, 1001, 2023-01-01), (102, 1, 1…

pycharm简易使用码云gitee

文章目录 参考文献官网地址安装插件第一个选项报错了不可&#xff0c;第二个选项&#xff0c;可以了新库上传到主分支&#xff0c;push改进实验新建分支&#xff0c;上传为新分支&#xff1a;做另一种改进&#xff0c;选择回退主分支&#xff0c;另建一个分支 使用对于一个新项…

SQL158 每类视频近一个月的转发量/率

描述 用户-视频互动表tb_user_video_log iduidvideo_idstart_timeend_timeif_followif_likeif_retweetcomment_id110120012021-10-01 10:00:002021-10-01 10:00:20011NULL210220012021-10-01 10:00:002021-10-01 10:00:15001NULL310320012021-10-01 11:00:502021-10-01 11:01…

Python 基于机器学习模型的车牌检测和识别系统 有GUI界面 【含Python源码 MX_004期】

一、系统介绍 车牌的检测和识别技术在现代社会中的应用场景可谓十分广泛&#xff0c;不仅涉及交通管理领域&#xff0c;还延伸至社区安保等多个方面。例如&#xff0c;在交通违章管理中&#xff0c;通过车牌追踪可以有效追踪违章车辆&#xff0c;维护交通秩序&#xff1b;在小区…

【UML用户指南】-05-对基本结构建模-类

在UML中&#xff0c;所有的事物都被建模为类。类是对词汇表中一些事物的抽象。类不是个体对象&#xff0c;而是描述一些对象的一个完整集合。 强调抽象的最重要的部分&#xff1a;名称、属性和操作 类 &#xff08;class&#xff09;是对一组具有相同属性、操作、关系和语义的对…

JVM垃圾收集器和内存分配策略

概述 Java内存运行时数据区的程序计数器、虚拟机栈、本地方法栈3个区域会随着线程而产生&#xff0c;随线程而消失。这几个区域分配多少内存时在类结构确定下来即已知的&#xff0c;在这几个区域内就不需要过多考虑如何回收内存的问题&#xff0c;当方法结束或者线程结束时&am…

第三届大湾区算力大会丨暴雨开启数字未来新篇

5月30-31日&#xff0c;韶关市迎来主题为“算启新篇智创未来”的第三届粤港澳大湾区(广东)算力产业大会暨第二届中国算力网大会&#xff0c;活动由广东省人民政府主办&#xff0c;广东省政数局、韶关市人民政府共同承办。暴雨信息作为算力产业发展的重要构建者受邀赴会&#xf…

【C++进阶】深入STL之string:模拟实现走进C++字符串的世界

&#x1f4dd;个人主页&#x1f339;&#xff1a;Eternity._ ⏩收录专栏⏪&#xff1a;C “ 登神长阶 ” &#x1f921;往期回顾&#x1f921;&#xff1a;C模板入门 &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; ❀STL之string &#x1f4d2;1. string…

力扣575. 分糖果

题目&#xff1a; Alice 有 n 枚糖&#xff0c;其中第 i 枚糖的类型为 candyType[i] 。Alice 注意到她的体重正在增长&#xff0c;所以前去拜访了一位医生。 医生建议 Alice 要少摄入糖分&#xff0c;只吃掉她所有糖的 n / 2 即可&#xff08;n 是一个偶数&#xff09;。Alic…

Java中连接Mongodb进行操作

文章目录 1.引入Java驱动依赖2.快速开始2.1 先在monsh连接建立collection2.2 java中快速开始2.3 Insert a Document2.4 Update a Document2.5 Find a Document2.6 Delete a Document 1.引入Java驱动依赖 注意&#xff1a;启动服务的时候需要加ip绑定 需要引入依赖 <dependen…

Java的数据库编程-----JDBC

目录 一.JDBC概念&使用条件&#xff1a; 二.mysql-connector驱动包的下载与导入&#xff1a; 三.JDBC编程&#xff1a; 使用JDBC编程的主要五个步骤&#xff1a; 完整流程1&#xff08;更新update&#xff09;&#xff1a; 完整流程2(查询query)&#xff1a; 一.JDB…

FS212E 系列PD协议

PD快充协议芯片FS212EL、FS212EH可以智能的识别插入的手机类型&#xff0c;选择最为合适的协议应对手机快充需要。兼容多类USB Type-C协议&#xff0c;包括TypeC协议、TypeC PD2.0、TypeC PD3.0、TypeC PD3.2等协议。集成OPTO输出&#xff0c;通过电阻直驱反馈光耦。FS212E 的调…

【STL】C++ queue队列(包含优先级队列) 基本使用

目录 一 queue 1 常见构造 1 空容器构造函数 2. 使用指定容器构造 3 拷贝构造函数 2 empty 3 size 4 front && back 5 push && pop 6 emplace 7 swap 二 优先级队列( priority_queue) 1 常见构造 2 其他操作 3 大堆和小堆 1. 大小堆切换 2 自…

961题库 北航计算机 MIPS基础选择题 附答案 选择题形式

有题目和答案&#xff0c;没有解析&#xff0c;不懂的题问大模型即可&#xff0c;无偿分享。 第1组 习题 MIPS处理器五级流水线中&#xff0c;涉及DRAM的是 A. 取指阶段 B. 译码阶段 C. 执行阶段 D. 访存阶段 MIPS处理器五级流水线中&#xff0c;R型指令保存结果的阶段是 A.…

Mixly UDP局域网收发数据

一、开发环境 软件&#xff1a;Mixly 2.0在线版 硬件&#xff1a;ESP32-C3&#xff08;立创实战派&#xff09; 固件&#xff1a;ESP32C3 Generic(UART) 测试工具&#xff1a;NetAssist V5.0.1 二、实现功能 ESP32作为wifi sta连接到路由器&#xff0c;连接成功之后将路由器…

基于Django的博客系统之增加类别导航栏(六)

上一篇&#xff1a;基于Django的博客系统之用HayStack连接elasticsearch增加搜索功能&#xff08;五&#xff09; 下一篇&#xff1a; 功能概述 博客类型导航栏。 需求详细描述 1. 博客类型导航栏 描述&#xff1a; 在博客首页添加类型导航栏&#xff0c;用户可以通过导航…

属性(property)

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 1 创建用于计算的属性 在Python中&#xff0c;可以通过property&#xff08;装饰器&#xff09;将一个方法转换为属性&#xff0c;从而实现用于计算…

vue3-调用API实操-调用开源头像接口

文档部分 这边使用是开源的API 请求地址: &#xff1a;https://api.uomg.com/api/rand.avatar 返回格式 : json/images 请求方式: get/post 请求实例: https://api.uomg.com/api/rand.avatar?sort男&formatjson 请求参数 请求参数说明 名称必填类型说明sort否strin…

探索安全之道 | 企业漏洞管理:从理念到行动

如今&#xff0c;网络安全已经成为了企业管理中不可或缺的一部分&#xff0c;而漏洞管理则是网络安全的重中之重。那么企业应该如何做好漏洞管理呢&#xff1f;不妨从业界标准到企业实践来一探究竟&#xff01;通过对业界标准的深入了解&#xff0c;企业可以建立起完善的漏洞管…

算法每日一题(python,2024.05.28) day.10

题目来源&#xff08;力扣. - 力扣&#xff08;LeetCode&#xff09;&#xff0c;中等&#xff09; 解题思路&#xff1a; 辅助数组 找规律&#xff0c;设旋转前某点matrix[i][j]&#xff0c;则旋转后改点变为matrix[j][n&#xff0d;1&#xff0d;i]&#xff08;n为len(matr…