数据结构与算法:插入排序希尔排序

news2024/9/25 23:23:20

数据结构与算法:插入排序&希尔排序

    • 插入排序
    • 希尔排序


插入排序

假设现在你有一个有序的数组,你要把一个数据插入到数组中,保证插入后依然有序,要怎么做?

对于人来说,这个问题就像是在整理扑克牌,瞄一眼就知道应该插入什么位置。但是对于程序来说,就需要一一对比,直到找到一个位置左边比它大,右边比它小,就算找到了一合适的位置插入。

而插入排序就是基于这样的一个过程完成的排序。

比如下面这个数组,其左边是有序的,右边是无序的。
在这里插入图片描述

我们只需要将第一个无序的元素进行插入,向右一一对比,就可以找到合适的位置。
请添加图片描述
那么如果一个数组完全无序,要如何利用这个插入的思想呢?
比如对于以下数组:
在这里插入图片描述
我们可以将其视为:第一个元素有序,插入第二个元素:
在这里插入图片描述
当插入第二个元素,前两个元素就是有序的,然后插入第三个元素;接着前三个元素就是有序的,插入第四个元素…以此类推
最后前n-1个元素是有序的,第n个元素是无序的,插入第n个元素,最后整个数组都是有序的了。

过程如下:请添加图片描述
代码实现:

// 插入排序 
void InsertSort(int* a, int n)
{
	for (int i = 0; i < n - 1; i++)
	{
		//设前[0, end]是一个有序数列,将end+1插入
		int end = i;
		int tmp = a[end + 1];
		while (end >= 0)
		{
			if (tmp < a[end])
			{
				a[end + 1] = a[end];
				--end;
			}
			else
			{
				break;
			}
		}

		a[end + 1] = tmp;
	}
}

具体解释如下:

  1. 函数定义了一个名为InsertSort的函数,接收两个参数:一个整型数组a和一个整型变量n,表示数组的元素个数。

  1. 使用一个循环遍历数组a,循环变量i从0开始,每次循环递增1,直到i大于或等于n-1

  1. 在每次循环中,将数组的第i个元素设为待插入的元素,即end+1处的元素。

  1. 定义一个整型变量end,初始值为i,用于表示有序数列的末尾位置

  1. end+1处的元素暂存到变量tmp中。

  1. 使用一个循环将end+1处的元素插入到有序数列中。循环条件是end大于等于0,也就是当end等于0时,说明已经遍历到了有序数列的开头

  1. 在循环内部,判断tmpa[end]的大小关系。如果tmp小于a[end],则将a[end]后移一位,end递减1。这样,tmp大的元素就会向右移动一位,为tmp腾出插入的位置

  1. 如果tmp大于或等于a[end],则说明tmp应该插入到end+1的位置。同时跳出循环。

  1. 在跳出循环后,将tmp插入到end+1的位置,即a[end+1] = tmp

  1. 重复步骤2~9,直到遍历完整个数组a,排序完成。

希尔排序

希尔排序(Shell Sort)是插入排序的一种,它是针对直接插入排序算法的改进。
希尔排序又称缩小增量排序,因 DL.Shell 于 1959 年提出而得名。

假设我们现在有一个完全逆序的数组,如果此时使用插入排序对其排序,那么第二个元素就要和第一个元素交换,第三个元素要和前两个全部交换一一次…第n个元素要和前n-1个元素交换一次,这不是把时间复杂度拉满了吗。

于是有人就开始考虑要如何处理这种逆序环境下插入排序要全部交换一次的问题。
插入排序在这种情况下排序效率低的原因在于,当一个比较小的元素处于后面时,其要和前面的所有比他大的元素对比一次,比如这样:
在这里插入图片描述
这个8想要到达正确位置,就要和前面所有的元素进行一次比大小,这会造成很多的计算量,想要优化这个问题,其实就是解决:如何让比较小的元素快速到达数组的前面?

