排序之插入排序----直接插入排序和希尔排序(1)

news2025/1/16 15:45:43

 个人主页:C++忠实粉丝
欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 C++忠实粉丝 原创

排序之插入排序----直接插入排序和希尔排序(1)

收录于专栏【数据结构初阶
本专栏旨在分享学习数据结构学习的一点学习笔记,欢迎大家在评论区交流讨论💌

目录

1.排序的概念及其运用

1.1排序的概念

1.2排序运用 

1.2.1 购物和电商

1.2.2 图书馆和书店

1.2.3 教育

1.2.4 交通和物流

1.2.5 餐饮业

1.3 常见的排序算法 

2.插入排序

2.1基本思想

2.2直接插入排序

2.2.1直接插入排序的概念:

2.2.2直接插入排序示例:

2.2.3动图演示:

 2.2.4代码实现:

2.2.5测试代码:

2.2.6时间复杂度分析

2.3希尔排序 ( 缩小增量排序 )

2.3.1希尔排序的概念

2.3.2希尔排序图解分析:

2.3.3代码展示:

 2.3.4测试代码:

2.3.5希尔排序时间复杂分析:

2.3.6 希尔排序的特性总结:

2.4希尔排序与直接插入排序的关系和比较 

3总结 


1.排序的概念及其运用

1.1排序的概念

排序:所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。

稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次 序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排 序算法是稳定的;否则称为不稳定的。

内部排序:数据元素全部放在内存中的排序。

外部排序:数据元素太多不能同时放在内存中,根据排序过程的要求不断地在内外存之间移动数据的排序。

1.2排序运用 

排序算法在生活中有着广泛的应用,无论是日常活动还是专业领域,都能看到排序算法的身影。以下是一些具体的例子:

1.2.1 购物和电商

  • 产品排列:在线购物平台会根据价格、销量、评价等对商品进行排序,方便用户查找和比较。
  • 推荐系统:根据用户的浏览和购买历史,推荐系统会对可能感兴趣的商品进行排序。

1.2.2 图书馆和书店

  • 分类与索引:图书按字母顺序、类别、作者或者出版日期排序,方便读者查找。
  • 借阅记录:按时间顺序记录借还书信息,便于管理和统计。

1.2.3 教育

  • 成绩排名:考试成绩会按照分数排序,以便评估学生的表现。
  • 学籍管理:按学号或姓名排序学生信息,便于查询和管理。

1.2.4 交通和物流

  • 航班和列车时刻表:按出发时间、目的地等排序,方便乘客查询和安排行程。
  • 快递分拣:按目的地、优先级等对包裹进行排序,提高运送效率。

1.2.5 餐饮业

  • 菜单排序:餐厅菜单按菜品类型、受欢迎程度等排序,方便顾客选择。
  • 订单处理:按下单时间、优先级等排序订单,确保及时准确地完成服务。

这些例子展示了排序算法在各种场景中的重要性和广泛应用,从而提高了效率和用户体验。无论是简单的字母排序还是复杂的多条件排序,排序算法在现代生活中都是不可或缺的工具。

1.3 常见的排序算法 

大家可以去下面链接查看各个排序算法的动态演示效果

--Comparison Sorting Visualization (usfca.edu) 

2.插入排序

2.1基本思想

直接插入排序是一种简单的插入排序法,其基本思想是: 把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列 。

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

2.2直接插入排序

2.2.1直接插入排序的概念:

直接插入排序(Insertion Sort)是一种简单直观的排序算法,其基本思想是逐步构建有序序列。具体操作如下:

  1. 初始状态:将序列分为两部分,一部分是有序序列,初始时只包含第一个元素;另一部分是无序序列,包含剩余的元素。

  2. 排序过程

    • 从第二个元素开始,依次将该元素插入到前面已经排好序的序列中的合适位置。
    • 假设当前要插入的元素为 current_value,将 current_value 与已排序序列中的元素从后向前逐个比较。
    • 如果发现已排序元素比 current_value 大,则将该元素后移一位,直到找到比 current_value 小的位置,或者到达序列的开头。
    • 将 current_value 插入到找到的位置后,此时已排序序列长度增加一。
  3. 重复:重复以上步骤,直到所有元素都插入到有序序列中。

  4. 结束:当所有元素都插入到有序序列后,排序完成。

