【数据结构与算法】希尔排序(直接插入排序)

news2024/12/24 2:06:46

大家好,我是小卡皮巴拉

文章目录

目录

引言

一.直接插入排序的基本思想

二. 直接插入排序算法解析

详细版本的算法思想解析

算法思想提炼

实现代码

画图刨析

三. 直接插入排序的特性

复杂度分析

稳定性分析

四. 希尔排序的基本思想

五. 希尔排序算法解析

详细版本的算法思想解析

算法思想提炼

实现代码

画图刨析

六. 希尔排序的特性

复杂度分析

稳定性分析

兄弟们共勉 !!! 


每篇前言

博客主页:小卡皮巴拉

咱的口号:🌹小比特,大梦想🌹

作者请求:由于博主水平有限,难免会有错误和不准之处,我也非常渴望知道这些错误,恳请大佬们批评斧正。

引言

在计算机科学的浩瀚星空中,排序算法如同璀璨星辰,各自闪耀着独特的光芒。其中,直接插入排序(简称插入排序)以其直观易懂、实现简便的特点,成为了初学者的首选之一,也是理解复杂排序算法的重要基石。

直接插入排序,顾名思义,是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。这一过程仿佛我们在整理书架上的书籍,每次拿起一本新书,从已排列好的书堆中寻找合适的空位,小心翼翼地放置进去,直到所有书籍都井然有序。

本文旨在深入探讨直接插入排序及其进阶版本希尔排序的核心思想、实现步骤以及时间空间复杂度分析,帮助读者掌握这一基础而强大的排序工具。

下面,让我们开始学习!

一.直接插入排序的基本思想

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

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

下面我们给出直接插入排序的动图,帮助大家更好的理解

二. 直接插入排序算法解析

详细版本的算法思想解析

  1. 初始化
    • 从数组的第二个元素开始(因为第一个元素默认是有序的),对剩余的每一个元素进行排序。
  2. 取待排序元素
    • 对于当前索引 i,取出 arr[i+1] 作为待排序的元素 tmp,并记录 i 作为已排序数组的最后一个元素的索引 end
  3. 扫描已排序部分
    • 从 end 向左扫描已排序的数组部分(即从 arr[end] 到 arr[0])。
    • 在扫描过程中,如果已排序部分的元素 arr[end] 大于 tmp,则将该元素向右移动一个位置(即 arr[end+1] = arr[end]),为 tmp 腾出插入空间。
    • 如果 arr[end] 不大于 tmp,则停止扫描,因为 tmp 应该插入到这个元素之后(或与之相等的位置)。
  4. 插入元素
    • 将 tmp 插入到扫描停止时的位置(即 arr[end+1])。
  5. 重复
    • 对数组的每一个元素(从第二个元素开始)重复步骤 2 到步骤 4,直到整个数组排序完成。 

算法思想提炼

直接插入排序从数组第二个元素开始,逐个取出作为待排序元素tmp。对于每个tmp,从已排序部分(初始为第一个元素)的末尾向前扫描,若遇到比tmp大的元素则后移,为tmp腾出插入位置。找到不大于tmp的元素或扫描至开头后,将tmp插入。重复此过程直至数组完全排序。

就像整理扑克牌,每次摸一张新牌,找到并插入到已排序牌堆中的正确位置。

实现代码

//直接插入排序
void InsertSort(int* arr, int n)
{
	// 从第二个元素开始,因为第一个元素默认是有序的  
	for (int i = 0; i < n - 1; i++)
	{
		// end 变量表示当前已经排好序的数组的最后一个元素的索引  
		// tmp 变量保存当前要插入的元素的值  
		int end = i;
		int tmp = arr[end + 1];

		// 向前扫描已经排好序的数组,找到tmp应该插入的位置  
		while (end >= 0)
		{
			// 如果已经排好序的数组中的元素比tmp大,  
			// 就把这个元素向后移动一个位置,为tmp腾出空间  
			if (arr[end] > tmp)
			{
				arr[end + 1] = arr[end];
				end--; // 继续向前扫描  
			}
			else {
				// 如果已经排好序的数组中的元素不大于tmp,  
				// 那么tmp就应该插入到这个元素之后(或与之相等的位置),  
				// 循环结束  
				break;
			}
		}

		// 把tmp插入到正确的位置  
		arr[end + 1] = tmp;
	}
}

下面我们给出一个数组int arr[] = {4,6,7,2}

画图刨析

三. 直接插入排序的特性

复杂度分析

  • 时间复杂度
    • 最优情况(数组已排序):O(n)
    • 最坏情况(数组逆序):O(n^2)
    • 平均情况:O(n^2)
  • 空间复杂度:O(1),因为直接插入排序是原地排序算法,不需要额外的存储空间。

稳定性分析

