数据结构——排序(1):插入排序

news2024/9/24 22:32:38

目录

一、排序的概念

二、排列的运用

三、常见的排序算法

四、插入排序

1.直接插入排序

 (1)思路

(2)过程图示

(3)代码实现

 (4)代码解释

(5)特性

2.希尔排序

(1)思路

(2)过程图示

(3)代码实现

(4)代码解释

(5)特性 

(6)时间复杂度

 五、写在最后


一、排序的概念

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

二、排列的运用

(1)购物筛选排序

(2)院校排名排序

三、常见的排序算法

四、插入排序

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

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

1.直接插入排序

 (1)思路

插入到第i个元素时,其前面的元素已经排好序了,此时将第i个元素的大小与第i-1、i-2……个元素的大小进行比较,找到合适的位置,将原来位置上的元素后移,并将第i个元素插入。


(2)过程图示

我们以数组{2,1,3,5,4,8}为例:

①首先end指的是已排好序的最后一个元素;tmp为end之后的元素,即要比较插入的元素。

如图:tmp比end小,那么应该将tmp往前遍历。让end+1位置的数据为end位置的数据,end往前遍历,此时end<0无法再遍历,则跳出循环。

②③继续遍历:随后的两次中tmp均大于end对应的数据,直接进行下一此遍历。

④此时tmp小于end对应的位置,令end+1位置的数据为end位置的数据,end后移继续遍历。此时tmp大于end对应位置的数据,那么令此时end+1位置的数据为tmp。

⑤继续遍历,此时tmp大于end,直接进行下一次遍历。

⑥继续遍历我们发现end指向最后一个数据时,tmp越界了,说明该情况不合理。我们也可以知道在写代码时,end的约束条件应该是<n-1。

至此遍历完成,所有数据也排为了升序。


(3)代码实现

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

 (4)代码解释

①首先最外层的for循环,用i遍历下标为0 ~ n-2的数据,即最后一个数据没有被遍历(前面有解释,end = i ,且end不能为最后一个数据,因为tmp会越界);

②end为已经排好序的范围的最后一个数据;tmp为被比较之后插入的元素;

③while循环用来限制end不能出界,即end<0或者tmp>=arr[end](else情况),都会跳出循环执行下一步:令end+1位置的数据为tmp储存的数据;

④如果tmp比end指向的数据小,即tmp应该排在end前面,就令end+1的位置数据为end对应的数据,相当于给tmp腾位置,遍历寻找一个end<tmp且end+1>tmp的位置,插入tmp。


(5)特性

①元素集合越接近有序,算法的时间效率越高;

②时间复杂度:O(n^2):最差的情况是序列为降序(要排列为升序),即Tmp要与其前面的每个数据都比较。O(2+3+……+n-1+n) = O(n^2);

③空间复杂度:O(1)。


2.希尔排序

我们学习了直接插入排序,但是它的时间复杂度为O(n^2),效果并不是很理想。而这个结果是基于降序的情况计算的,那么可以思考一下,我们是不是能够将其优化一下,避免这种的情况出现呢?

(1)思路

希尔排序法又称缩小增量法。

先选定一个整数(定义为gap),把待排序的文件分成若干组,所有的距离相等的数据分在同一组,并对每一组的数据进行排序,然后gap = gap / 3 + 1得到下一个新的gap,再将数据进行分组并对每组进行插入排序,在这个过程中gap会越来越小,当gap等于1时就相当于直接插入排序算法。(那么,为什么新的gap要等于gap/3+1呢?这是因为相较于除以2、除以4来说,除以3所分的组数不是太多,也不会太少;并且在最后+1避免了达不到gap=1的情况。)


(2)过程图示

我们以数组{9,1,2,5,7,4,8,6,3,5}、gap等于5为例:

相同颜色的数据为一组,由于gap等于5,我们将以上10个数据分为了5组,每组2个数据。将每组的两个数据进行比较(此时靠前的数据为end,靠后的数据为tmp),如果tmp小于end对应的数据,则让end+gap对应的数据为end所对应的数据,end往前遍历(end-gap)。巧的是,在这几组中end-gap均小于0,跳出循环。然后继续遍历…… 就这样,我们将每组数据进行了排序,得到{4,1,2,3,5,9,8,6,5,7};

gap改变,gap等于2、1的情况亦是如此:

得到{2,1,4,3,5,6,5,7,8,9};

得到{1,2,3,4,5,5,6,7,8,9},至此排序完成。 


(3)代码实现

我们可以根据将每组进行排序的思路写代码:

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

第一个for循环是将分成的3组进行排序,第二个for循环及以下是将每组数据进行排序,只是在直接插入排序的基础上的优化。可是,现在怎么比之前还要多一个循环呢?可想而知时间复杂度并不理想,我们应该设法去掉这个for循环: 

void ShellSort(int* arr, 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 = arr[end+gap];
            while(end >= 0)
            {
                if(tmp < arr[end])
                {    
                    arr[end + gap] = arr[end];
                    end -= gap;
                }
                else
                {
                    break;
                }
            }
            arr[end + gap] = tmp;
        }    
    }
}

区别在于前者是每一组内部的数据先进行比较(以组为单位),后者为按照原有的顺序进行遍历,当然所插入的数据是与其所在组中的数据进行比较。


(4)代码解释

其实与直接插入排列类似:

①gap的限制条件为gap>1,gap不能等于1(如果等于1,gap=gap/3+1的计算中gap一直为1,会造成死循环);

②最外层的for循环,用i遍历下标为0 ~ n-gap-1的数据,即end-gap位置的数据没有被遍历。解释同上,end = i ,且end不能为最后一个数据,因为tmp会越界(若end=n-gap,那么tmp=n-gap+gap=n,此时越界);

③end为已经排好序的范围的最后一个数据;tmp为被比较之后插入的元素;

④while循环用来限制end不能出界,即end<0或者tmp>=arr[end](else情况),都会跳出循环执行下一步:令end+gap位置的数据为tmp储存的数据;

⑤如果tmp比end指向的数据小,即tmp应该排在end前面,就令end+gap的位置数据为end对应的数据,相当于给tmp腾位置,遍历寻找一个end<tmp且end+gap>tmp的位置,插入tmp。


(5)特性 

①希尔排序是对直接插入排序的优化;

②gap>1都是预排序,目的是让数组更接近有序;当gap等于1时,数组已经接近有序,这样排序的效率更高。


(6)时间复杂度

外层循环的时间复杂度为O(log2 N)或者O(log3 N),即为O(log N);

内存循环的时间复杂度为O(n^1.3):

假设有n个数据,距离为gap,那么一共有gap组,每组有n/gap个数据。

最坏的情况下:

每组的移动次数为1+2+3+……+(n/gap - 1),一共有gap组,因此总移动总数为gap*[ 1+2+3+……+(n/gap - 1)]。

当gap为n/3时,移动总次数为:n/3 *(1 +2 ) = n;

当gap为n/9时,移动总次数为:n/9 *(1 +2 + …… + 8) = n/9 * 8(1+8)/2 = 4n;

最后一趟,gap等于1时,即为直接插入排序,内层循环排序消耗为n。

根据上述分析,可画出这样的曲线图:

因此,希尔排序在最初和最后的排序的次数都为n,即前一阶段排序次数是逐渐上升的状态,当到达某⼀顶点时,排序次数逐渐下降至n,而该顶点的计算暂时无法给出具体的计算过程。

希尔排序时间复杂度不好计算,因为 gap 的取值很多,导致很难去计算,因此很多书中给出的希尔排序的时间复杂度都不固定。

《数据结构(C语言版)》---严蔚敏书中给出的时间复杂度为:


 五、写在最后

这节课我们学习了插入排序,而排序好几种。

敬请期待“选择排序”~

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

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

相关文章

【Java算法专场】位运算(上)