2.2.2直接插入排序示例:

假设要对数组 [5, 2, 4, 6, 1, 3] 进行直接插入排序:

  1. 初始时,有序序列为 [5],无序序列为 [2, 4, 6, 1, 3]
  2. 将 2 插入到 [5] 中,得到 [2, 5],无序序列变为 [4, 6, 1, 3]
  3. 将 4 插入到 [2, 5] 中,得到 [2, 4, 5],无序序列变为 [6, 1, 3]
  4. 依此类推,直到所有元素都插入到有序序列中,最终得到排序后的数组 [1, 2, 3, 4, 5, 6]

直接插入排序虽然简单,但在某些特定情况下仍然可以提供不错的性能,特别是在处理部分有序的数据或者数据量较小时。

2.2.3动图演示:

 2.2.4代码实现:

void InsertSort(int* a, int n)
{
	//  [0, n-1]
	for (int i = 0; i < n - 1; i++)
	{
		// [0, n-2]是最后一组
		// [0,end]有序 end+1位置的值插入[0,end],保持有序
		int end = i;
		int tmp = a[end + 1];
		while (end >= 0)
		{
			if (tmp < a[end])
			{
				a[end + 1] = a[end];
				--end;
			}
			else
			{
				break;
			}
		}
		a[end + 1] = tmp;
	}
}

分析:

  1. 外层循环(i 循环)

    • for (int i = 0; i < n - 1; i++) 循环遍历数组,从第一个元素到倒数第二个元素。每次迭代开始时,数组从 a[0] 到 a[i] 是已经排好序的部分。
  2. 内层循环(end 循环)

    • int end = i; 将当前元素 a[i+1] 视为待插入的元素。
    • int tmp = a[end + 1]; 记录待插入元素的值。
  3. 插入过程

    • while (end >= 0) 内层循环用于找到待插入元素 tmp 的正确位置。
    • if (tmp < a[end]) 如果待插入元素比当前位置 a[end] 的元素小,则将 a[end] 向后移动一位,即 a[end + 1] = a[end];,同时 end-- 继续向前比较。
    • 当找到合适的位置(即 tmp >= a[end]),退出内层循环。
  4. 插入操作

    • a[end + 1] = tmp; 将 tmp 插入到找到的合适位置 end + 1 处,此时数组从 a[0] 到 a[i+1] 又变成有序状态。

 注意:我们的外层循环for (int i = 0; i < n - 1; i++) 是遍历的是我们已经排好序的数组,我们需要排的数为a[end+1],也就是a[i+1],所以这里i<n-1,不能等于n-1

2.2.5测试代码:

测试链接:912. 排序数组 - 力扣(LeetCode)

题目描述:

给你一个整数数组 nums,请你将该数组升序排列。

示例 1:

输入:nums = [5,2,3,1]
输出:[1,2,3,5]

示例 2:

输入:nums = [5,1,1,2,0,0]
输出:[0,0,1,1,2,5]

提示:

  • 1 <= nums.length <= 5 * 104
  • -5 * 104 <= nums[i] <= 5 * 104

代码展示:

void InsertSort(int* a, int n)
{
    for(int i = 0; i < n-1; i++)
    {
        int end = i;
        int tmp = a[end + 1];
        while(end >= 0)
        {
            if(a[end] > tmp)
            {
                a[end + 1] = a[end];
                end--;
            }
            else
            {
                break;
            }
        }
        a[end + 1] = tmp;
    }
}