在排序算法中,稳定性指的是:如果待排序的记录序列中存在多个具有相同关键字的记录,那么经过排序后,这些具有相同关键字的记录的相对次序应该保持不变。换句话说,如果两个元素在排序前的相对位置是固定的(即一个元素在另一个元素之前),且它们的值相等,那么在排序后,这两个元素的相对位置也应该保持不变。

在直接插入排序中,如果两个元素相等,插入操作不会改变它们的相对位置。也就是说,如果 A 在 B 之前,且 A 和 B 的键值相等,那么在排序过程中,A 会被插入到它原本应该在 B 之前的位置,从而保持了它们的相对顺序。因此,直接插入排序是稳定的

四. 希尔排序的基本思想

希尔排序法又称缩小增量法。希尔排序法的基本思想是:先选定一个整数(通常是gap=n/3+1),把待排序文件所有记录分成各组,所有的距离相等的记录分在同⼀组内,并对每一组内的记录进行排序,然后gap=gap/3+1得到下一个整数,再将数组分成各组,进行插入排序,当gap=1时,就相当于直接插入排序。

通过把数组不断拆分成多个直接插入排序,经过多次这样的预排序,保证了大数据在前,小数据在后,当gap = 1时,再进行最终的直接插入排序。

它是在直接插入排序算法的基础上进行改进而来的,综合来说它的效率肯定是要高于直接插入排序算法的。

五. 希尔排序算法解析

详细版本的算法思想解析

  1. 初始化增量:将增量gap初始化为数组的长度n

  2. 缩小增量:使用Hibbard增量序列(或其他有效的增量序列),在每次循环中将gap缩小为gap / 3 + 1,直到gap变为1。这个过程形成了多个逐渐减小的子序列。

  3. 分组插入排序:对于每个gap值,将数组分成若干个子序列,每个子序列由相隔gap个位置的元素组成。然后对每个子序列进行插入排序,但这里的插入排序与传统的插入排序略有不同,因为它是基于gap间隔进行的。

  4. 元素移动与插入:在插入排序的过程中,如果当前元素(tmp)小于它前面的元素(在同一个子序列中,相隔gap个位置),则将前面的元素向后移动gap个位置,为当前元素腾出插入空间。这个过程一直进行,直到找到当前元素应该插入的位置。

  5. 重复步骤:对每个gap值,重复步骤3和步骤4,直到gap变为1。当gap为1时,整个数组就变成了一个子序列,此时进行一次最终的插入排序,完成整个排序过程。

算法思想提炼

希尔排序的核心算法思想很简单:它先不急着对整个数组进行排序,而是先将数组“拆分”成若干个小子数组(这些子数组是通过每隔一定距离——我们称之为“增量gap”——选取元素形成的),然后对每个小子数组分别进行插入排序。随着排序的进行,这个“增量gap”会逐渐变小,直到最后变成1,此时整个数组就被当作一个大的子数组来进行最后一次插入排序。

换句话说,希尔排序就是先通过大间隔的插入排序让数组元素部分有序,然后逐渐缩小间隔,让元素越来越接近它们最终的位置,直到最后通过一次完整的插入排序将整个数组完全排序好。这种方法的好处是,它可以在较短的时间内让数组变得大致有序,从而减少了后续排序的工作量,提高了排序效率。

实现代码

//希尔排序
void ShellSort(int* arr, int n)
{
	// 初始化增量gap为数组长度n
	int gap = n;
	// 当gap大于1时,继续执行循环
	while (gap > 1)
	{
		// 使用Hibbard增量序列:gap = gap / 3 + 1,逐步缩小增量
		gap = gap / 3 + 1;
		// 对每个子序列进行插入排序
		for (int i = 0; i < n - gap; i++)
		{
			// end记录当前考虑的元素在子序列中的位置
			int end = i;
			// tmp记录要插入的元素
			int tmp = arr[end + gap];
			// 在子序列中从后向前扫描,为tmp找到正确的插入位置
			while (end >= 0 && arr[end] > tmp)
			{
				// 将大于tmp的元素向后移动gap个位置
				arr[end + gap] = arr[end];
				// 向前移动end,继续比较前一个元素
				end -= gap;
			}
			// 将tmp插入到正确的位置
			arr[end + gap] = tmp;
		}
	}
}

画图刨析

六. 希尔排序的特性

