DS八大排序之直接插入排序和希尔排序

前言

我们前面几期介绍了线性和非线性的基本数据结构。例如顺序表、链表、栈和队列、二叉树等~!本期和接下来的几期我们来详解介绍各个排序的概念、实现以及性能分析!

本期内容

排序的概念以及其运用

常见的排序算法

直接插入排序

希尔排序

一、排序的概念及其运用

排序的概念

排序:按照一定的规则,把一组元素序列以递增或递减排列起来的操作!

稳定性:假设在待排序的一组序列中有多个相同的元素,若经过排序,这些元素的相对位置(相对次序)是不变的,则称为这种排序是稳定的。否则就是不稳定的!

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

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

关于哪些是内部排序、哪些是外部排序后面性能分析会在介绍!

排序的运用

排序在日常生活中是极其常见的:例如你每天看的京东、淘宝、拼夕夕等购物平台上的综合、销量、好评、价格、等一系列都是对商品的排序!

还有高校排名:

还有大家考试的时候的排名,这些都是排序~!排序在生活只能是很常见的!!!

常见的排序算法

二、直接插入排序及其实现

插入排序的基本思想

待排序的元素序列按照大小逐个插入到已经排序好的有序序列中,直到所有待排序的元素插入完为止, 而此时得到的新的序列就是有序的序列!

这就和我们小时候玩扑克牌摸牌整理的一样,一次与前面的排比较找到合适的位置插入!

直接插入排序

第i个元素插入时(i >= 1)前面的序列已经有序的, 此时只需要用array[i]的元素与前面的所有元素逐一比较前面的元素比当前的前面的元素插入到当前位置(升序),否则当前元素插入到前面元素的后面!

我们根据上述思路写单趟改造整体

	int end = 0;//一开始end置0位置int tmp = a[end + 1];//tmp是end 的下一个位置的元素while (end >= 0){if (a[end] > tmp)//前面元素比当前的元素大{a[end + 1] = a[end];//前面元素的插入到前面元素的后一个位置}else//前面元素不比当前的元素大{a[end + 1] = tmp;//当前元素插入到前面元素的下一个位置break;//记得结束否则会又把排好的区间搞乱}--end;}if (end < 0)//所有的都比tmp大,此时end一直减会减到-1{a[0] = tmp;//此时把tmp(end的下一个位置的元素)插入到0下标位置}

整体:

其实单趟写出来,改造整体就会很容易,我们在外面在套一层循环即可!让end从i 开始,每个元素与前面的逐一比较走单趟,直至最后有序!

void InsertSort(int* a, int n)
{for (int i = 0; i < n - 1; i++){int end = i;//一开始end置0位置int tmp = a[end + 1];//tmp是end 的下一个位置的元素while (end >= 0){if (a[end] > tmp)//前面元素比当前的元素大{a[end + 1] = a[end];//前面元素的插入到前面元素的后一个位置}else//前面元素不比当前的元素大{a[end + 1] = tmp;//当前元素插入到前面元素的下一个位置break;//记得结束否则会又把排好的区间搞乱}--end;}if (end < 0)//所有的都比tmp大,此时end一直减会减到-1{a[0] = tmp;//此时把tmp(end的下一个位置的元素)插入到0下标位置}}
}

但要注意的是:外面的for循环的判断条件,i < n - 1, 也就是说i最多走到n - 2的位置即倒数第二个元素!原因是:tmp才是每次要插入的元素,而tmp = a[end +1]是end(i)的下一个位置,如果让i 到最后一个元素的位置即n-1处,那tmp = a[end+1]就会越界!!!所以i 只能到倒数第二个元素的位置!

OK, 直接插入写完了,测试一下:

这样写是没问题,但感觉稍微有点挫~!我们在当前元素不大于前面元素的时候要判断一次,是否越界也要判断一次。

我们能不能想办法给优化一下呢?答案是肯定的!我们无论是判断当前的元素不比前面的大还是越界最后插入的都是end+1的位置,所以当在单趟的循环(while)内一旦不满足当前的比前面的大则立刻跳出当前循环。到了单趟循环外有可能是break结束的,也有可能是end < 0结束的,但我们根本不需要关心他,直接插入到end+1的位置即可~!

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];}else{break;}--end;}a[end + 1] = tmp;}
}