int* sortArray(int* nums, int numsSize, int* returnSize) {
    (*returnSize) = numsSize;
    int* array = (int*)malloc(sizeof(int)*(*returnSize));
    for(int i = 0; i < numsSize; i++)
    {
        array[i] = nums[i];
    }
    InsertSort(array, numsSize);
    return array;
}

2.2.6时间复杂度分析

 插入排序可以说是排序的最底层,它最好的情况是有序,时间复杂度为O(n),很显然这种情况很少见,最坏的情况是逆序,时间复杂度为O(n^2).在平均情况下,直接插入排序的时间复杂度也是 O(n^2)。虽然有时候可能会比较少于最坏情况下的比较次数,但是对于大规模的随机数组,其平均时间复杂度仍然是二次阶的。

在力扣这道题目中只通过了12个例子就超时了.....

 

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

1. 元素集合越接近有序,直接插入排序算法的时间效率越高

2. 时间复杂度:O(N^2)

3. 空间复杂度:O(1),它是一种稳定的排序算法

4. 稳定性:稳定

2.3希尔排序 ( 缩小增量排序 )

2.3.1希尔排序的概念

希尔排序(Shell Sort)是一种改进的插入排序算法,也称作缩小增量排序。希尔排序通过将原始序列分割成若干个子序列来改善插入排序的性能,每个子序列分别进行插入排序,最后再对整体进行一次插入排序。其基本思想可以描述如下:

  1. 步骤

    • 选择一个增量序列,通常是使用 Knuth 序列(例如 ( n / 2, n / 4, ..., 1 ))或者 Hibbard 序列(( 2^k - 1 ))来作为增量。
    • 根据选定的增量序列,将待排序的序列分割成若干个子序列。
    • 对每个子序列分别进行插入排序。
    • 逐渐缩小增量,重复以上步骤,直到增量为 1。
  2. 排序过程

    • 假设待排序数组为 [5, 2, 4, 6, 1, 3]
    • 如果选取增量序列为 ( n / 2, n / 4, ..., 1 ),则初始增量 ( n / 2 = 3 )。
    • 分别对 [5, 6][2, 1][4, 3] 这三个子序列进行插入排序。
    • 第一次插入排序后,可能得到 [1, 2, 3, 5, 4, 6]
    • 接下来使用更小的增量进行插入排序,直到最终使用增量为 1 的插入排序完成整体排序。

2.3.2希尔排序图解分析:

 

2.3.3代码展示:

void ShellSort(int* a, int n)
{
	int gap = n;
	while (gap > 1)
	{
		// +1保证最后一个gap一定是1
		// gap > 1时是预排序
		// gap == 1时是插入排序
		gap = gap / 3 + 1;

		for (size_t i = 0; i < n - gap; ++i)
		{
			int end = i;
			int tmp = a[end + gap];
			while (end >= 0)
			{
				if (tmp < a[end])
				{
					a[end + gap] = a[end];
					end -= gap;
				}
				else
				{
					break;
				}
			}
			a[end + gap] = tmp;
		}
	}
}

代码逻辑:

  1. 初始设置 gap

    • 初始时,将 gap 设置为数组长度 n。在每一轮迭代中,通过 gap = gap / 3 + 1 逐渐减小 gap 的值,直到最后一次迭代时 gap 等于 1,变成普通的插入排序。
  2. 主循环(while 循环):

    • 当 gap 大于 1 时,进行希尔排序的预处理阶段,即根据当前的 gap 进行分组预排序。
    • 当 gap 等于 1 时,执行最后一轮,此时相当于执行普通的插入排序。
  3. 预排序阶段

    • 对于每个 gap 值,通过一个 for 循环遍历数组,对每个分组进行插入排序。这里的 for (size_t i = 0; i < n - gap; ++i) 控制每个分组的起始位置。
  4. 插入排序

    • 对于当前的分组起始位置 i,使用插入排序的方式将该分组内的元素排序。
    • 内部的 while 循环用于找到合适的插入位置,确保当前位置的元素插入到正确的位置。
  5. 交换和移动

    • 如果当前位置的元素比 tmp(待插入元素)大,则将当前位置的元素向后移动 gap 个位置,直到找到合适的插入位置。
    • 插入位置确定后,将 tmp 插入到该位置。
  6. 最终结果

    • 经过多次循环和逐步缩小的 gap 值处理后,数组 a 将被排序完成。

 2.3.4测试代码:

 测试链接:912. 排序数组 - 力扣(LeetCode)