复杂度分析

  1. 时间复杂度

    • 最坏情况:希尔排序的时间复杂度依赖于所选的增量序列(gap sequence)。常见的增量序列有希尔增量(初始为n/2,每次减半)、Hibbard增量(形如1, 3, 7, ...)、Sedgewick增量等。对于不同的增量序列,最坏情况的时间复杂度会有所不同,但通常在O(n(3/2))之间。

    • 平均情况:尽管最坏情况的时间复杂度可能较高,但希尔排序在实际应用中通常表现较好,其平均时间复杂度接近于O(n^(3/2))或更优,具体取决于增量序列的选择。

    • 最好情况:在最佳情况下,希尔排序的时间复杂度可以达到O(n log n),但这依赖于特定的输入数据和增量序列。

  2. 空间复杂度

    • 希尔排序是原地排序算法(in-place sorting algorithm),它只需要O(1)的额外空间。

稳定性分析

  • 稳定性:希尔排序不是稳定的排序算法。稳定排序算法是指如果两个元素相等,它们在排序后的相对顺序与排序前相同。然而,希尔排序在比较和交换元素时可能会破坏这种相对顺序。例如,在较大的间隔上进行插入排序时,原本相邻且相等的元素可能会被分开,并在后续步骤中重新排序,导致它们的相对顺序发生变化。

总结

  • 时间复杂度:通常在O(n(3/2))之间,取决于增量序列的选择。

  • 空间复杂度:O(1),因为它是原地排序算法。

  • 稳定性:希尔排序不是稳定的排序算法。

兄弟们共勉 !!! 

码字不易,求个三连

抱拳了兄弟们!

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

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

相关文章

RK3568 Android12跳过认证 预置谷歌服务GMS

在Rom开发中需要发布海外版本时基本都需要内置google服务,而规范方式集成的话都需要设备进行认证,获取google应用签名等非常复杂的一套流程,一般大厂才有这些资质和资源,这里介绍一种非常规方式集成GMS,跳过设置认证流程,在RK3568 android12环境亲测有效。 谷歌全家桶中…

深度学习之卷积问题

1 卷积在图像中有什么直观作用 ​ 在卷积神经网络中&#xff0c;卷积常用来提取图像的特征&#xff0c;但不同层次的卷积操作提取到的特征类型是不相同的&#xff0c;特征类型粗分如表1所示。 ​ 表1 卷积提取的特征类型 卷积层次特征类型浅层卷积边缘特征中层卷积局部特征深…

Go语言的内置容器

文章目录 一、数组数组的定义数组声明数组特点数组元素修改 二、切片切片声明基于数组创建切片使用make()函数构造切片使用append()为切片动态添加元素\使用copy()复制新的切片数组与切片相互转换 三、Map映射Map定义使用make()函数创建map用切片作为map的值使用delete()函数删…

二叉树的各种操作补充

二叉树的各种操作补充 求二叉树的结点数求二叉树的叶结点数求二叉树的高度求二叉树的第k层结点数查找指定结点层序遍历判断二叉树是否是完全二叉树 我们任然沿用二叉树的基本信息&#xff1a; typedef char BTDataType; typedef struct BinaryTreeNode {BTDataType _data;struc…

Go语言的常用内置函数

文章目录 一、Strings包字符串处理包定义Strings包的基本用法Strconv包中常用函数 二、Time包三、Math包math包概述使用math包 四、随机数包&#xff08;rand&#xff09; 一、Strings包 字符串处理包定义 Strings包简介&#xff1a; 一般编程语言包含的字符串处理库功能区别…

Perfetto中如何使用SQL语句

在使用 Perfetto 分析 Android 性能时&#xff0c;可以通过 Perfetto 提供的内置 SQL 查询来提取和分析不同的性能数据。Perfetto 允许你在 UI 界面或命令行中运行 SQL 查询&#xff0c;提取出 Trace 数据中包含的各种性能信息&#xff0c;比如 CPU 使用率、线程状态、内存分配…

QML项目实战:自定义TextField

目录 一.添加模块 import QtQuick.Controls 1.2 import QtQuick.Controls.Styles 1.4 import QtGraphicalEffects 1.15 二.自定义TextField 1.属性设置 2.输入框设置 3.按钮开关 三.效果 1.readonly为false 2.readonly为true 四.代码 一.添加模块 import QtQuick.…

【进阶】Stable Diffusion 插件 Controlnet 安装使用教程(图像精准控制)

Stable Diffusion WebUI 的绘画插件 Controlnet 最近更新了 V1.1 版本&#xff0c;发布了 14 个优化模型&#xff0c;并新增了多个预处理器&#xff0c;让它的功能比之前更加好用了&#xff0c;最近几天又连续更新了 3 个新 Reference 预处理器&#xff0c;可以直接根据图像生产…

DAF-FM DA与NO反应后,生成的产物能够发出强烈的绿色荧光,254109-22-3

一、基本信息 产品名称&#xff1a;DAF-FM DA&#xff08;一氧化氮NO荧光探针DAF-FM&#xff09; 英文名称&#xff1a;DAF-FM DA&#xff0c;DAF-FM diacetate CAS号&#xff1a;254109-22-3 分子式&#xff1a;C25H18F2N2O7 供应商&#xff1a;陕西新研博美生物科技 分…