这样写简洁了不少吗,也不容易出错了~!我以前学的时候老是把上面第一种的break给忘了,最后找半天(Q^Q)....所以建议大家一般写第二种~!

复杂度分析

时间复杂度:O(N^2)  ---> 单趟是O(N),最坏情况N个元素都要走一次单趟

空间复杂度:O(1)  ---> 额外使用空间的个数是常数个

当要排序的序列接近有序时性能最好O(N)~!

OK, 我们直接插入排序就介绍到这里,下面我们来介绍一下它的优化版 ----- 希尔排序!!

三、希尔排序及其实现

我们上面介绍过直接插入的时间复杂度是O(N^2),它的性能一般~!有一天一位叫D.L.Shell的大佬去学习了直接插入排序后,想既然你直接插入排序在接近有序的情况下性能很好(O(N)),那我能不能把一组无序的元素经过处理先让他变得接近有序然后再来一趟直接插入呢?其实他这种想法直接把直接插入排序优化到几乎能和快排平起平坐了~!就是下面这位大佬:

OK,我们来看看大佬的具体思路!

希尔排序的思路

1、进行多组预排序

2、最后再来一次直接插入

什么意思呢?我来解释一下:这里的多组预排是:

先选定一个整数增量gap(一开始gap = n / 2),将数组的元素分为gap 组,将每个距离为gap的分为一组,并对每个小组进行距离为gap的"直接插排",然后不断的缩小gap(gap /= 2),重复上述操作,直到gap == 1时就说明各个组的都已经排好,此时相较于一开始已经是非常有序的了~!最后我们再来一趟直接插入排序则该序列就是有序的了!

OK, 画个图理解一下:

这就将一个完整的数组分成了gap 组,此时的gap 是 5,我们走一下预排序的一个gap:

他这样走下去,gap最后一定会到gap == 1,当gap == 1那就是直接插排了:

上述栗子可以清楚的看到当gap == 1时,未开始最后一次直接插排前的数组已经是很有序了,当经过最后一趟直接插入排序后就是有序了~!

希尔排序的实现

还是先单趟,再整体~!

单趟

每一组的单趟

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

注意的是这里是n - gap 而不是n - gap - 1

这是一组的单趟,这里和直接插入排序几乎一样,当gap == 1就是直接插排~!此时有多少组,就走多少组这样的单趟,所以在外面在套一层循环才是所有组的单趟~!

所有组的单趟

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

这就是所有的预排序的单趟,那他到底走多少趟预排呢?具体不知道,当gap最后通过调整到(gap /= 2或gap = gap / 3 + 1) gap == 1即可!所以总体应该在外面套个循环:

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

gap 一开始是n,然后开始执行第一趟时时gap /= 2;然后每次调整都是/=2,最后一次进入循环一定是1(n, n / 2, n / 4, n / 8, n / 16, n /32 ..... 4, 2, 1),即直接插入排序了~!

OK,测试一下:

没问题!但这段代码被另一位大佬进行过优化,如下:

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 (a[end] > tmp){a[end + gap] = a[end];}else{break;}end -= gap;}a[end + gap] = tmp;}}
}

他这里把,单组逐一排改成了多组并排~~以前我们是一组跑完了再去排另一组,他改完后直接一遍就可以把多组排好~!但性能上没有差别,,。

这里他给的gap = gap / 3 + 1;这样写的效率其实比gap /= 2的要好一点(网上以前看到的),他这里+1是因为,gap / 3有时候会小于1(例如: 2 / 3),这样最后一趟就排不了了~!+1就可以解决这个问题!

复杂度分析

希尔排序的时间复杂度是极其不好计算的,因为它的gap 的取值方法太多,很难精确地去计算,在好多书中给的都不同:例如严蔚敏老师的书中是如下

殷人昆老师的书中如下:

由于这里我们的gap是按殷人昆老师提出的方式取的,而殷人昆老师也对其进行了大量的数据实验统计,我们可以暂时认为