代码展示:

void ShellSort(int* a, int n)
{
    int gap = n;
    while(gap > 1)
    {
        gap = gap/3 + 1;
        for(int i = 0; i < n - gap; i++)
        {
            int end = i;
            int tmp = a[end + gap];
            while(end >= 0)
            {
                if(tmp < a[end])
                {
                    a[end + gap] = a[end];
                    end-=gap;
                }
                else
                {
                    break;
                }
            }
            a[end + gap] = tmp;
        }
    }
}

int* sortArray(int* nums, int numsSize, int* returnSize) {
    (*returnSize) = numsSize;
    int* array = (int*)malloc(sizeof(int)*(*returnSize));
    for(int i = 0; i < numsSize; i++)
    {
        array[i] = nums[i];
    }
    ShellSort(array, numsSize);
    return array;
}

结果展示:

居然过了!!!!!!!!!!

 

希尔排序虽然有点难理解,看起来很复杂,但是它的效率真的很高.

2.3.5希尔排序时间复杂分析:

 有关希尔排序的时间复杂度至今都没有定论.

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

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

因为我的gap是按照Knuth提出的方式取值的,而且Knuth进行了大量的试验统计,我们暂时就按照:O(n^1.3)来算

那为什么希尔排序的时间复杂难算呢?

第一次预排序 gap = n/3(这里我们将-1省略方便计算),一组有3个数据(n=10),最坏的情况需要排3次,也就是3*3/n=n,也就是说,希尔第一次预排序接近于O(n)

最后一次排序:数组接近有序,可以看成O(n)

第二次预排序:gap=n/9,每组九个数据,最坏的情况(1+2.....+8)*n/9=4n

注意:这里我们第二次预排序取得是最坏的情况,而经过我们第一次得预排序,我们第二次往往不会是最坏的情况,希尔排序难就难在除第一次和最后一次,其他情况是变化的

2.3.6 希尔排序的特性总结:

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

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

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

2.4希尔排序与直接插入排序的关系和比较 

关系与比较:

  1. 基础原理: 希尔排序可以看作是对直接插入排序的改进,通过预处理数据,使其更接近最终排序后的位置,从而减少了直接插入排序中的元素比较和移动次数。

  2. 性能比较: 在数据量较小时,直接插入排序可能会比希尔排序更快,因为希尔排序的预处理阶段可能带来一定的额外开销。但是在大规模数据和随机数据排序时,希尔排序通常能够明显优于直接插入排序。

  3. 稳定性: 直接插入排序是稳定的,而希尔排序一般来说是不稳定的,这是因为希尔排序涉及多个子序列的插入排序,子序列之间的相对顺序可能发生变化。

综上所述,希尔排序和直接插入排序虽然在实现上有所区别,但它们的基本思想都是通过逐步将元素移动到正确位置来完成排序,希尔排序通过增量序列的方式优化了插入排序的性能,特别是在处理大规模数据时表现更为优越。

3总结 

直接插入排序和希尔排序虽然在我们排序中使用较少,但它们具有可使用性,尤其是希尔排序(从它AC力扣的数组排序就可以看出)

我马上会更选择排序--(选择排序和推排序)

宝子们记得点赞关注支持一下

我将会在数据结构初阶这个专栏持续更新

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

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

相关文章

uni-app uni-data-picker级联选择器无法使用和清除选中的值