目录 常见位运算总结 位1的个数 算法思路 算法代码 比特位计数 算法思路 算法代码 汉明距离 算法思路 算法代码 只出现一次的数字 算法思路 算法代码 丢失的数字 算法思路 算法代码 常见位运算总结 了解位运算的一些基本操作&#xff0c;那么我们就来通过题目来…

STM32的USB接口介绍

STM32 USB接口是STM32微控制器系列中集成的一种通信接口&#xff0c;它允许STM32微控制器与外部设备或计算机进行高速的数据传输和通信。以下是STM32 USB接口的简要介绍&#xff1a; 1. 接口类型 STM32的USB接口通常支持USB 2.0标准&#xff0c;部分高端型号可能还支持USB 3.…

新手必看!剪映轻松上手,让你的视频瞬间高大上

相信现在短视频兴起的时代下&#xff0c;几乎人手都在学习如何剪辑出日常视频&#xff0c;尤其是想要走新媒体路线的小伙伴更是在尝试专业的剪辑&#xff0c;不过平时的vlog或者抖音短视频可以从简单的开始接触&#xff0c;剪映是其中的一款适合初学者上手的剪辑了&#xff0c;…

网络编程复习

1.网络编程基础 1.1引入 socket套接字实现主机之间的通信 cs通信模型基于socket实现&#xff0c;需要客户端软件来实现通信 bs通信模型基于http实现&#xff0c;是网页通信&#xff0c;不需要任何客户端软件 1.2通信协议 &#xff08;1&#xff09;OSI七层通信协议&#xff…

QEMU理解与分析系列(1):QEMU简介

QEMU简介 一、QEMU基本介绍1.1操作模式1.2 虚拟化方式中间代码实现方式简介源码结构分布 二、qemu tcg前端解码逻辑2.1 tcg翻译流程2.1.1 decode tree语法2.1.2 trans_xxx函数的逻辑 三、编译相关3.1 代码拉取&#xff08;拉取自己想要的版本&#xff09;3.2 编译参数3.3 依赖包…

Spring Boot - 在Spring Boot中实现灵活的API版本控制(上)

文章目录 为什么需要多版本管理&#xff1f;在Spring Boot中实现多版本API的常用方法1. URL路径中包含版本号2. 请求头中包含版本号3. 自定义注解和拦截器 注意事项 为什么需要多版本管理&#xff1f; API接口的多版本管理在我们日常的开发中很重要&#xff0c;特别是当API需要…

关于Zoho mail邮箱续费、退款、升级的说明

在使用企业邮箱服务的过程中&#xff0c;可能会遇到续费&#xff0c;退款及升级服务的情况。遇到这些情况时应该如何处理&#xff1f;本文将为您提供遇到邮箱情况时的详细操作步骤&#xff1a;邮箱续费方式及续费流程、邮箱申请退款方式、邮箱升级服务的流程。 一、Zoho邮箱如…

动态面板门槛模型及 Stata 具体操作步骤

目录 一、文献综述 二、理论原理 三、实证模型 四、稳健性检验 五、程序代码及解释 六、代码运行结果 一、文献综述 动态面板门槛模型作为一种先进的计量经济学方法&#xff0c;在众多领域的研究中发挥着关键作用。在经济增长领域&#xff0c;[学者 A]通过构建动态面板门槛…

你的会议记录还用手写吗?3款免费语音转文字工具

现在&#xff0c;随着大家越来越习惯用电脑和手机工作&#xff0c;很多专业人士都在找更快捷的方法来记录会议内容。以前那种手写笔记&#xff0c;不仅写起来慢&#xff0c;而且整理和分享起来也很麻烦。幸好&#xff0c;随着科技的发展&#xff0c;出现了一些能将说话的声音直…

【计算机网络】什么是socket编程?以及相关接口详解