于是希尔排序诞生了,我们可以将一个数组分为分为多组,对每一组单独进行一次插入排序,比如这样:
在这里插入图片描述
这是一个8个数字的数组,我们将其分为4份,每两个数字一组,上面每种颜色为一组,然后让他们进行一次插入排序。排序后,虽然数组依然无序,但是整体上来说,前面的数字比较小,后面的数字比较大,通过这种方法,可以将后面的小元素快速提到前面。

我们再利用一个较大的数据看一次:
在这里插入图片描述
我们现在对这个数组分区,分为两份,左边是一份,右边是一份。其中左边的第一个元素和右边的第一个元素是一组,左边的第二个元素和右边的第二个元素是一组…左边的最后一个元素和右边的最后一个元素是一组。
这样我们就把这个数组分为了两两一组,然后每两个元素进行一次插入排序:

请添加图片描述

这样一趟排序后,我们看看数组的状态:
在这里插入图片描述
整体上来说,左边的数据要小于右边的数据,这不就达成了我们想要的效果:让比较小的元素快速到达数组的前面。这个过程,每个数据都只进行了一次交换,就到达了比较前的位置。这个过程叫做预排序

希尔排序是基于插入排序的算法,通过分组对元素进行插入排序来提高效率。

上述过程的代码如下:

int gap = n / 2;

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

首先,定义一个变量 gap,其值为数组的长度 n 除以 2。gap 的值决定了分组的大小,可以根据具体情况进行调整。

然后,使用一个循环遍历数组 a,从前往后依次处理每个元素。循环的结束条件是 i < n - gap,即遍历到倒数第 gap 个元素时结束。

在循环内部,首先定义一个变量 end,并将其赋值为当前遍历的元素下标 i。然后,定义一个变量 tmp,并将其赋值为 a[end + gap],即当前元素所在分组的下一个元素。

接下来,使用一个循环将当前元素插入到正确的位置。循环的条件是 end >= 0,即遍历到数组的起始位置时结束。在循环内部,做如下操作:

  • 如果 tmp 的值小于当前位置的元素 a[end],则将 a[end] 的值赋给 a[end + gap],并将 end 的值减去 gap
  • 如果 tmp 的值大于等于当前位置的元素 a[end],则退出循环。

最后,将 tmp 的值赋给最终位置上的元素,即 a[end + gap]

但是这样还不是一个完整的希尔排序,设想一下,如果我们的数据量很庞大,是不是要多进行几次预排序才比较好?这样小的数据才更可能再数组的前面。
所以我们的gap也是动态变化的,在希尔排序中,一开始我们两两一组进行了插入排序,我们可以再进行4个一组插入排序,接着8个一组插入排序…以此类推,直到n个一组,将整个数组作为一组是,也就是最后一趟插入排序,这时候数组已经十分接近有序,最后对整个数组进行一次插入排序,数组就有序了。

过程如下:
请添加图片描述
经过这一次每四个一组的排序,我们的数组状态如下:
在这里插入图片描述
可以看到,数组整体呈阶梯递增趋势了。

接着进行后续排序:
请添加图片描述
可以发现,数组越来越接近有序,最后一趟排序,几乎每个元素都没有移动几次就插入到了目标位置,这就是希尔排序的完整推导思路。

外层控制gap的代码如下:

// 希尔排序
void ShellSort(int* a, int n)
{
	int gap = n;
	while (gap > 1)
	{
		gap = gap / 2;

		//插入排序
	}
}

在这段代码中,首先定义了一个变量gap,它表示初始的增量,也即是划分的分组的大小。初始化时,gap的值为待排序数组的大小n

然后,代码进入一个while循环,循环条件是gap > 1,也就是说当gap为1时,循环结束。在每一轮循环中,gap的值减半,并更新为新的增量。这样,希尔排序就是通过不断缩小增量的大小来进行排序。

在每一轮循环中,代码执行了一个"插入排序"的操作。

最终,当增量gap减小到1时,希尔排序的整个过程结束,待排序数组完成排序。

总代码如下:

// 希尔排序
void ShellSort(int* a, int n)
{
	int gap = n;
	while (gap > 1)
	{
		gap = gap / 2;

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

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

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

相关文章

优化 - 重构一次Mysql导致服务器的OOM

概述 优化了一次前后端处理不当导致的CPU的一次爆机行为&#xff0c;当然&#xff0c;这和服务器的配置低也有着密不可分的关系&#xff0c;简单的逻辑学告诉我们&#xff0c;要找到真正的问题&#xff0c;进行解决&#xff0c;CPU爆机的关键点在于前后端两个方面&#xff0c;…

在CentOS中,对静态HTTP服务的性能监控

在CentOS中&#xff0c;对静态HTTP服务的性能监控和日志管理是确保系统稳定运行和及时发现潜在问题的关键。以下是对这一主题的详细探讨。 性能监控 使用工具监控&#xff1a;top、htop、vmstat、iostat等工具可以用来监控CPU、内存、磁盘I/O等关键性能指标。这些工具可以实时…

Linux中常使用的命令之ls、cd、pwd、mkdir、rmdir

ls: 列出目录 cd&#xff1a;切换目录 pwd&#xff1a;显示目前的目录 mkdir&#xff1a;创建一个新的目录 -m &#xff1a;配置文件的权限-p &#xff1a;帮助你直接将所需要的目录(包含上一级目录)递归创建起来&#xff01; rmdir&#xff1a;删除一个空的目录 注意这…

RK3568驱动指南|第十二篇 GPIO子系统-第130章 GPIO的调试方法

瑞芯微RK3568芯片是一款定位中高端的通用型SOC&#xff0c;采用22nm制程工艺&#xff0c;搭载一颗四核Cortex-A55处理器和Mali G52 2EE 图形处理器。RK3568 支持4K 解码和 1080P 编码&#xff0c;支持SATA/PCIE/USB3.0 外围接口。RK3568内置独立NPU&#xff0c;可用于轻量级人工…

maya显示方式及视图操作

原始图像&#xff1a; 按数字键2后&#xff08;平滑效果&#xff09;&#xff1a; 按数字键3后&#xff08;平滑效果&#xff0c;无原始外边框&#xff09;&#xff1a; 按数字键4后&#xff08;仅显示边框&#xff09;&#xff1a; 方便选择后面的点、线及面 按数字键5后&…

Pytest测试 —— 如何使用属性来标记测试函数!

在软件开发领域&#xff0c;单元测试是确保代码质量和可维护性的关键一环。随着项目的不断发展&#xff0c;测试用例的管理变得愈发复杂&#xff0c;而一些测试可能需要特殊的处理、环境或者标记。在Python中&#xff0c;我们可以通过使用属性&#xff08;Attribute&#xff09…

大语言模型向量数据库

大语言模型&向量数据库 LARGE LANGUAGE MODELSA. Vector Database & LLM WorkflowB. Vector Database for LLMC. Potential Applications for Vector Database on LLMD. Potential Applications for LLM on Vector DatabaseE. Retrieval-Based LLMF. Synergized Exampl…

解决ELK日志收集中Logstash报错的关键步

ElK执行日志收集的时候logstash报错&#xff1a; Failed to execute action {:action>LogStash::PipelineAction::Create/pipeline_id:main, :exception>“LogStash::ConfigurationError”, :message>“Expected one of [^\r\n], “\r”, “\n” at line 88, column 4…

BikeDNA(九) 特征匹配

BikeDNA&#xff08;九&#xff09; 特征匹配 特征匹配采用参考数据并尝试识别 OSM 数据集中的相应特征。 特征匹配是比较单个特征而不是研究区域网格单元水平上的特征特征的必要前提。 方法 将两个道路数据集中的特征与其数字化特征的方式以及边缘之间潜在的一对多关系进行…

what is BERT?

BERT Introduction Paper 参考博客 9781838821593_ColorImages.pdf (packt-cdn.com) Bidirectional Encoder Representation from Transformer 来自Transformer的双向编码器表征 基于上下文&#xff08;context-based&#xff09;的嵌入模型。 那么基于上下文&#xff08;…

imgaug库指南(19):从入门到精通的【图像增强】之旅

引言 在深度学习和计算机视觉的世界里&#xff0c;数据是模型训练的基石&#xff0c;其质量与数量直接影响着模型的性能。然而&#xff0c;获取大量高质量的标注数据往往需要耗费大量的时间和资源。正因如此&#xff0c;数据增强技术应运而生&#xff0c;成为了解决这一问题的…

“所有伙食开销统计:轻松查看,智能管理你的餐饮支出“

你是否经常为伙食开销感到困扰&#xff0c;不知道如何有效控制和管理&#xff1f;现在&#xff0c;有了我们的伙食开销统计工具&#xff0c;这些问题将得到轻松解决&#xff01; 首先第一步&#xff0c;我们要进入晨曦记账本并在上方功能栏里选择“查看方式”。并在弹出来的列表…

数据结构第十三弹---链式二叉树基本操作(上)

链式二叉树 1、结构定义2、手动创建二叉树3、前序遍历4、中序遍历5、后序遍历6、层序遍历7、计算结点个数8、计算叶子结点个数9、计算第K层结点个数10、计算树的最大深度总结 1、结构定义 实现一个数据结构少不了数据的定义&#xff0c;所以第一步需要定义二叉树的机构。 typ…

API Monitor简易使用教程 监控Windows dll调用 监控Windows API调用 查看函数名,参数类型,参数,返回值

先看效果&#xff0c;可以显示所有dll及windows api的指定函数调用&#xff0c;以及传递的参数查看与修改。 官网下载 也有教程 我验证使用方法 1、API Filter窗口&#xff1a;选定要监听的dll函数或windows API&#xff0c;可以打断点 选中并右键勾上Breakpoint 选 Before C…

MFC为资源对话框添加消息处理函数和初始化控件

现在我VC6新建了一个对话框工程&#xff1b;又在资源添加了一个新的对话框&#xff0c;并为新的对话框添加了名为CTestDlg的类&#xff1b; 在主对话框的cpp文件包含#include "TestDlg.h"&#xff1b; 在主对话框的cpp文件的OnInitDialog()成员函数中&#xff0c;添…

web学习笔记(十五)

目录 1.Date对象 1.1日期对象的概念 1.2Date()方法的使用 1.3Date()常用方法汇总 1.4例题&#xff1a;用函数编写一个倒计时 2.函数 2.1函数的概念 2.2函数的使用 2.3函数的参数 2.4函数的声明 2.5函数的返回值 2.6异步函数 3特殊函数类型 3.1匿名函数 3.2箭头函数…

挖种子小游戏

欢迎来到程序小院 挖种子 玩法&#xff1a;看到种子点击鼠标左键进行挖种子&#xff0c;30秒内看你能够挖多少颗种子&#xff0c;快去挖种子吧^^。开始游戏https://www.ormcc.com/play/gameStart/251 html <canvas id"canvas" width"640" height"…

BSC/平衡记分卡

一、Balanced Score Card BSC即平衡计分卡&#xff08;Balanced Score Card&#xff09;&#xff0c;是常见的绩效考核方式之一&#xff0c;是从财务、客户、内部运营、学习与成长四个角度&#xff0c;将组织的战略落实为可操作的衡量指标和目标值的一种新型绩效管理体系。 是…

关于lombok插件的使用

在 idea 中有个非常好用的插件 lombok&#xff0c;可以用来在实体类中自动生成 get 、set以及构造方法&#xff0c;下面我们来学习如何使用它&#xff1a; 首先打开settings&#xff0c;按照以下方法&#xff1a; 到 marketplace 中搜索 lombok&#xff0c;我这里已经安装好了…

STM32——OLED实验

1.OLED简介 OLED&#xff0c;即有机发光二极管 OLED引脚说明 引脚说明&#xff1a; 1、CS&#xff1a;OLED片选信号&#xff08;低电平有效&#xff09; 2、WR&#xff1a;向OLED写入数据 3、RD&#xff1a;向OLED读取数据 4、D[7:0]&#xff1a;8位双向数据线&#xff0c;有…