出现问题&#xff1a; 使用点击右边的叉号按钮无法清除已经选择的uni-data-picker值 解决办法&#xff1a; 在uni-app uni-data-picker使用中&#xff0c;要添加v-model&#xff0c;v-model在官网的示例中没有体现&#xff0c;但若不加则无法清除。 <uni-data-picker v-m…

从0搭建一个vue项目,不使用脚手架从html到vue

前言 从最开始学习web网页开始&#xff0c;搭建一个网页只需要创建一个html文件对其进行编写dom标签语言即可&#xff1b;后来分离了html&#xff0c;css和js&#xff0c;搭建一个网页开始需要文件夹&#xff0c;文件夹包含了这3类文件以及静态文件&#xff0c;图片&#xff0c…

2024年全新Studio One 6 .6.1新功能介绍及安装激活使用指南

​ 录制、制作、混音、掌握和表演所需的一切。Studio One 6 .6.1通过新的智能模板、直观的拖放工作流程、可定制的用户界面和强大的集成工具&#xff0c;让创作变得快速而轻松。 智能模板。快速启动。 快速启动录音、混音、掌握、节拍制作或简单地演奏虚拟乐器的基本工作流程—…

#04贪心法

要点&#xff1a; 贪心法的基本思想、基本要素与求解步骤&#xff1b; 贪心法的应用。 难点&#xff1a; 贪心法的最优子结构性质与贪心选择性质。 贪心法的基本思想 每个阶段的决策一旦做出就不可更改。不允许回溯。 并不从整体最优考虑&#xff0c;所作出的选择只是在某…

视频素材网站哪个好用?好用的做短视频素材下载网站分享

你是否正面临着为短视频寻找完美素材的困扰&#xff1f;是否对网络上那些雷同的素材网站感到厌烦了&#xff1f;今天&#xff0c;我将为大家推荐几个优秀的海外视频素材网站&#xff0c;这些隐藏的宝藏网站定能让你的作品焕发新光彩&#xff01; 蛙学素材网——您的创作好帮手…

希望数学少年俱乐部精品课学生用书-四年级

PDF版本链接链接&#xff1a;https://pan.baidu.com/s/1t6prtG9sd-1mYsnCUz-mKQ 提取码&#xff1a;0548 --来自百度网盘超级会员V7的分享

《编译原理》阅读笔记:p19-p24

《编译原理》学习第 4 天&#xff0c;p19-p24总结&#xff0c;总计 5 页。 一、技术总结 1.grouping of phases 这里谈到分组(group)&#xff0c;那么就会有一个疑问&#xff0c;分组的依据是什么&#xff1f;即根据什么来分组。 (1) front end & back end 编译器包含…

办公效率神器:ONLYOFFICE 的配置和使用

1.认识 ONLYOFFICE ONLYOFFICE 是一款免费开源的协同办公软件套件&#xff0c;支持多种文件格式&#xff0c;包括PDF、Word、Excel、PowerPoint等。它提供了文档编辑、表格处理、演示制作等功能&#xff0c;并可以与其他应用程序和平台集成&#xff0c;功能亮点特点如下&#…

【unity笔记】六、UI实现下拉列表切换视角

具体步骤如下 1. 创建UI下拉列表&#xff1a; 在Unity场景中右键点击并选择UI -> 下拉列表 来创建一个新的下拉列表。 2. 添加摄像机选项&#xff1a; 在Dropdown的Options属性中添加新的选项&#xff0c;通过点击按钮来添加选项&#xff0c;并为每个选项设置一个显示名…

【大数据】—谁是世界上最富的人?

引言 在2024年&#xff0c;全球财富的分布再次成为公众和经济学家关注的焦点。随着经济的波动和新兴市场的崛起&#xff0c;亿万富翁的名单也在不断变化。本文将深入探讨这一现象&#xff0c;通过最新的数据可视化分析&#xff0c;揭示世界上最富有的人在2024年的财富状况和趋…