希尔排序的时间复杂度为:O(N^1.25)或O(N^1.3)

希尔排序的空间复O(1)

OK,本期插入排序就先介绍到这里,好兄弟,我们下期选择排序见~!

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

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

相关文章

“SRP模型+”多技术融合在生态环境脆弱性评价模型构建、时空格局演变分析与RSEI 指数的生态质量评价及拓展

近年来&#xff0c;国内外学者在生态系统的敏感性、适应能力和潜在影响等方面开展了大量的生态脆弱性研究&#xff0c;他们普遍将生态脆弱性概念与农牧交错带、喀斯特地区、黄土高原区、流域、城市等相结合&#xff0c;评价不同类型研究区的生态脆弱特征&#xff0c;其研究内容…

Jmeter性能综合实战——签到及批量签到

提取性能测试的三个方面&#xff1a;核心、高频、基础功能 签 到 请 求 步 骤 1、准备工作&#xff1a; 签到线程组 n HTTP请求默认值 n HTTP cookie 管理器 n 首页访问请求 n 登录请求 n 查看结果树 n 调试取样器 l HTTP代理服务器 &#xff08;1&#xff09;创建线…

vue建立组件无校验版

实现功能&#xff1a; 切换&#xff0c;相当于tab 1、非组件代码&#xff1a; <template><div><div class"tabStyle"><div v-for"(item,index) in tabTitle" :key"index" class"bordItemStyle" :class"c…

Spring框架体系及Spring IOC思想

目录 Spring简介Spring体系结构SpringIOC控制反转思想自定义对象容器Spring实现IOCSpring容器类型容器接口容器实现类对象的创建方式使用构造方法使用工厂类的方法使用工厂类的静态方法对象的创建策略对象的销毁时机生命周期方法获取Bean对象的方式通过id/name获取通过类型获取…

Kvm 管理器突然打不开 启动虚拟系统管理器出错: g-dbus-error-quark: GDBus.Error:org.freed

环境&#xff1a; Ubuntu20.04 KVM mobaxterm 11.1 问题描述&#xff1a; 启动虚拟系统管理器出错: g-dbus-error-quark: GDBus.Error:org.freedesktop.DBus.Error.NoReply: Message recipient disconnected from message bus without replying (4)Traceback (most recen…

一文搞定XMLHttpRequest,AJAX,Promise,Axios及操作实战

XMLHttpRequest,AJAX,Promise,Axios都是发送异步请求的工具&#xff0c;只是使用的场合和方式有所不同。都是一种用于创建快速动态网页的技术 1、认识同步请求和异步请求&#xff1f; 1.1、认识同步请求&#xff1f; 1、我们点击淘宝首页的男装&#xff0c;进入男装页面的这…

C++中的模板

模板概论 c提供了函数模板 (function template.) 所谓函数模板&#xff0c;实际上是建立一个通用函 数&#xff0c;其函数类型和形参类型不具体制定&#xff0c;用一个虚拟的类型来代表。这个通用函数 就成为函数模板。凡是函数体相同的函数都可以用这个模板代替&#xff0c;不…

好物分享(领劵优惠好手)

本次分享一个领取领优惠、充值、会员、购物优惠等功能 部分截图 需要的点击下方按钮 前往体验 感谢大家的支持 更多内容请关注微信小程序&#xff0c;源码、插件、模板及时更新

【C++干货铺】STL简述 | string类的使用指南

个人主页点击直达&#xff1a;小白不是程序媛 C系列专栏&#xff1a;C干货铺 代码仓库&#xff1a;Gitee 目录 什么是STL STL的版本 STL的六大组件 STL的缺陷 string类 C语言中的字符串 标准库中的string类 string类常用的接口使用指南 string类中常见的构造 strin…

CUDA安装及环境配置——最新详细版

确定安装版本 在安装之前呢&#xff0c;我们需要确定三件事 第一&#xff1a;查看显卡支持的最高CUDA的版本&#xff0c;以便下载对应的CUDA安装包 第二&#xff1a;查看对应CUDA对应的VS版本&#xff0c;以便下载并安装对应的VS版本&#xff08;vs需要先安装&#xff09; 第三…

