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

news2025/1/17 1:46:46

1. 排序的概念及其意义

1.1 排序的概念

所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。

1.2 排序的稳定性

  • 假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。
  • 在排序算法中,稳定性是一个十分重要的评判标准。它在我们生活中的某些方面有着作用,比如一个有时间限制的竞赛项目,当两人分数相同时,先提交的选手的排名要比后提交的选手的排名高。这就必须使用具有稳定的排序了。

1.3 常见的排序算法

常见的排序有冒泡排序,插入排序,希尔排序,选择排序,堆排序,快速排序,归并排序,计数排序等,其中我们最常用的是快速排序
在这里插入图片描述

插入排序

基本思想:

把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列 。

实际中我们玩扑克牌时,就用了插入排序的思想
在这里插入图片描述

2. 直接插入排序

基本思路:

当插入第i(i>=1)个元素时,前面的arr[0],arr[1],…,array[i-1]已经排好序,此时用arr[i]的排序码与arr[i-1],arr[i-2],…的排序码顺序进行比较找到插入位置即将arr[i]插入,原来位置上的元素顺序后移

如下图所示:
假设我们要排升序5 2 4 6 1 3
在这里插入图片描述

  1. 把首元素5看成是有序的,我们用tmp保存下一个元素,即tmp = 2,再让tmp与前面已经有序的数据进行比较,此时tmp < 5,所以把5后挪,把tmp插入到5的前面,保持有序;
  2. 接着再让tmp保存第三个元素,即tmp = 4,再让tmp与前面已经有序的数据进行比较,此时tmp < 5但是tmp > 2,所以把5后挪,把tmp插入到5的前面,2的位置不动,保持有序;
  3. ……重复上述上述步骤,以此类推……
  • 先考虑单趟排序的实现,代码如下:
int end ;
int tmp = arr[end + 1];//保存下一个值

while (end >= 0)
{
	if (tmp < arr[end])
	{
		arr[end + 1] = arr[end];//把前面的数按顺序往后挪
		end--;
	}
	else
	{
		break; //比前一个数要大时,说明找到了位置
	}
}

arr[end+1] = tmp;//包含了两种情况:1是在比较的过程中比前一个数要大,就把tmp放进去
                     //2是把前面的数都比完了,此时end==-1了,就把tmp放到最前面

这里的跳出循环包括两种情况:

  1. 在tmp与前面的有序数据比较的过程中,tmp的值比最后一个有序数据大,进入了else语句,break直接跳出循环。例如上图中的第3次排序过程。
  2. tmp比前面的有序数据都要小,一直end–,直到end < 0时,不满足循环条件,跳出循环。例如上图中的第4次排序过程。

而把arr[end+1] = tmp放在循环外面也是由于这两种情况,无论是第1种情况还是第2种情况,在循环结束后都要把待排序的元素放到指定是位置上。所以抽离这条语句放在循环外。

  • 再考虑排序的整体过程,代码实现如下:

i是控制已经有序的数据的最后一个数据,是一个边界。

void InsertSort(int* arr, int sz)
{
	for (int i = 0; i < sz - 1; i++)
	{
		int end = i;
		int tmp = arr[end + 1];//保存下一个值

		while (end >= 0)
		{
			if (tmp < arr[end])
			{
				arr[end + 1] = arr[end];//把前面的数按顺序往后挪
				end--;
			}
			else
			{
				break; //比前一个数要大时,说明找到了位置
			}
		}
		arr[end+1] = tmp;
	}
}

void PrintArray(int* arr, int sz)
{
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}

int main()
{
	int arr[] = { 6,7,9,2,4,3,5,1,0,8,-1};
	int sz = sizeof(arr) / sizeof(int);

     InsertSort(arr, sz);
     PrintArray(arr, sz);
     
     return 0;
}

排序结果为:
在这里插入图片描述

2.2 时间复杂度的分析

  1. 最好:当待排序的元素为顺序时,时间复杂度0(N)。
  2. 最坏:当待排序的元素为逆序时,时间复杂度0(N*N)。

所以,直接插入排序的时间复杂度是0(N*N)。

2.3 排序的稳定性分析

  • 在上述代码排序过程中,当遇到两个相同数据时,会进入else语句,直接跳出循环,不会发生位置的挪动,即两个相同数据的相对位置依旧保持不变,所以直接插入排序是稳定的。

3. 希尔排序(缩小增量排序)