在 Mac 和 Windows 系统中快速部署 OceanBase

OceanBase 是一款分布式数据库&#xff0c;具备出色的性能和高扩展性&#xff0c;可以为企业用户构建稳定可靠、灵活扩展性能的数据库服务。本文以开发者们普遍熟悉的Windows 或 Mac 环境为例&#xff0c;介绍如何快速上手并体验OceanBase。 一、环境准备 1. 硬件准备 OceanB…

使用Ant Design的Layout布局不能撑满整个屏幕问题解决方法

代码示例&#xff1a; import React, { useState } from react import {LaptopOutlined,NotificationOutlined,UserOutlined, } from ant-design/icons import type { MenuProps } from antd import { Layout, Menu, theme } from antd import routes from ./routes/index imp…

【ubuntu18.04】使用U盘制作ubuntu18.04启动盘操作说明

打开show application 打开Startup Disk 选择镜像 双击选择ubuntu的iso镜像 镜像下载地址 Ubuntu 18.04.6 LTS (Bionic Beaver) 制作镜像 注意&#xff1a; 制作镜像会格式化U盘&#xff0c;记得备份资料 点击Make Startup Disk,弹出如下对话框 点击Yes 输入管理员密码&a…

22.04Ubuntu---ROS2创建python节点

创建工作空间 mkdir -p 02_ros_ws/src 然后cd到该目录 创建功能包 在这条命令里&#xff0c;tom就是你的功能包 ros2 pkg create tom --build-type ament_python --dependencies rclpy 可以看到tom功能包已经被创建成功了。 使用tree命令&#xff0c;得到如下文件结构 此时…

《手写Spring渐进式源码实践》实践笔记(第十七章 数据类型转换)

文章目录 第十七章 数据类型转换工厂设计实现背景技术背景Spring数据转换实现方式类型转换器&#xff08;Converter&#xff09;接口设计实现 业务背景 目标设计实现代码结构类图实现步骤 测试事先准备属性配置文件转换器工厂Bean测试用例测试结果&#xff1a; 总结 第十七章 数…

使用docker形式部署jumpserver

文章目录 前言一、背景二、使用步骤1.基础环境准备2.拉取镜像3.进行部署4.备份记录启动命令 前言 记录一下使用docker形式部署jumpserver服务的 一、背景 搭建一个jumpserver的堡垒机&#xff0c;但是发现之前是二进制文件部署的&#xff0c;会在物理机上部署污染环境&#x…

(62)使用RLS自适应滤波器进行系统辨识的MATLAB仿真

文章目录 前言一、基本概念二、RLS算法原理三、RLS算法的典型应用场景四、MATLAB仿真代码五、仿真结果1.滤波器的输入信号、参考信号、输出信号、误差信号2.对未知系统进行辨识得到的系数 总结与后续 前言 RLS&#xff08;递归最小二乘&#xff09;自适应滤波器是一种用于系统…

算法每日双题精讲——滑动窗口(长度最小的子数组,无重复字符的最长子串)

&#x1f31f;快来参与讨论&#x1f4ac;&#xff0c;点赞&#x1f44d;、收藏⭐、分享&#x1f4e4;&#xff0c;共创活力社区。 &#x1f31f; 别再犹豫了&#xff01;快来订阅我们的算法每日双题精讲专栏&#xff0c;一起踏上算法学习的精彩之旅吧&#xff01;&#x1f4aa;…

MySQL数据库的备份与还原

目录 mysql 数据库的备份 生成SQL脚本 1 在控制台使用mysqldump命令可以用来生成指定数据库的脚本 ​编辑2 在数据库图形化界面工具&#xff1a;DateGrip 中操作&#xff1a;导出 mysql 数据库的还原 执行SQL脚本 1 在控制台使用 命令&#xff0c;指定将sql脚本导入到指定…

使用 IDEA 创建 Java 项目(二)

IDEA 创建 Java 项目 一般创建 Java 项目可以创建一个空项目&#xff0c;然后在空项目中添加模块&#xff0c;在模块中编写包&#xff0c;包中包含 Java 类。 如果你的 JDK 没有被 IDEA 自动找到的话&#xff0c;可以手动选择 JDK。我们先来学习 Intellij 构建系统下的 Java …

图论算法:最短路径算法详解【c语言版】(无权最短路径、Dijkstra算法)

别忘了请点个赞收藏关注支持一下博主喵&#xff01;&#xff01;&#xff01; 图论算法&#xff1a;最短路径算法详解 在图论中&#xff0c;最短路径问题是寻找图中两点之间具有最小总权重的路径。这个问题在许多实际应用中都有重要的作用&#xff0c;比如网络路由、城市交通规…