排序算法上——插入,希尔,选择,堆排序
前言:
常见排序方法如下:

本篇将介绍4种排序方法,分别为插入排序,希尔排序,选择排序,堆排序,并分别举例与讲解。
一. 插入排序
1.1 含义与动图分析
插入排序的思想是在有序区间的下一个位置插入一个新的元素,之后将该新区间重新排序,依次类推逐个完成整个区间的排序。
扑克整理手牌时就运用了这一思想。

动态理解:

1.2 问题分析
问题:
有序数组插入新元素并排序较为简单,关键在于我们要排序的数组常常区间为无序,那么如何使要插入元素之前的区间有序呢?
分析:
1.假定有序区间为[0,end],那么需要在[end+1]处插入新元素。
2.因此end的最大值应该小于n-1,否则end+1会导致数组访问越界
3.由于最初的数组为乱序,因此我们可以通过逐次增加有序区间的方式进行插入排序。
1.3 测试实现
代码示例如下:
void InsertSort(int* arr, int n)
{//n-2for (int i = 0; i < n - 1; i++){int end = i;int tmp = arr[end + 1];while (end >= 0){if (arr[end] > tmp){arr[end + 1] = arr[end];end--;}else {break;}}arr[end + 1] = tmp;}
}
时间复杂度分析:
最坏情况:数组降序排列
当我们对下标为i(0<i<n)的tmp数据进行插入时,会将其与前面i个数据比较i次,总比较次数即1+2+3+……(n-1),为O(n2)
最好情况:数组升序排列
当我们对下标为i(0<i<n)的tmp数据进行插入时,只会与其前面一个数据比较一次,即总共(n-1)此,为O(n)
空间复杂度:O(1)
二. 希尔排序
在直接插入排序中我们发现,元素越无序,直接插入排序算法时间效率越低(因为比较次数越多)。特别是当数组为降序,我们要排升序,此时数组的相对无序程度达到了最大,时间复杂度也到了最大。
2.1 含义与图片分析
希尔排序,也称为递减增量排序算法,是插入排序的一种高效率的改进版本。它通过将待排序的序列分割成若干子序列,分别进行直接插入排序,从而达到整个序列有序的目的。希尔排序的核心在于间隔序列的选择,间隔序列通常是按某种规则递减至1的。
2.2 思路及相关结论
思路分析:
1.希尔排序的核心在于对数组进行预排序,值得注意的是,预排序只是让数组相对有序,而非达到真正的有序状态。
2.预排序的处理可以通过分组实现,假设数组元素个数为n,那么我们可以分为gap组,每组元素就要n/gap个(假设可以gap可以被n整除)
3. 其排序思路与插入排序基本相同,但是gap在每次排序之后,需要令gap逐次减小,当gap减小为1时,就相当于插入排序。
2.3 测试实现
//希尔排序时间复杂度:O(n^1.3)
void ShellSort(int* arr, int n)
{int gap = n;while (gap > 1){gap = gap / 3 + 1;//保证最后一次gap一定为1for (int i = 0; i < n - gap; i++){int end = i;//n-gap-1int tmp = arr[end + gap];while (end >= 0){if (arr[end] > tmp){arr[end + gap] = arr[end];end -= gap;}else {break;}}arr[end + gap] = tmp;}}
}
时间复杂度分析:
外层循环:gap每次除以2或3,O(log2n) 或者O(log3n) ,即O(logn)
内层循环:

假设⼀共有n个数据,合计gap组,则每组为n/gap(大致)个;在每组中,插⼊移动的次数最坏的情况下为 S=1 + 2 + 3 + ……+ (n/gap-1),⼀共是gap组,因此:
总计最坏情况下移动总数为:gap ∗ S
gap取值有(以除3为例):n/3 n/9 n/27 … 2 1
一一带入
- 当gap为n/3时,移动总数为: n
- 当gap为n/9时,移动总数为: 4n
- 最后⼀趟,数组已经已基本有序了,gap=1即直接插⼊排序,移动次数就是n
- 通过以上的分析,可以画出这样的曲线图:

因此,希尔排序在最初和最后的排序的次数都为n,即前⼀阶段排序次数是逐渐上升的状态,当到达某⼀顶点时,排序次数逐渐下降⾄n,⽽该顶点的计算暂时⽆法给出具体的计算过程
- 内外循环综合来看,外层循环总共log3n次,内层循环的每次排序次数暂时无法精确计算,所以其复杂度不好计算。
希尔排序时间复杂度不好计算,因为 gap 的取值很多,导致很难去计算,因此很多书中给出的希尔排序的时间复杂度都不固定。《数据结构(C语⾔版)》—严蔚敏书中给出的时间复杂度为:
![]()
-
总之希尔排序的时间复杂度综合来说是高于直接插入排序的,范围大概是O(n1.3)~O(n2)
-
总结:
- 希尔排序的时间性能优于直接插入排序的原因:
在希尔排序开始时增量较大,分组较多,每组的记录数目少,n小,此时直接插入最好和最坏时间复杂度n2差距很小,故各组内直接插入较快,后来增量di逐渐缩小,分组数逐渐减少,而各组的记录数目逐渐增多,但由于已经按di-1作为距离排过序,使文件较接近于有序状态,所以新的一趟排序过程也较快。
gap越大,分组越快,相对有序性越差
gap越小,分组越慢,相对有序性越好。
当gap=1时,相当于插入排序。
三. 选择排序
3.1 含义与动图分析
思路:在整个数组内,每次选出最大的值和最小的值分别位于末尾和开头(升序情况,若为降序则与之相反),之后逐次缩小遍历选择空间。
动图图解:

3.2 测试实现
void SelectSort(int* arr, int n)
{int begin = 0;int end = n - 1;while (begin < end){int mini = begin, maxi = begin;for (int i = begin+ 1; i <= end; i++){if (arr[i] > arr[maxi]){maxi = i;}if (arr[i] < arr[mini]){mini = i;}}//mini begin//maxi endSwap(&arr[mini], &arr[begin]);Swap(&arr[maxi], &arr[end]);++begin;--end;}}
注意:上述代码存在问题!!!
在定义maxi和mini时,我们都初始化为了begin处,但当begin处的数据即为最大值时,排序就存在了问题。
分析:
mini正常完成交换之后,begin处的最大值也被交换了过去,那么此时end处的交换就会失败,并不能完成正常功能,因为maxi始终位于begin处,而此时begin处的值已经变为了最小值。
改进如下:
//避免maxi begini都在同一个位置,begin和mini交换之后,maxi数据变成了最小的数据if (maxi == begin){maxi = mini;}
此时就可以解决最大值位于begin处的情况。
完整代码如下:
void Swap(int* x, int* y)
{int tmp = *x;*x = *y;*y = tmp;
}void SelectSort(int* arr, int n)
{int begin = 0;int end = n - 1;while (begin < end){int mini = begin, maxi = begin;for (int i = begin + 1; i <= end; i++){if (arr[i] > arr[maxi]){maxi = i;}if (arr[i] < arr[mini]){mini = i;}}//mini begin//maxi end//避免maxi begini都在同一个位置,begin和mini交换之后,maxi数据变成了最小的数据if (maxi == begin){maxi = mini;}Swap(&arr[mini], &arr[begin]);Swap(&arr[maxi], &arr[end]);++begin;--end;}
}
四. 堆排序
4.1 基本思路分析
由于堆具有相对有序的特性,要么为大堆,要么为小堆,其相当于完成了希尔排序的预排序工作,因此可以利用建堆之后再向上或向下调整来进行排序。
4.2 测试实现
排升序,建大堆
分析:
1.此时我们常常有一个误区,认为小堆与升序类似,应该建立小堆。
2. 然而在排序的时候,时间消耗过于庞大,因为在挪动元素时,会把堆内的序列和关系打乱,因此应该建大堆,然后利用算法进行调整。
代码示例如下:
void AdjustDown(int* a, int n, int parent)
{// 先假设左孩子小int child = parent * 2 + 1;while (child < n) // child >= n说明孩子不存在,调整到叶子了{// 找出小的那个孩子if (child + 1 < n && a[child + 1] > a[child]){++child;}if (a[child] > a[parent]){Swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}else{break;}}
}void HeapSort(int* a, int n)
{// 向下调整建堆 O(N)for (int i = (n - 1 - 1) / 2; i >= 0; i--){AdjustDown(a, n, i);}// O(N*logN)int end = n - 1;while (end > 0){Swap(&a[0], &a[end]);AdjustDown(a, end, 0);--end;}
}
排降序,建小堆
思路于此相同,在此不做过多阐述。
代码示例如下:
void AdjustDown(int* a, int n, int parent)
{// 先假设左孩子小int child = parent * 2 + 1;while (child < n) // child >= n说明孩子不存在,调整到叶子了{// 找出小的那个孩子if (child + 1 < n && a[child + 1] < a[child]){++child;}if (a[child] < a[parent]){Swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}else{break;}}
}void HeapSort(int* a, int n)
{// 向下调整建堆 O(N)for (int i = (n - 1 - 1) / 2; i >= 0; i--){AdjustDown(a, n, i);}// O(N*logN)int end = n - 1;while (end > 0){Swap(&a[0], &a[end]);AdjustDown(a, end, 0);--end;}
}
小结:本文主要讲解了四种排序方法,下篇将会继续讲解排序的其他方式,欢迎各位大佬前来斧正支持!!!

相关文章:
排序算法上——插入,希尔,选择,堆排序
前言: 常见排序方法如下: 本篇将介绍4种排序方法,分别为插入排序,希尔排序,选择排序,堆排序,并分别举例与讲解。 一. 插入排序 1.1 含义与动图分析 插入排序的思想是在有序区间的下一个位置…...
Mycat 详细介绍及入门实战,解决数据库性能问题
一、基本原理 1、数据分片 (1)、水平分片 Mycat 将一个大表的数据按照一定的规则拆分成多个小表,分布在不同的数据库节点上。例如,可以根据某个字段的值进行哈希取模,将数据均匀的分布到不同的节点上。 这样做的好处…...
FFmpeg源码:avformat_new_stream函数分析
一、avformat_new_stream函数的声明 avformat_new_stream函数定义在FFmpeg源码(本文演示用的FFmpeg源码版本为7.0.1)的头文件libavformat/avformat.h中: /*** Add a new stream to a media file.** When demuxing, it is called by the dem…...
【java】深入解析Lambda表达式
Lambda表达式是Java 8引入的一项重要特性,它提供了一种简洁的方式来实现函数式编程。Lambda表达式的使用广泛而且灵活,可以简化代码并提高可读性。本篇文章将深入解析Lambda表达式,包括使用场景、基础学习、代码案例、实现方法和注意事项等方…...
Chromium html<img>对应c++接口定义
<img src"tulip.jpg" alt"上海鲜花港 - 郁金香" /> 1、html_tag_names.json5中接口定义: (third_party\blink\renderer\core\html\html_tag_names.json5) {name: "img",constructorNeedsCreateElementF…...
卸载Python
1、查看安装框架位置并删除 Sudo rm -rf /Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/Versions/3.8 2、查看应用并删除 在 /Applications/Python 3.x 看是否存在,如果存在并删除。 3、删除软连接 ls -l /usr/bin/py* 或 ls -…...
算法剖析:二分查找
文章目录 前言二分查找模板朴素模板左右查找模板 一、二分查找二、 在排序数组中查找元素的第一个和最后一个位置三、搜索插入位置四、x 的平方根五、山脉数组的峰顶索引六、寻找峰值七、寻找旋转排序数组中的最小值八、 点名总结 前言 二分查找是一种高效的查找算法ÿ…...
Invoke 和 InvokeRequired以及他们两个的区别
在.NET中,Invoke和InvokeRequired是Windows Forms编程中用于确保线程安全的关键方法和属性。它们通常用在多线程环境中,以确保UI控件的更新操作在创建控件的线程上执行,避免因跨线程操作导致的异常。 InvokeRequired 属性 InvokeRequired属…...
SpringBoot概览及核心原理
Spring Boot 是由Pivotal 团队设计的全新框架,其目的是用来简化 Spring 应用开发过程。该框架使用了特定的方式来进行配置,从而使得开发人员不再需要定义一系列样板化的配置文件,而专注于核心业务开发,项目涉及的一些基础设施则交…...
根据basic auth请求https获取token
根据basic auth请求https获取token 对接第三方接口,给了接口文档,但是没有示例代码,postman一直可请求成功,java就是不行。百思不得其解,最后请求公司大神,得到一套秘籍。 第一步 第二步 Authorization&am…...
【基础版】React缓存路由
前言 项目背景 Reactumireact-router5 需求 用户在某一页面操作后点击跳转到其详情页,返回到列表页还是之前操作过的页面,即把页面缓存下来(基础版先处理路由缓存,tab页展示先不处理) 实践 在布局页面对页面进行…...
Java基础15-Java高级
十五、Java高级 单元测试、反射、注解、动态代理。 1、单元测试 定义:就是针对最小的功能单元(方法),编写测试代码对其进行正确性测试。 1.1 Junit单元测试框架 可以用来对方 法进行测试,它是第三方公司开源出来的(很多开发工具已经集成了Junit框架&…...
selenium工具的几种截屏方法介绍(9)
在使用selenium做自动化的时候,可以对于某些场景截图保存当时的执行情况,方便后续定位问题或者作为一些证据保留现场。 获取元素后将元素截屏 我们获取元素后,使用函数screenshot将元素截屏,参数filename传入完整的png文件名路径…...
【设计模式】深入理解Python中的过滤器模式(Filter Pattern)
深入理解Python中的过滤器模式(Filter Pattern) 在软件设计中,面对复杂的数据处理需求时,我们常常需要从一组数据中筛选出符合特定条件的子集。**过滤器模式(Filter Pattern)**是一种能够简化这种操作的设…...
vue的动态组件 keep-alive
1. 什么是动态组件 动态组件指的是 动态切换组件的显示与隐藏 2. 如何实现动态组件渲染 vue提供了一个内置的<component>组件,专门用来实现动态组件的渲染。 作用:组件的占位符is的值表示要渲染的组件 示例代码如下: Left.vue的代…...
现代框架开发官网
一、项目背景 维护过 灵犀官网、企业邮官网、免费邮官网 均使用 jquery webpack多页面打包的方式 开发起来较为繁琐 新的官网项目,想使用现代前端框架,但SPA应用不利于SEO 使用SSR方案又依赖运维,增加维护和沟通成本 二、SSG vs 预渲染 S…...
一篇文章快速认识YOLO11 | 关键改进点 | 安装使用 | 模型训练和推理
前言 本文分享YOLO11的关键改进点、性能对比、安装使用、模型训练和推理等内容。 YOLO11 是 Ultralytics 最新的实时目标检测器,凭借更高的精度、速度和效率重新定义了可能性。 除了传统的目标检测外,YOLO11 还支持目标跟踪、实例分割、姿态估计、OBB…...
AtCoder Beginner Contest 375(A,B,C,D,E,F)(大模拟,前缀和,dp,离线处理,Floyd)
比赛链接 AtCoder Beginner Contest 375 A题 代码 #pragma GCC optimize("O2") #pragma GCC optimize("O3") #include <bits/stdc.h> using namespace std; #define int long long const int N 2e5 5, M 1e6 5; const int inf 0x3f3f3f3f3f…...
认识maven
什么是 Maven? Maven 是一个开源的项目管理工具,主要用于 Java 项目的构建、依赖管理和项目生命周期管理。它提供了一种标准的项目结构和管理流程,使得开发人员能够更轻松地管理项目的构建过程,提高代码的可重用性和可维护性。 …...
OSINT技术情报精选·2024年10月第2周
OSINT技术情报精选2024年10月第2周 2024.10.16版权声明:本文为博主chszs的原创文章,未经博主允许不得转载。 1、亿欧智库:《2024中国高精定位服务产业白皮书》 报告的主要内容如下: 产业背景:在“北斗”发展态势的…...
VB.net复制Ntag213卡写入UID
本示例使用的发卡器:https://item.taobao.com/item.htm?ftt&id615391857885 一、读取旧Ntag卡的UID和数据 Private Sub Button15_Click(sender As Object, e As EventArgs) Handles Button15.Click轻松读卡技术支持:网站:Dim i, j As IntegerDim cardidhex, …...
SciencePlots——绘制论文中的图片
文章目录 安装一、风格二、1 资源 安装 # 安装最新版 pip install githttps://github.com/garrettj403/SciencePlots.git# 安装稳定版 pip install SciencePlots一、风格 简单好用的深度学习论文绘图专用工具包–Science Plot 二、 1 资源 论文绘图神器来了:一行…...
Admin.Net中的消息通信SignalR解释
定义集线器接口 IOnlineUserHub public interface IOnlineUserHub {/// 在线用户列表Task OnlineUserList(OnlineUserList context);/// 强制下线Task ForceOffline(object context);/// 发布站内消息Task PublicNotice(SysNotice context);/// 接收消息Task ReceiveMessage(…...
【入坑系列】TiDB 强制索引在不同库下不生效问题
文章目录 背景SQL 优化情况线上SQL运行情况分析怀疑1:执行计划绑定问题?尝试:SHOW WARNINGS 查看警告探索 TiDB 的 USE_INDEX 写法Hint 不生效问题排查解决参考背景 项目中使用 TiDB 数据库,并对 SQL 进行优化了,添加了强制索引。 UAT 环境已经生效,但 PROD 环境强制索…...
python爬虫:Newspaper3k 的详细使用(好用的新闻网站文章抓取和解析的Python库)
更多内容请见: 爬虫和逆向教程-专栏介绍和目录 文章目录 一、Newspaper3k 概述1.1 Newspaper3k 介绍1.2 主要功能1.3 典型应用场景1.4 安装二、基本用法2.2 提取单篇文章的内容2.2 处理多篇文档三、高级选项3.1 自定义配置3.2 分析文章情感四、实战案例4.1 构建新闻摘要聚合器…...
【HarmonyOS 5 开发速记】如何获取用户信息(头像/昵称/手机号)
1.获取 authorizationCode: 2.利用 authorizationCode 获取 accessToken:文档中心 3.获取手机:文档中心 4.获取昵称头像:文档中心 首先创建 request 若要获取手机号,scope必填 phone,permissions 必填 …...
Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...
Android第十三次面试总结(四大 组件基础)
Activity生命周期和四大启动模式详解 一、Activity 生命周期 Activity 的生命周期由一系列回调方法组成,用于管理其创建、可见性、焦点和销毁过程。以下是核心方法及其调用时机: onCreate() 调用时机:Activity 首次创建时调用。…...
Java毕业设计:WML信息查询与后端信息发布系统开发
JAVAWML信息查询与后端信息发布系统实现 一、系统概述 本系统基于Java和WML(无线标记语言)技术开发,实现了移动设备上的信息查询与后端信息发布功能。系统采用B/S架构,服务器端使用Java Servlet处理请求,数据库采用MySQL存储信息࿰…...
Vue3中的computer和watch
computed的写法 在页面中 <div>{{ calcNumber }}</div>script中 写法1 常用 import { computed, ref } from vue; let price ref(100);const priceAdd () > { //函数方法 price 1price.value ; }//计算属性 let calcNumber computed(() > {return ${p…...