希尔排序是一种基于插入排序的改进算法。通过引入间隔的概念来改进插入排序的性能。

3.1 基本思想:

通过设置间隔,而对于每个间隔上的元素再进行插入排序,一趟排序后,间隔变小,再继续对此次间隔上的元素进行插入排序,直到间隔为1,此时就是一次完整的直接插入排序

在这里插入图片描述

  • 下面的排序代码与图解都是降序。

希尔排序分为两个步骤:

  1. 预排序
    预排序是让数组接近有序,需要通过分组来排,间隔为gap的是一组。 间隔为3时的一组预排序图解:
    ![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-blog.csdnimg.cn/direct/f6fe8bbfab294df69ae114525b900e07.png
    所以要进行多组间隔为gap的预排序,gap由大变小。

  2. 直接插入排序
    当gap = 1时,就是一次直接插入排序。

3.2 对预排序的代码实现:

  • 首先考虑间隔gap = 3时的单趟排序
int gap = 3;
int end ;
int tmp = arr[end + gap];

while (end >= 0)
{
	if (tmp < arr[end + gap])
	{
		arr[end + gap] = arr[end];
		end -= gap;
	}
	else
	{
		break;
	}
}
arr[end + gap] = tmp;

这里跳出循环的情况与上文的直接插入排序的两种情况一模一样。

图解如下:
![![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=https%3A%2F%2Fimgblog.csdnimg.cn%2Fdirect%2F7ff366ecbedf469bb5eeb644b0598c93.png&pos_id=img-ahdsKvHT-1712387936626](https://img-blog.csdnimg.cn/direct/3afc22d489bf4076830300497b5d8ffa.png

  • 然后把间隔为gap = 3的多组数据同时排:

int gap = 3;
for (int i = 0; i < sz - gap; i++)
{
	int end = i;
	int tmp = arr[end + gap];

	while (end >= 0)
	{
		if (tmp > arr[end])
		{
			arr[end + gap] = arr[end];
			end -= gap;
		}
		else
		{
			break;
		}
	}
	arr[end + gap] = tmp;
}

部分图解如下:
每趟都是间隔为3的元素之间的一次直接插入排序。其中 i 控制end的走法,endgap控制每次排序的走法。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 接下来考虑如何取gap的值,就是如何让gap的值由大变小:
    gap越大,小的数可以越快到后面,大的数可以越快到前面,同时,gap越大,相对于gap越小而言预排完后越不接近有序。
void ShellSort(int* arr, int sz)
{
	int gap = sz;
	while (gap > 1)
	{
		gap = gap / 2;         // O(log2N)
		//gap = gap / 3 + 1;   // O(log3N)

		//对多组间隔为gap的数据同时排序
		for (int i = 0; i < sz - gap; i++)
		{
			int end = i;
			int tmp = arr[end + gap];

			while (end >= 0)
			{
				if (tmp > arr[end])
				{
					arr[end + gap] = arr[end];
					end -= gap;
				}
				else
				{
					break;
				}
			}
			arr[end + gap] = tmp;
		}
	}
	
}

gap的取值有两种形式,首先它与数组元素个数挂钩,让gap = sz

  1. gap = gap / 2 ;当满足gap > 1时,任何数除2最后结果都是1,对应了gap = 1时就是直接插入排序。
  2. gap = gap / 3 + 1;当满足gap > 1时,为了使最后 gap= 1,所以要+1。

排序结果为:
在这里插入图片描述

3.3 时间复杂度分析:
希尔排序的时间复杂度与数组元素的个数和和gap的取值有关。假设元素个数为N个。
当gap很大时,下面两层循环预排序接近O(N)
当gap很小时,数组已经很接近有序了,差不多也是O(N)

gap = gap / 2时,第一个循环大致执行log2N次(以2为底N的对数)
gap = gap / 3 + 1时,第一个循环大致执行log3N次(以3为底N的对数)

所以希尔排序的时间复杂度是O(N * log2N)或是O(N * log3N)。

3.4 稳定性分析:

不稳定,考虑序列(2,2,1),排序后序列为(1,2,2),我们发现2的相对位置发生了变化(粗体2与非粗体2),所以是不稳定的排序算法。

4. 两种排序的性能对比

clock() 函数是 <time.h> 头文件中的一个函数,用来返回程序启动到函数调用时之间的CPU时钟周期数。这个值通常用来帮助衡量程序或程序的某个部分的性能

我们可以用这个函数进一步对比两种排序占用的CPU时间

代码实现为:

void TestOP()
{
	    srand(time(0));
		const int N = 100000;
		int* a1 = (int*)malloc(sizeof(int) * N);
		int* a2 = (int*)malloc(sizeof(int) * N);
		
	for (int i = 0; i < N; ++i)
	{
		a1[i] = rand();
		a2[i] = a1[i];
	}
	
	int begin1 = clock();
	InsertSort(a1, N);
	int end1 = clock();
	
	int begin2 = clock();
	ShellSort(a2, N);
	int end2 = clock();
	
	printf("InsertSort:%d\n", end1 - begin1);
	printf("ShellSort:%d\n", end2 - begin2);
	
	free(a1);
	free(a2);
}

这里让随机生成十万个随机数,分别用希尔排序和直接插入排序来进行排序,测试两种算法的执行时间:
在这里插入图片描述

通过上面的执行时间的对比,可以发现希尔排序的效率远远快于插入排序。

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

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

相关文章

电商系列之风控安全

> 插&#xff1a;AI时代&#xff0c;程序员或多或少要了解些人工智能&#xff0c;前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。 坚持不懈&#xff0c;越努力越幸运&#xff0c;大家…

243.回文链表

给你一个单链表的头节点 head &#xff0c;请你判断该链表是否为 回文链表 。如果是&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 示例 1&#xff1a; 输入&#xff1a;head [1,2,2,1] 输出&#xff1a;true示例 2&#xff1a; 输入&#xff1a;head …

初始Java篇(JavaSE基础语法)(6)(继承和多态)(上)

Java学习篇 个人主页&#xff08;找往期文章包括但不限于本期文章中不懂的知识点&#xff09;&#xff1a;我要学编程(ಥ_ಥ)-CSDN博客 目录 继承篇 为什么需要继承&#xff1f; 继承概念 继承的语法 父类成员访问 super关键字 子类构造方法 super和this的比较 再谈…

CLoVe:在对比视觉语言模型中编码组合语言

CLoVe:在对比视觉语言模型中编码组合语言 摘要引言相关工作CLoVe: A Framework to Increase Compositionality in Contrastive VLMsSynthetic CaptionsHard NegativesModel Patching CLoVe: Encoding Compositional Language inContrastive Vision-Language Models 摘要 近年来…

Spark 部署与应用程序交互简单使用说明

文章目录 前言步骤一&#xff1a;下载安装包Spark的目录和文件 步骤二&#xff1a;使用Scala或PySpark Shell本地 shell 运行 步骤3:理解Spark应用中的概念Spark Application and SparkSessionSpark JobsSpark StagesSpark Tasks 转换、立即执行操作和延迟求值窄变换和宽变换 S…

【.Net】Polly

文章目录 概述服务熔断、服务降级、服务限流、流量削峰、错峰、服务雪崩Polly的基本使用超时策略悲观策略乐观策略 重试策略请求异常响应异常 降级策略熔断策略与策略包裹&#xff08;多种策略组合&#xff09; 参考 概述 Polly是一个被.NET基金会支持认可的框架&#xff0c;同…

使用Flutter创建带有图标提示的TextField

在移动应用开发中&#xff0c;TextField是一种常用的用户输入小部件。然而&#xff0c;有时向用户提供有关他们应该输入什么的提示或说明是很有帮助的。在本教程中&#xff0c;我们将创建一个Flutter应用程序&#xff0c;演示如何在TextField旁边包含一个图标提示。 编写代码 …

C语言计算任意位数的水仙花数

一、水仙花数定义&#xff1a; 水仙花数&#xff08;Narcissistic number&#xff09;是指一个 n&#xff08;n≥3&#xff09; 位数&#xff0c;它的每个数位上的数字的 n 次幂之和等于它本身。例如 3 位数的 153&#xff1a;1 5 3 153 二、C语言计算任意位数的水仙花数代…

第十三届蓝桥杯C++A组 - B/D/E

文章目录 前言一、灭鼠先锋1.题目描述2.算法 二、选数异或1.题目描述2.算法 三、爬树的甲壳虫1.问题描述2.算法 前言 题目考点灭鼠先锋bfs博弈论MEX运算SG函数选数异或二分线段树爬树的甲壳虫快速幂逆元扩展欧几里得裴蜀定理dp 一、灭鼠先锋 1.题目描述 2.算法 我们先要确定…

顺序表的应用

文章目录 目录1. 基于动态顺序表实现通讯录项目2.顺序表经典算法2.1 [移除元素](https://leetcode.cn/problems/remove-element/description/)2.2 [合并两个有序数组](https://leetcode.cn/problems/merge-sorted-array/description/) 3. 顺序表的问题及思考 目录 基于动态顺序…

华为CCE部署RabbitMQ中间件操作文档

1、创建有状态&#xff08;StatefulSet&#xff09;部署 中间件一般为有状态部署&#xff0c;有状态部署与无状态部署区别参考文档&#xff1a;K8S有无状态部署-CSDN博客 1.1、基本信息 注意&#xff1a; 应用名称命名规则&#xff1a;&#xff08;命名规则最好统一&#xff…

深入理解计算机系统 家庭作业 2.85

A 7111.01.11*V E2,M1.11,f0.11 位表示: exp:10000...001其中0有k-2个.frac:1100...000其中0有n-2个 B 有个默认条件就是E>n, En,M1.111...(小数部分n个1),f0.1111(n个1),V exp:111...11其中1有n-1个.frac:111...111其中1有n个 C有个默认条件就是没有符号位.最小的规格…

轻量的 WebHook 工具:歪脖虎克

本篇文章聊聊轻量的网络钩子&#xff08;WebHook&#xff09;工具&#xff1a;歪脖虎克。 写在前面 这是一篇迟到很久的文章&#xff0c;在 21 年和 22 年的时候&#xff0c;我分享过两篇关于轻量的计划任务工具 Cronicle 的文章&#xff1a;《轻量的定时任务工具 Cronicle&a…

EFK(elasticsearch+filebeat+kibana)日志分析平台搭建

本文是记录一下EFK日志平台的搭建过程 项目背景&#xff1a; 此次搭建的日志分析平台主要是采集服务器上的java服务的log日志(输出的日志已经是json格式)&#xff0c;这些日志都已经按照不同环境输出到/home/dev /home/test1 /home/test2 目录下了&#xff0c;按照不同的应…

具身智能机器人实现新里程碑!新型3D世界模型问世

随着人工智能技术的不断进步&#xff0c;视觉-语言-动作&#xff08;VLA&#xff09;模型在机器人控制、自动驾驶、智能助手等领域展现出了广阔的应用前景。这类模型能够将视觉、语言、动作等多模态信息进行融合&#xff0c;实现从感知到决策的端到端学习。然而&#xff0c;现有…

商业开源MES+源码+可拖拽式数据大屏

商业开源的一套超有价值的JAVA制造执行MES系统源码 带本地部署搭建教程 教你如何在本地运行运行起来。 开发环境&#xff1a;jdk11tomcatmysql8springbootmaven 需要源码&#xff0c;私信我付费获取。 一、系统概述&#xff1a; 万界星空科技免费试用MES、开源MES、商业开…

SAR教程系列7——在cadence中用Spectrum工具FFT仿真ADC的ENOB、SNR等动态性能指标

首先在仿真之前&#xff0c;你得有一个ADC。然后是思考如何仿真的问题&#xff0c;如何加激励&#xff0c;如何使用相关工具查看仿真结果。假定你有一个可以仿真的ADC&#xff0c;大致经过下列步骤可以得到ADC的相关动态性能指标。 第一步&#xff1a;在ADC后面接一个理想的DA…

docker命令:查看镜像、查看正在运行的容器、终止某个正在运行的容器

2024年4月6日&#xff0c;周五下午 查看docker镜像&#xff08;image&#xff09;有哪些 docker image ls 查看正在运行的容器&#xff08;container&#xff09;有哪些 docker ps 终止正在运行的container docker stop 容器ID 用docker ps可以查到正在运行的容器的ID

如何从数码相机恢复已删除的照片?

“嗨&#xff0c;我删除了索尼数码相机中的所有照片。有什么办法可以让他们回来吗&#xff1f;” ——刘凯 我们经常从数码相机中删除照片。但是&#xff0c;如果我们误删除了一些重要的照片&#xff0c;则很难将其恢复&#xff0c;因为删除的照片可能会绕过回收站或垃圾箱&am…

docker + miniconda + python 环境安装与迁移(详细版)

本文主要列出从安装dockerpython环境到迁移环境的整体步骤。windows与linux之间进行测试。 简化版可以参考&#xff1a;docker miniconda python 环境安装与迁移&#xff08;简化版&#xff09;-CSDN博客 目录 一、docker 安装和测试 二、docker中拉取miniconda&#xff…