排序算法上——插入,希尔,选择,堆排序
前言:
常见排序方法如下:
本篇将介绍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中国高精定位服务产业白皮书》 报告的主要内容如下: 产业背景:在“北斗”发展态势的…...

51c自动驾驶~合集58
我自己的原文哦~ https://blog.51cto.com/whaosoft/13967107 #CCA-Attention 全局池化局部保留,CCA-Attention为LLM长文本建模带来突破性进展 琶洲实验室、华南理工大学联合推出关键上下文感知注意力机制(CCA-Attention),…...

新能源汽车智慧充电桩管理方案:新能源充电桩散热问题及消防安全监管方案
随着新能源汽车的快速普及,充电桩作为核心配套设施,其安全性与可靠性备受关注。然而,在高温、高负荷运行环境下,充电桩的散热问题与消防安全隐患日益凸显,成为制约行业发展的关键瓶颈。 如何通过智慧化管理手段优化散…...

令牌桶 滑动窗口->限流 分布式信号量->限并发的原理 lua脚本分析介绍
文章目录 前言限流限制并发的实际理解限流令牌桶代码实现结果分析令牌桶lua的模拟实现原理总结: 滑动窗口代码实现结果分析lua脚本原理解析 限并发分布式信号量代码实现结果分析lua脚本实现原理 双注解去实现限流 并发结果分析: 实际业务去理解体会统一注…...

Docker 本地安装 mysql 数据库
Docker: Accelerated Container Application Development 下载对应操作系统版本的 docker ;并安装。 基础操作不再赘述。 打开 macOS 终端,开始 docker 安装mysql之旅 第一步 docker search mysql 》〉docker search mysql NAME DE…...
虚拟电厂发展三大趋势:市场化、技术主导、车网互联
市场化:从政策驱动到多元盈利 政策全面赋能 2025年4月,国家发改委、能源局发布《关于加快推进虚拟电厂发展的指导意见》,首次明确虚拟电厂为“独立市场主体”,提出硬性目标:2027年全国调节能力≥2000万千瓦࿰…...
jmeter聚合报告中参数详解
sample、average、min、max、90%line、95%line,99%line、Error错误率、吞吐量Thoughput、KB/sec每秒传输的数据量 sample(样本数) 表示测试中发送的请求数量,即测试执行了多少次请求。 单位,以个或者次数表示。 示例:…...
Bean 作用域有哪些?如何答出技术深度?
导语: Spring 面试绕不开 Bean 的作用域问题,这是面试官考察候选人对 Spring 框架理解深度的常见方式。本文将围绕“Spring 中的 Bean 作用域”展开,结合典型面试题及实战场景,帮你厘清重点,打破模板式回答,…...

WPF八大法则:告别模态窗口卡顿
⚙️ 核心问题:阻塞式模态窗口的缺陷 原始代码中ShowDialog()会阻塞UI线程,导致后续逻辑无法执行: var result modalWindow.ShowDialog(); // 线程阻塞 ProcessResult(result); // 必须等待窗口关闭根本问题:…...
go 里面的指针
指针 在 Go 中,指针(pointer)是一个变量的内存地址,就像 C 语言那样: a : 10 p : &a // p 是一个指向 a 的指针 fmt.Println(*p) // 输出 10,通过指针解引用• &a 表示获取变量 a 的地址 p 表示…...
Pydantic + Function Calling的结合
1、Pydantic Pydantic 是一个 Python 库,用于数据验证和设置管理,通过 Python 类型注解强制执行数据类型。它广泛用于 API 开发(如 FastAPI)、配置管理和数据解析,核心功能包括: 数据验证:通过…...