成都百洲文化传媒有限公司助力电商品牌乘风破浪

在当今数字化时代&#xff0c;电商行业风起云涌&#xff0c;竞争异常激烈。成都百洲文化传媒有限公司凭借其对电商行业的深刻理解与实战经验&#xff0c;已然成为行业中的佼佼者。公司专注于电商服务领域&#xff0c;通过创新的营销模式和精准的市场策略&#xff0c;助力众多品…

#03动态规划

要点&#xff1a; 动态规划方法与贪心法、分治法的异同&#xff1b; 动态规划方法的基本要素与求解步骤&#xff1b; 动态规划方法的应用。 难点&#xff1a; 如何根据问题的最优子结构性质构造构造动态规划方法中的递归公式或动态规划方程。 动态规划的基本思想 动态规…

使用Servlet开发javaweb,请求常见错误详解及其解决办法【404、405、500】

Servlet报错的情况多种多样&#xff0c;涵盖了配置错误、代码逻辑错误、资源未找到、权限问题等多个方面。以下是一些常见的Servlet报错情况及其可能的原因和解决方法&#xff1a; 404 Not Found: 错误原因图示&#xff1a; URL映射 发送请求&#xff0c;出现404错误 原因: 请…

excel隐藏的部分内容如何显示出来?3个方法帮助你!

excel隐藏的部分如何显示出来&#xff1f;你是否曾经在Excel中遇到过某些单元格被隐藏&#xff0c;或者某些数据在编辑时突然消失&#xff0c;让你感到困惑和无助&#xff1f;不要担心&#xff0c;今天我将向你揭示如何解决这些问题&#xff0c;让你轻松显示隐藏的部分。 方法1…

神经网路学习7-线性模型

一个最简单的线性模型&#xff0c;w是权重&#xff0c;一般来说会取随机值&#xff0c;然后不断学习直到与预期相同 如此以此取每个值与真实值的差值&#xff0c;即评估误差 即找一个合适的权重w&#xff0c;使得平均误差最小 上面的是针对单个样本的&#xff0c;后面的是对…

onnx模型修改:去掉Dropout层

文章目录 尝试1&#xff1a;强行设置dropout层train mode为False尝试2&#xff1a;找到onnx模型中的dropout, train mode设置为False尝试3&#xff1a;直接删除dropout层&#xff0c;连接其输入输出结语 最近训练模型使用了tinyvit&#xff0c;性能挺强的&#xff1a; 但是导出…

Posix消息队列使用总结

Posix在线文档&#xff1a; The Single UNIX Specification, Version 2 (opengroup.org) Linux系统中提供了两种不同接口的消息队列&#xff1a; POSIX消息队列。POSIX为可移植的操作系统接口。System V消息队列。System V 是 AT&T 的第一个商业UNIX版本(UNIX System III)的…

【Mac】iTerm for mac(终端工具)软件介绍及安装教程

软件介绍 iTerm 是 macOS 上一个非常受欢迎的终端仿真器&#xff0c;提供了比默认的 Terminal 应用更多的功能和定制选项。它是一款开源软件&#xff0c;主要用于命令行界面的操作和开发者工具。 主要特点和功能&#xff1a; 分页和标签&#xff1a; iTerm 允许用户在单个窗…

Spring Boot项目的两种发布方式

5.1.方式1&#xff1a;通过jar包发布 步骤1&#xff1a;在pom中添加一个SpringBoot的构建的插件 <build><plugins><plugin><groupId>org.springframework.boot</groupId><!--自动检测项目中的 main 函数--><artifactId>spring-boot…

开发一款直播APP完整指南

直播是一种强大的营销工具&#xff0c;可以让企业与观众进行真实的互动。 根据Grand View Research发布的预测&#xff0c;直播行业规模将从 2021 年的 700 亿美元增长到 2028 年的近 2240 亿美元&#xff0c;七年内增长三倍。 区块链技术和人工智能等技术进步将在未来几年提…