【vue脚手架配置代理+github用户搜索案例+vue项目中常用的发送Ajax请求的库+slot插槽】

vue脚手架配置代理github用户搜索案例vue项目中常用的发送Ajax请求的库slot插槽 1 vue脚手架配置代理2 github用户搜索案例2.1 静态列表2.2 列表展示2.3 完善案例 3 vue项目中常用的发送Ajax请求的库3.1 xhr3.2 jQuery3.3 axios3.4 fetch3.5 vue-resource 4 slot 插槽4.1 效果4…

【沁恒蓝牙mesh】CH58x 将RTC时钟切换为LSE外部低速时钟

本文主要记录了【沁恒蓝牙mesh】CH58x 如何将RTC时钟切换为外部时钟 &#x1f496; 作者简介&#xff1a;大家好&#xff0c;我是喜欢记录零碎知识点的小菜鸟。&#x1f60e;&#x1f4dd; 个人主页&#xff1a;欢迎访问我的 Ethernet_Comm 博客主页&#x1f525;&#x1f389;…

实测有效的 8 个顶级Android 数据恢复工具

由于我们现在生活在一个依赖数字数据的时代&#xff0c;当重要文件从我们的 Android 手机中消失时&#xff0c;这将是一场数字噩梦。如果您没有预先备份Android手机上的数据或未能通过备份找到已删除的数据&#xff0c;那么选择最好的Android数据恢复软件是最佳选择。 因此&am…

苹果提醒事项怎么用?几个简单步骤就能学会!

苹果提醒事项可以帮助你轻松管理待办事项&#xff0c;让你更好地安排自己的时间和工作。但是&#xff0c;有些小伙伴可能对如何使用这个功能还有一些疑问。苹果提醒事项怎么用&#xff1f;不要担心&#xff0c;小编将为大家提供使用提醒事项的方法&#xff0c;帮助你学会如何使…

Nginx系列-正向代理和反向代理

Nginx系列-正向代理和反向代理 文章目录 Nginx系列-正向代理和反向代理1. 三个对象2. 两种场景代理2.1. 正向代理2.2. 反向代理 3. 两种场景的对比3.1 为什么叫做反向代理3.2 正向代理和反向代理的作用 1. 三个对象 客户端&#xff1a;发出请求到代理&#xff0c;并接收代理的…

Android : Fragment 传递数据 — 简单应用

示例图&#xff1a; 创建 Fragment new -> Fragment -> Fragment&#xff08;Blank&#xff09; MainActivity.java package com.example.fragmentdemo;import androidx.appcompat.app.AppCompatActivity; import androidx.fragment.app.FragmentManager; import andro…

C语言每日一题(41)循环队列

力扣 622 循环队列 题目描述 设计你的循环队列实现。 循环队列是一种线性数据结构&#xff0c;其操作表现基于 FIFO&#xff08;先进先出&#xff09;原则并且队尾被连接在队首之后以形成一个循环。它也被称为“环形缓冲器”。 循环队列的一个好处是我们可以利用这个队列之前…

5W2H分析法

5W2H分析法 5W2H分析法又叫七问分析法。 模型介绍 简单、方便&#xff0c;易于操作的思考&#xff08;框架&#xff09;模型&#xff0c;问题分析模型&#xff0c;它可以帮助我们保证思考的严谨与全面&#xff0c;也能给人启发&#xff0c;有着广泛的应用&#xff1a; 提问-可…

AndroidNDK开发之交叉编译

在Android studio2.2以及以上&#xff0c;构建原生库的默认工具是cmake。 CMake是一个跨平台的构建工具&#xff0c;可以使用简单的语句来描述所有平台的安装(编译过程)。 能够输出各种各样的makefile或者project文件。cmake并不直接构建出最终的软件&#xff0c;而是产生其他工…

Web学习笔记

Web学习笔记 flask库前端基础超链接&#xff1a;空连接&#xff1a;图片&#xff1a;视频&#xff08;音频&#xff09;&#xff1a;嵌套使用列表表格格式化表格input表单系列 网络请求GET方式POST请求通过GET方式获取输入参数通过POST方式获取输入参数注册页面 CSS三种使用方式…