&#x1f490; &#x1f338; &#x1f337; &#x1f340; &#x1f339; &#x1f33b; &#x1f33a; &#x1f341; &#x1f343; &#x1f342; &#x1f33f; &#x1f344;&#x1f35d; &#x1f35b; &#x1f364; &#x1f4c3;个人主页 &#xff1a;阿然成长日记 …

letcode 分类练习 x个数之和问题 15. 三数之和 18. 四数之和 454. 四数相加 II 383. 赎金信

letcode 分类练习 x个数之和问题 15. 三数之和 18. 四数之和 454. 四数相加 II 383. 赎金信 三数之和四数之和454. 四数相加 II383. 赎金信 三数之和 三数之和&#xff0c;双指针模版代码&#xff0c;注意去重逻辑&#xff0c;还有只需要去重第一和第二重循环&#xff0c;第三…

从Python翻译Go代码谈起:AI辅助编程的现状与展望

最近&#xff0c;一位同学使用GPT-4o将一个约300行的Python程序转换成Golang&#xff0c;正确率达到了90%。这引发了一个有趣的讨论&#xff1a;如果是整个项目规模的代码转换&#xff0c;准确率会如何&#xff1f;作为被的对象&#xff0c;我决定深入探讨这个话题&#xff0c;…

高等数学精解【6】

文章目录 直线与二元一次方程直线方程斜率两点式方程截距式方程将不同形式的直线方程转换为截距方程直线的一般方程直线一般方程的系数有一个或两个为零的直线 参考文献 直线与二元一次方程 直线方程 斜率 直线对于 x 轴的倾角&#xff0c;平行于 x 轴&#xff0c;倾角为 0 &…

从一个服务预热不生效问题谈微服务无损上线

作者&#xff1a;凡问、启淮 前言 本文基于阿里云技术服务团队和产研团队&#xff0c;在解决易易互联使用 MSE&#xff08;微服务引擎&#xff09;产品无损上线功能所遇到问题的过程总结而成。本文将从问题和解决方法谈起&#xff0c;再介绍相关原理&#xff0c;后进一步拓展…

jupyter下载

https://blog.csdn.net/qq_48372575/article/details/125630622 我下面是CPU运行的&#xff0c;GPU链接在上面 Anaconda下载 https://docs.anaconda.com/miniconda/miniconda-other-installer-links/ 参考链接&#xff1a; https://blog.csdn.net/qq_48372575/article/detai…

计算机编码 - 笔记

1 ASCII码 - 0- 127 2 ASCII码扩展字符集 - 128- 255

反转字符串(LeetCode)

题目 编写一个函数&#xff0c;其作用是将输入的字符串反转过来。输入字符串以字符数组 的形式给出。 不要给另外的数组分配额外的空间&#xff0c;你必须原地修改输入数组、使用 的额外空间解决这一问题。 解题 def reverse_string(s):left 0right len(s) - 1while left …

SDF Marching Cubes Ray-marching Teahouse

SDF & Marching Cubes & Ray-marching SDF SDF(Signed Distance Field)有向距离场。SDF是由到&#xff08;多边形模型&#xff09;物体表面最近距离的采样网格。作为惯例&#xff0c;使用负值来表示物体内部&#xff0c;使用正值表示物体外部。 Marching Cubes marc…

CUDA编程05 - GPU内存架构和数据局部性

一&#xff1a;概述 到目前为止&#xff0c;我们已经学会了如何编写 CUDA 核函数&#xff0c;以及如何设置和分配大量线程来执行核函数。我们还了解了当前 GPU 硬件的计算架构&#xff0c;以及线程在硬件上调度执行过程。在本章中&#xff0c;我们将重点关注 GPU 的片上(on-chi…

golang实现Digest认证鉴权接口

什么是Digest认证鉴权接口? Digest认证鉴权接口是一种基于摘要算法的身份验证方法,用于确保API请求的安全性。在实际应用中,常常使用HTTP协议的Digest认证鉴权接口来验证请求的合法性。下面是一种常见的Digest认证鉴权流程: 1. 客户端发送HTTP请求到服务器,请求接口资源…